diff options
Diffstat (limited to 'Src/Plugins/Input')
745 files changed, 172026 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 diff --git a/Src/Plugins/Input/in_cdda/AUDIO.Cpp b/Src/Plugins/Input/in_cdda/AUDIO.Cpp new file mode 100644 index 00000000..6d378ecd --- /dev/null +++ b/Src/Plugins/Input/in_cdda/AUDIO.Cpp @@ -0,0 +1,112 @@ +#include <windows.h> +#include "Audio.h" +#include "main.h" +#include "api__in_cdda.h" + +static int start_clock,paused; +static HWAVEIN hWaveIn; +static short data_latest[576*2]; +static short data_buffers[2][576*2]; +static WAVEHDR wave_hdrs[2]; +static HANDLE hThread; +static DWORD WINAPI Thread(LPVOID _a); +int a_v = -666, a_p = 0, initted = 0; + +int audioInit(/*int sample*/) +{ + WAVEFORMATEX wft = {0}; + DWORD threadid; + start_clock=GetTickCount(); + paused=0; +// if (!sample) return initted=0; + wft.wFormatTag = WAVE_FORMAT_PCM; + wft.nChannels = 2; + wft.nSamplesPerSec = 44100; + wft.nBlockAlign = 2*2; + wft.nAvgBytesPerSec = wft.nSamplesPerSec*wft.nBlockAlign; + wft.wBitsPerSample = 16; + wft.cbSize = 0; + if (waveInOpen(&hWaveIn,WAVE_MAPPER,&wft,0,0,0) != MMSYSERR_NOERROR) + { + return 1; + } + for (int x = 0; x < 2; x ++) + { + memset(&wave_hdrs[x],0,sizeof(wave_hdrs[x])); + wave_hdrs[x].lpData = (char *) data_buffers[x]; + wave_hdrs[x].dwBufferLength = 576*2*sizeof(short); + waveInPrepareHeader(hWaveIn,&wave_hdrs[x],sizeof(wave_hdrs[0])); + waveInAddBuffer(hWaveIn,&wave_hdrs[x],sizeof(wave_hdrs[0])); + } + initted=1; + done=0; + waveInStart(hWaveIn); + hThread = CreateThread(NULL,0,Thread,(LPVOID)&done,0,&threadid); + SetThreadPriority(hThread, (int)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + return 0; +} + +void audioPause(int s) +{ + if (s) + { + if (!paused) + { + paused=1; + if (initted) waveInStop(hWaveIn); + start_clock = GetTickCount()-start_clock; + } + } + else + { + if (paused) + { + if (initted) waveInStart(hWaveIn); + start_clock=GetTickCount()-start_clock; + paused=0; + } + } +} + +void audioQuit() +{ + if (!initted) return; + done=1; + WaitForSingleObject(hThread,INFINITE); + waveInStop(hWaveIn); + waveInReset(hWaveIn); + while (waveInClose(hWaveIn) == WAVERR_STILLPLAYING) Sleep(10); + CloseHandle(hThread); +} + +int audioGetPos() +{ + if (paused) return start_clock; + return GetTickCount()-start_clock; +} + + +void audioSetPos(int ms) +{ + start_clock = GetTickCount()-ms; +} + +static DWORD WINAPI Thread(LPVOID _a) +{ + volatile int *a = (volatile int *)_a; + int w; + while (!*a) + { + Sleep(576000/44100); + if (wave_hdrs[0].dwFlags & WHDR_DONE) w=0; + else if (wave_hdrs[1].dwFlags & WHDR_DONE) w=1; + else continue; + memcpy(data_latest,wave_hdrs[w].lpData,576*2*sizeof(short)); + wave_hdrs[w].dwFlags=WHDR_PREPARED; + waveInAddBuffer(hWaveIn,&wave_hdrs[w],sizeof(wave_hdrs[0])); +// memset(data_latest,0,576*2*sizeof(short)); + line.VSAAddPCMData(data_latest,2,16,0); + line.SAAddPCMData(data_latest,2,16,0); + } + return 0; +} diff --git a/Src/Plugins/Input/in_cdda/AUDIO.H b/Src/Plugins/Input/in_cdda/AUDIO.H new file mode 100644 index 00000000..9d2c65db --- /dev/null +++ b/Src/Plugins/Input/in_cdda/AUDIO.H @@ -0,0 +1,6 @@ +int audioInit(/*int*/); +int audioGetPos(); +void audioSetPos(int ms); +void audioGetWaveform(unsigned short data[576*2]); +void audioQuit(); +void audioPause(int s); diff --git a/Src/Plugins/Input/in_cdda/CDDB.Cpp b/Src/Plugins/Input/in_cdda/CDDB.Cpp new file mode 100644 index 00000000..626cc329 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/CDDB.Cpp @@ -0,0 +1,1709 @@ +#include "main.h" +#include "api__in_cdda.h" + +#include "cddbinterface.h" +#include "cddb.h" +#include "../nu/AutoWide.h" +#include "../nu/AutoChar.h" +#include "../winamp/wa_ipc.h" +#include "../nu/ns_wc.h" +#include ".\cddbevnt.h" +#include ".\cddbui.h" +#include ".\grabwnd.h" +#include <api/application/api_application.h> +#include <atlbase.h> +#include "../nde/ndestring.h" +#include "../Winamp/buildtype.h" +#include <commctrl.h> +#include <strsafe.h> + +// {C0A565DC-0CFE-405a-A27C-468B0C8A3A5C} +static const GUID internetConfigGroupGUID = + { 0xc0a565dc, 0xcfe, 0x405a, { 0xa2, 0x7c, 0x46, 0x8b, 0xc, 0x8a, 0x3a, 0x5c } }; + +TRACKINFO::~TRACKINFO() +{ + Reset(); +} + +void TRACKINFO::Reset() +{ +ndestring_release(artist); + artist=0; + ndestring_release(title); + title=0; + ndestring_release(tagID); + tagID=0; + ndestring_release(composer); + composer=0; + ndestring_release(conductor); + conductor=0; + ndestring_release(extData); + extData=0; + ndestring_release(remixing); + remixing=0; + ndestring_release(isrc); + isrc=0; +} + +TRACKINFO::TRACKINFO(const TRACKINFO ©) +{ + operator =(copy); +} + +TRACKINFO &TRACKINFO::operator =(const TRACKINFO ©) +{ + ndestring_retain(artist=copy.artist); + ndestring_retain(title=copy.title); + ndestring_retain(tagID=copy.tagID); + ndestring_retain(composer=copy.composer); + ndestring_retain(conductor=copy.conductor); + ndestring_retain(extData=copy.extData); + ndestring_retain(remixing=copy.remixing); + ndestring_retain(isrc=copy.isrc); + return *this; +} + +void DINFO::Reset() +{ + ndestring_release(title); + title=0; + ndestring_release(artist); + artist=0; + ndestring_release(tuid); + tuid=0; + ndestring_release(year); + year=0; + ndestring_release(genre); + genre=0; + ndestring_release(label); + label=0; + ndestring_release(notes); + notes=0; + ndestring_release(conductor); + conductor=0; + ndestring_release(composer); + composer=0; + ndestring_release(remixing); + remixing=0; + discnum=0; + numdiscs=0; +} + +DINFO::~DINFO() +{ + Reset(); +} + +DINFO::DINFO(const DINFO ©) +{ + operator =(copy); +} + +DINFO &DINFO::operator =(const DINFO ©) +{ + ndestring_retain(title=copy.title); + ndestring_retain(artist=copy.artist); + ndestring_retain(tuid=copy.tuid); + ndestring_retain(year=copy.year); + ndestring_retain(genre=copy.genre); + ndestring_retain(label=copy.label); + ndestring_retain(notes=copy.notes); + compilation=copy.compilation; + discnum=copy.discnum; + numdiscs=copy.numdiscs; + ntracks=copy.ntracks; + ndestring_retain(conductor=copy.conductor); + ndestring_retain(composer=copy.composer); + ndestring_retain(remixing=copy.remixing); + for (int i=0;i<sizeof(tracks)/sizeof(*tracks);i++) + tracks[i]=copy.tracks[i]; + + CDDBID=copy.CDDBID; + memcpy(pnFrames, copy.pnFrames, sizeof(pnFrames)); + nDiscLength=copy.nDiscLength; + + populated=copy.populated; + return *this; +} + + +#define TM_INVOKERESULTS (WM_APP + 222) +#define TM_UNINITTHREAD (WM_APP + 2) + +#define CENTER_PARENT (-32000) +#define CENTER_LEFT 0x0001 +#define CENTER_TOP 0x0002 + +#define WASABI_WND_CLASSW L"BaseWindow_RootWnd" + +#define INVARIANT_LCID MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT) + +typedef struct _MEDIALOOKUP +{ + HRESULT result; + HWND hwndInfo; + CDDB_CB callback; + ULONG_PTR user; + UINT flags; + BSTR bstrTOC; + DWORD threadId; +} MEDIALOOKUP; + +typedef struct _THREADDATA +{ + LONG ref; + HHOOK hhook; + BOOL uninitCom; +} THREADDATA; + +CRITICAL_SECTION lockThread; + +typedef struct _INVOKEDATA +{ + MEDIALOOKUP lookup; + ICddbDisc *pDisc; + HANDLE evntDone; + DWORD *pdwAutoCloseDelay; +} INVOKEDATA; + +static DWORD tlsSlot = TLS_OUT_OF_INDEXES; +#ifndef IGNORE_API_GRACENOTE +ICDDBControl *pCDDBControl=0; +static CDBBEventManager eventMngr; +#endif +static MEDIALOOKUP g_lookup = {0, 0, }; +static HANDLE evntBusy = NULL; +static LONG evntBusyRef = 0; +static HWND hwndProgressListener = NULL; +static ICddbDisc *pSubmitDisc = NULL; +static POINT g_lastcddbpos = { CENTER_PARENT, CENTER_PARENT}; // center + + +static HRESULT SetupCDDB(BOOL resetError); +static void CALLBACK Cddb_OnCommandCompleted(LONG lCommandCode, HRESULT hCommandResult, VARIANT *pCommandData, UINT_PTR user); +static void CALLBACK Cddb_OnCommandProgress(LONG lCommandCode, LONG lProgressCode, LONG lBytesDone, LONG lBytesTotal, UINT_PTR user); +#ifndef IGNORE_API_GRACENOTE +static void Cddb_OnMediaLookupCompleted(HRESULT hr, CDDBMatchCode matchCode, MEDIALOOKUP *pLookup); +static void Cddb_OnGetFullDiscInfoCompleted(HRESULT hr, ICddbDisc *pDisc, MEDIALOOKUP *pLookup); +static void Cddb_OnSubmitDiscCompleted(HRESULT result, MEDIALOOKUP *pLookup); + +static HRESULT Cddb_DoFuzzyMatchDlg(HWND hwndCaller, UINT flags, ICddbDiscs *pDiscs, LONG *plVal); +static HRESULT Cddb_DoSubmitNewDlg(HWND hwndCaller, LPCWSTR pszTOC, UINT flags, ICddbDisc **ppDisc); + +#define CLEARLOOKUP() SecureZeroMemory(&g_lookup, sizeof(MEDIALOOKUP)) +#define IS_BUSY(__timeout) (evntBusy && (WAIT_OBJECT_0 == WaitForSingleObject(evntBusy, (__timeout)))) +#define SET_BUSY(__enable) ((__enable) ? (SetEvent(evntBusy)) : ResetEvent(evntBusy)) + +void ShutDownCDDB() +{ + if (pCDDBControl) + { + eventMngr.Unadvise(pCDDBControl); + pCDDBControl->Release(); + pCDDBControl=0; + } +} +#endif + +static HWND GetAdaptedParent(HWND hwndParent) +{ + if (!hwndParent || !IsWindow(hwndParent)) hwndParent = line.hMainWindow; + if (!hwndParent || !IsWindow(hwndParent)) hwndParent = GetDesktopWindow(); + if (hwndParent == line.hMainWindow) + { + hwndParent = (HWND)SendMessageW(line.hMainWindow, WM_WA_IPC, 0, IPC_GETDIALOGBOXPARENT); + if (!IsWindow(hwndParent)) hwndParent = line.hMainWindow; + } + return hwndParent; +} + +static BOOL CalculatePopUpPos(RECT *prcParent, RECT *prcPopUp, UINT flags) +{ + LONG x, y; + MONITORINFO mi; + if (!prcPopUp) return FALSE; + + if (prcParent) + { + x = prcParent->left; + y = prcParent->top; + + OffsetRect(prcPopUp, -prcPopUp->left, -prcPopUp->top); + OffsetRect(prcParent, -prcParent->left, -prcParent->top); + if (CENTER_LEFT & flags) x += (prcParent->right - prcPopUp->right)/2; + if (CENTER_TOP & flags) y += (prcParent->bottom - prcPopUp->bottom )/2; + + SetRect(prcPopUp, x, y, x + prcPopUp->right, y + prcPopUp->bottom); + } + mi.cbSize = sizeof(MONITORINFO); + HMONITOR hMonitor = MonitorFromRect(prcPopUp, MONITOR_DEFAULTTONULL); + if (!hMonitor) + { + OffsetRect(prcPopUp, -prcPopUp->left, -prcPopUp->top); + hMonitor = MonitorFromRect(prcPopUp, MONITOR_DEFAULTTONEAREST); + + if(GetMonitorInfo(hMonitor, &mi)) + { + x = mi.rcWork.left + ((mi.rcWork.right - mi.rcWork.left) - prcPopUp->right)/2; + if(x < mi.rcWork.left) x = mi.rcWork.left; + y = mi.rcWork.top + ((mi.rcWork.bottom - mi.rcWork.top) - prcPopUp->bottom)/2; + if(y < mi.rcWork.top) y = mi.rcWork.top; + SetRect(prcPopUp, x, y, x + prcPopUp->right, y + prcPopUp->bottom); + } + } + else + { + if(GetMonitorInfo(hMonitor, &mi)) + { + if (prcPopUp->right > mi.rcWork.right) + { + OffsetRect(prcPopUp, mi.rcWork.right - prcPopUp->right, 0); + } + } + } + return TRUE; +} + +static BOOL SetPopUpPos(HWND hwnd, UINT flags) +{ + RECT rc, rw; + + if (!hwnd) return FALSE; + + HWND hwndParent = GetParent(hwnd); + hwndParent = GetAdaptedParent(hwndParent); + + if (GetClientRect(hwndParent, &rc) && GetWindowRect(hwnd, &rw)) + { + HWND hwndFound; + wchar_t szClass[2048] = {0}; + UINT flags = CENTER_LEFT; + + MapWindowPoints(hwndParent, HWND_DESKTOP, (POINT*)&rc, 2); + + if (hwndParent == line.hMainWindow || + (GetClassNameW(hwndParent, szClass, sizeof(szClass)/sizeof(wchar_t)) && + CSTR_EQUAL == CompareStringW(INVARIANT_LCID, 0, szClass, -1, WASABI_WND_CLASSW, -1) && + NULL != (hwndFound = FindWindowEx(NULL, hwndParent, WASABI_WND_CLASSW, NULL))) + && IsWindowVisible(hwndFound)) + { + OffsetRect(&rc, (rc.right - rc.left + 4), 0); + flags &= ~ CENTER_LEFT; + } + if (CalculatePopUpPos(&rc, &rw, flags)) + { + SetWindowPos(hwnd, HWND_TOP, rw.left, rw.top, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE); + } + } + return TRUE; +} + +static void Cddb_ProcessResult(MEDIALOOKUP *pLookup, ICddbDisc *pDisc, DWORD *pdwAutoCloseDelay) +{ + if (pLookup && pLookup->callback) pLookup->callback(pLookup->result, pDisc, pdwAutoCloseDelay, pLookup->user); +} + +static void CALLBACK Execute_ProcessResult(INVOKEDATA *pData) +{ + #ifndef IGNORE_API_GRACENOTE + INVOKEDATA data; + + if (!pData) return; + + CopyMemory(&data, &pData->lookup, sizeof(INVOKEDATA)); + if (data.pDisc) + data.pDisc->AddRef(); + + Cddb_ProcessResult(&data.lookup, data.pDisc, data.pdwAutoCloseDelay); + if (data.pDisc) + data.pDisc->Release(); + if (data.evntDone) + SetEvent(data.evntDone); + #endif +} + +static LRESULT CALLBACK HookMessageProc(int code, WPARAM wParam, LPARAM lParam) +{ + THREADDATA *pData; + + if (TLS_OUT_OF_INDEXES == tlsSlot) return 0; + pData = (THREADDATA*)TlsGetValue(tlsSlot); + if (!pData) return 0; + + if (code < 0) return CallNextHookEx(pData->hhook, code, wParam, lParam); + + if (NULL == ((MSG*)lParam)->hwnd) // thread message + { + switch(((MSG*)lParam)->message) + { + case TM_INVOKERESULTS: + Execute_ProcessResult((INVOKEDATA*)((MSG*)lParam)->lParam); + return TRUE; + case TM_UNINITTHREAD: + Cddb_UninitializeThread(); + return TRUE; + } + } + + return CallNextHookEx(pData->hhook, code, wParam, lParam); +} + +void Cddb_Initialize(void) +{ + InitializeCriticalSection(&lockThread); +} + +void Cddb_Uninitialize(void) +{ + DeleteCriticalSection(&lockThread); +} + +static HRESULT Cddb_IsThreadInitialized(void) +{ + THREADDATA *pData; + if (TLS_OUT_OF_INDEXES == tlsSlot) return E_OUTOFMEMORY; + pData = (THREADDATA*)TlsGetValue(tlsSlot); + return (pData) ? S_OK : S_FALSE; +} + +static HANDLE GetMainThreadHandle(void) +{ + HANDLE hThread = NULL; + api_application *pApp = NULL; + waServiceFactory *sf = NULL; + + if (!line.service) return NULL; + if (NULL == (sf = line.service->service_getServiceByGuid(applicationApiServiceGuid))) return NULL; + if (NULL == (pApp = (api_application*)sf->getInterface())) { return NULL; } + + hThread = pApp->main_getMainThreadHandle(); + sf->releaseInterface(pApp); + + return hThread; +} + +static HRESULT Cddb_InvokeProcessResult(MEDIALOOKUP *pLookup, ICddbDisc *pDisc, DWORD *pdwAutoCloseDelay) +{ + HRESULT hr = E_FAIL; + #ifndef IGNORE_API_GRACENOTE + INVOKEDATA data; + if (!pLookup) return E_INVALIDARG; + CopyMemory(&data.lookup, pLookup, sizeof(MEDIALOOKUP)); + data.pDisc = pDisc; + data.pdwAutoCloseDelay = pdwAutoCloseDelay; + if (NULL != pDisc) + pDisc->AddRef(); + + if (!data.lookup.threadId) data.lookup.threadId = GetWindowThreadProcessId(line.hMainWindow, NULL); + if (data.lookup.threadId != GetCurrentThreadId()) + { + data.evntDone = CreateEvent(NULL, FALSE, FALSE, NULL); + if (data.evntDone) + { + if (PostThreadMessage(data.lookup.threadId, TM_INVOKERESULTS, 0, (LPARAM)&data)) + { + MSG msg; + msg.message = NULL; + + for(;;) + { + DWORD status = MsgWaitForMultipleObjectsEx(1, &data.evntDone, + INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE); + + if (status == WAIT_OBJECT_0+1) + { + while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) + { + if (WM_QUIT == msg.message) + { + break; + } + if (0 == CallMsgFilter(&msg, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } + else if (status == WAIT_OBJECT_0) + { + break; + } + } + + if (WM_QUIT == msg.message) + PostQuitMessage((int)msg.wParam); + hr = S_OK; + } + CloseHandle(data.evntDone); + } + } + else + { + Cddb_ProcessResult(pLookup, pDisc, pdwAutoCloseDelay); + hr = S_OK; + } + + if (NULL != pDisc) + pDisc->Release(); + #endif + return hr; +} + +#ifndef IGNORE_API_GRACENOTE +bool GetRole(ICddbTrack *track, BSTR roleId, BSTR *str) +{ + if (!roleId || !*roleId) + return false; + + if (!track) + return false; + + ICddbCreditsPtr credits; + track->get_Credits(&credits); + if (credits) + { + long creditCount; + credits->get_Count(&creditCount); + for (long c = 0;c < creditCount;c++) + { + ICddbCreditPtr credit; + credits->GetCredit(c + 1, &credit); + if (credit) + { + BSTR thisRole; + credit->get_Id(&thisRole); + if (!wcscmp(thisRole, roleId)) + { + credit->get_Name(str); + return true; + } + } + } + } + return false; +} + +bool GetRole(ICddbDisc *track, BSTR roleId, BSTR *str) +{ + if (!roleId || !*roleId) + return false; + + if (!track) + return false; + + ICddbCreditsPtr credits; + track->get_Credits(&credits); + if (credits) + { + long creditCount; + credits->get_Count(&creditCount); + for (long c = 0;c < creditCount;c++) + { + ICddbCreditPtr credit; + credits->GetCredit(c + 1, &credit); + if (credit) + { + BSTR thisRole; + credit->get_Id(&thisRole); + if (!wcscmp(thisRole, roleId)) + { + credit->get_Name(str); + return true; + } + } + } + } + return false; +} +#endif + +HRESULT Cddb_InitializeThread(void) +{ + HRESULT hr = S_OK; + EnterCriticalSection(&lockThread); + + if (!evntBusy) + { + evntBusyRef = 0; + evntBusy = CreateEvent(NULL, TRUE, FALSE, NULL); + if (NULL == evntBusy) + hr = E_UNEXPECTED; + } + + if (SUCCEEDED(hr)) + { + evntBusyRef++; + + if (TLS_OUT_OF_INDEXES == tlsSlot && + TLS_OUT_OF_INDEXES == (tlsSlot = TlsAlloc())) + hr = E_OUTOFMEMORY; + + if (SUCCEEDED(hr)) + { + THREADDATA *pData = (THREADDATA*)TlsGetValue(tlsSlot); + if (pData) + { + pData->ref++; + } + else + { + pData = (THREADDATA*)calloc(1, sizeof(THREADDATA)); + if (!pData) + { + hr = E_OUTOFMEMORY; + } + if (SUCCEEDED(hr)) + { + pData->hhook = SetWindowsHookExW(WH_MSGFILTER, HookMessageProc, NULL, GetCurrentThreadId()); + if (!pData->hhook || !TlsSetValue(tlsSlot, pData)) + { + if (pData->hhook) UnhookWindowsHookEx(pData->hhook); + free(pData); + pData = NULL; + hr = E_FAIL; + } + else + { + pData->ref = 1; + pData->uninitCom = (S_OK == CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)); + hr = S_OK; + } + } + } + } + } + LeaveCriticalSection(&lockThread); + return hr; +} + +HRESULT Cddb_UninitializeThread(void) +{ + EnterCriticalSection(&lockThread); + + if (TLS_OUT_OF_INDEXES != tlsSlot) + { + THREADDATA *pData = (THREADDATA*)TlsGetValue(tlsSlot); + if (NULL != pData && + pData->ref && 0 == --pData->ref) + { + TlsSetValue(tlsSlot, NULL); + if (pData->hhook) + UnhookWindowsHookEx(pData->hhook); + + if (pData->uninitCom) + CoUninitialize(); + + free(pData); + } + } + + if (evntBusyRef && 0 == --evntBusyRef) + { + CloseHandle(evntBusy); + evntBusy = NULL; + } + + LeaveCriticalSection(&lockThread); + return S_OK; +} + +LPCWSTR Cddb_CalculateTOC(DINFO *pDisc, LPWSTR pszTOC, size_t cchTOC) +{ + LPWSTR p; + if (!pszTOC || !pDisc || !cchTOC) return NULL; + pszTOC[0] = 0x00; + p = pszTOC; + for (int x = 0; x < pDisc->ntracks + 1; x++) + { + if (S_OK != StringCchPrintfExW(p, cchTOC, &p, &cchTOC, + STRSAFE_NULL_ON_FAILURE, L"%lu ", pDisc->pnFrames[x])) return NULL; + } + if (p != pszTOC) *(p - 1) = 0x00; + return pszTOC; +} + +// 2 author (???) +// 3 composer +// 6 lyricist +// 7 publisher (how is this different from label?) +// 9 songwriter (music & lyrics) +// 10 conductor/arranger +// 11 Arranger +// 12 Conductor +// 13 Director +// 72 Engineer +// 74 Mastering +// 75 Mastering Location +// 76 Mixing +// 77 Mixing Location +// 78 Producer +// 79 Programming (???) +// 80 Recording Location +// 147 remixer +#ifndef IGNORE_API_GRACENOTE +void GetDiscInfo(ICddbDiscPtr pDisc, DINFO *ps) +{ + CComBSTR str, disc_artist, disc_composer, disc_conductor, disc_remixing; + BSTR composerRole=L"3", conductorRole=L"12", remixingRole=L"147"; + + /* + for (int i=0;i<100;i++) + { + wchar_t id[256] = {0}; + _itow(i, id, 10); + ICddbRolePtr role; + pCDDBControl->GetRoleInfo(id, &role); + if (role) + { + BSTR name, description; + role->get_Name(&name); + role->get_Description(&description); + wchar_t str[4096] = {0}; + wsprintf(str, L"ID: %s\r\nName: %s\r\nDescription: %s\r\n", id, name, description); + MessageBoxW(NULL, str, L"CDDB Role", MB_OK); + } + } + */ + + ps->Reset(); + if (pDisc == NULL) // yikes! + return; + ICddbDisc2Ptr pDisc2; + pDisc->QueryInterface(&pDisc2); + + ICddbDisc2_5Ptr pDisc2_5; + pDisc->QueryInterface(&pDisc2_5); + + if (GetRole(pDisc, conductorRole, &disc_conductor) && disc_conductor && disc_conductor.m_str[0]) + ps->conductor = ndestring_wcsdup(disc_conductor.m_str); + + if (GetRole(pDisc, composerRole, &disc_composer) && disc_composer && disc_composer.m_str[0]) + ps->composer = ndestring_wcsdup(disc_composer.m_str); + + if (GetRole(pDisc, remixingRole, &disc_remixing) && disc_remixing && disc_remixing.m_str[0]) + ps->remixing = ndestring_wcsdup(disc_remixing.m_str); + + if (SUCCEEDED(pDisc->get_Artist(&disc_artist)) && disc_artist && disc_artist.m_str[0]) + ps->artist = ndestring_wcsdup(disc_artist.m_str); + + if (SUCCEEDED(pDisc->get_Year(&str)) && str && str.m_str[0]) + ps->year = ndestring_wcsdup(str.m_str); + + if (pDisc2_5 == NULL + || (FAILED(pDisc2_5->get_V2GenreStringPrimaryByLevel(3, &str)) + && FAILED(pDisc2_5->get_V2GenreStringPrimaryByLevel(2, &str)) + && FAILED(pDisc2_5->get_V2GenreStringPrimaryByLevel(1, &str)) + && FAILED(pDisc2_5->get_V2GenreStringPrimary(&str))) + ) + { + pDisc->get_GenreId(&str); + ICddbGenre *poop = 0; + if (SUCCEEDED(pCDDBControl->GetGenreInfo(str, &poop)) && poop) + { + poop->get_Name(&str); + poop->Release(); + } + else + str.Empty(); + } + if (str && str.m_str[0]) + ps->genre = ndestring_wcsdup(str.m_str); + + if (SUCCEEDED(pDisc->get_Title(&str)) && str && str.m_str[0]) + ps->title = ndestring_wcsdup(str.m_str); + + if (SUCCEEDED(pDisc->get_TitleUId(&str)) && str && str.m_str[0]) + ps->tuid = ndestring_wcsdup(str.m_str); + + if (SUCCEEDED(pDisc->get_Label(&str)) && str && str.m_str[0]) + ps->label = ndestring_wcsdup(str.m_str); + + if (SUCCEEDED(pDisc->get_Notes(&str)) && str && str.m_str[0]) + ps->notes = ndestring_wcsdup(str.m_str); + + long val; + if (SUCCEEDED(pDisc->get_Compilation(&val))) + ps->compilation = !!val; + + if (SUCCEEDED(pDisc->get_TotalInSet(&str)) && str && str.m_str[0]) + ps->numdiscs = _wtoi(str.m_str); + + if (SUCCEEDED(pDisc->get_NumberInSet(&str)) && str && str.m_str[0]) + ps->discnum = _wtoi(str.m_str); + + for (int x = 0; x < ps->ntracks; x ++) + { + TRACKINFO &trackInfo = ps->tracks[x]; + ICddbTrack *t; + ICddbTrack2_5Ptr track2_5; + pDisc->GetTrack(x + 1, &t); + if (!t) break; + + t->QueryInterface(&track2_5); + + if (SUCCEEDED(t->get_Artist(&str)) && str && str.m_str[0] && (!disc_artist || !disc_artist.m_str[0] || wcscmp(str.m_str, disc_artist.m_str))) + trackInfo.artist = ndestring_wcsdup(str.m_str); + + if (SUCCEEDED(t->get_Title(&str)) && str && str.m_str[0]) + trackInfo.title = ndestring_wcsdup(str.m_str); + + if (SUCCEEDED(t->get_ISRC(&str)) && str && str.m_str[0]) + trackInfo.isrc = ndestring_wcsdup(str.m_str); + + if (SUCCEEDED(pCDDBControl->GetDiscTagId(pDisc, x + 1, &str)) && str && str.m_str[0]) + trackInfo.tagID = ndestring_wcsdup(str.m_str); + + if (GetRole(t, conductorRole, &str) && str && str.m_str[0] && (!disc_conductor || !disc_conductor.m_str[0] || wcscmp(str.m_str, disc_conductor.m_str))) + trackInfo.conductor = ndestring_wcsdup(str.m_str); + + if (GetRole(t, composerRole, &str) && str && str.m_str[0] && (!disc_composer || !disc_composer.m_str[0] || wcscmp(str.m_str, disc_composer.m_str))) + trackInfo.composer = ndestring_wcsdup(str.m_str); + + if (GetRole(t, remixingRole, &str) && str && str.m_str[0] && (!disc_remixing || !disc_remixing.m_str[0] || wcscmp(str.m_str, disc_remixing.m_str))) + trackInfo.remixing = ndestring_wcsdup(str.m_str); + + if (track2_5 != NULL && (SUCCEEDED(track2_5->get_ExtDataSerialized(&str)) && str && str.m_str[0]) // try track first + || (pDisc2_5 != NULL && SUCCEEDED(pDisc2_5->get_ExtDataSerialized(&str)) && str && str.m_str[0])) // then disc + trackInfo.extData = ndestring_wcsdup(str.m_str); + + t->Release(); + } + + ps->populated = true; +} +#endif + +HRESULT Cddb_GetIUIOptions(void **ppUIOptions) +{ + HRESULT hr; + + if (!ppUIOptions) return E_INVALIDARG; + *ppUIOptions = NULL; + + hr = SetupCDDB(FALSE); + if (SUCCEEDED(hr)) + { + #ifndef IGNORE_API_GRACENOTE + hr = CoCreateInstance(CLSID_CddbUIOptions, NULL, + CLSCTX_INPROC_SERVER, IID_ICddbUIOptions, ppUIOptions); + #endif + } + return hr; +} + +HRESULT Cddb_GetIControl(void **ppControl) +{ + HRESULT hr; + + if (!ppControl) return E_INVALIDARG; + *ppControl = NULL; + + hr = SetupCDDB(FALSE); + if(SUCCEEDED(hr)) + { + #ifndef IGNORE_API_GRACENOTE + pCDDBControl->AddRef(); + *ppControl = pCDDBControl; + #endif + } + return hr; +} + +HRESULT Cddb_GetICacheManger(void **ppCache) +{ + HRESULT hr; + + if (!ppCache) return E_INVALIDARG; + *ppCache = NULL; + + hr = SetupCDDB(FALSE); + if (SUCCEEDED(hr)) + { + #ifndef IGNORE_API_GRACENOTE + hr = CoCreateInstance(CLSID_CddbCacheManager, NULL, + CLSCTX_INPROC_SERVER, IID_ICddbCacheManager, ppCache); + #endif + } + return hr; +} + +static HRESULT SetupCDDB(BOOL resetError) +{ + static HRESULT result(S_FALSE); + + #ifndef IGNORE_API_GRACENOTE + if (S_FALSE == result || (FAILED(result) && resetError)) + { + if (AGAVE_API_GRACENOTE) + pCDDBControl = AGAVE_API_GRACENOTE->GetCDDB(); + result = (NULL == pCDDBControl) ? CDDBCTLNotInitialized : S_OK; + if (SUCCEEDED(result)) + { + HRESULT hr; + ICddbOptionsPtr pOptions; + hr = pCDDBControl->GetOptions(&pOptions); + if (SUCCEEDED(hr)) + { + LONG lVal; + BOOL bUpdate(FALSE); + hr = pOptions->get_AsyncCompletion(&lVal); + if (SUCCEEDED(hr) && !lVal) { pOptions->put_AsyncCompletion(1); bUpdate = TRUE; } + hr = pOptions->get_ProgressEvents(&lVal); + if (SUCCEEDED(hr) && !lVal) { pOptions->put_ProgressEvents(1); bUpdate = TRUE; } + +#if defined(BETA) || defined(INTERNAL) || defined(NIGHT) + pOptions->put_TestSubmitMode(TRUE); // BETA +#endif + + if (bUpdate) pCDDBControl->SetOptions(pOptions); + } + if (SUCCEEDED(eventMngr.Advise(pCDDBControl))) + { + eventMngr.RegisterCallback(CDDB_CB_CMDCOMPLETED, Cddb_OnCommandCompleted); + eventMngr.RegisterCallback(CDDB_CB_CMDPROGRESS, Cddb_OnCommandProgress); + } + + } + } + #endif + return result; +} + +#ifndef IGNORE_API_GRACENOTE +HRESULT Cddb_GetDiscFromCache(BSTR bstrTOC, ICddbDisc **ppDisc) +{ + HRESULT hr; + ICddbCacheManager *pCache; + + if (!ppDisc) return E_INVALIDARG; + *ppDisc = NULL; + + if (!bstrTOC) return CDDB_E_BADTOC; + + hr = Cddb_GetICacheManger((void**)&pCache); + if (SUCCEEDED(hr)) + { + hr = pCache->FetchDiscByToc(bstrTOC, ppDisc); + pCache->Release(); + } + + return hr; +} +#endif + +static LRESULT CALLBACK DisplayDiscInfoWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + LRESULT result; + HWND hwndListener; + WNDPROC fnOldProc = (WNDPROC)GetPropW(hwnd, L"WNDPROC"); + switch(uMsg) + { + case WM_INITDIALOG: + result = (IsWindowUnicode(hwnd)) ? CallWindowProcW(fnOldProc, hwnd, uMsg, wParam, lParam) : CallWindowProcA(fnOldProc, hwnd, uMsg, wParam, lParam); + hwndListener = (HWND)GetPropW(hwnd, L"LISTENER"); + if (hwndListener) CddbProgressDlg_SetStatus(hwndListener, MAKEINTRESOURCEW(IDS_OPENING), -1); + return result; + case WM_DESTROY: + { + RemovePropW(hwnd, L"WNDPROC"); + hwndListener = (HWND)GetPropW(hwnd, L"LISTENER"); + if (hwndListener) + { + RECT rc, rw; + if (GetWindowRect(hwndListener, &rc) && GetWindowRect(hwnd, &rw)) + { + CalculatePopUpPos(&rw, &rc, CENTER_LEFT); + SetWindowPos(hwndListener, NULL, rc.left, rc.top, 0, 0, + SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW); + } + } + HWND *list = (HWND*)GetPropW(hwnd, L"WNDLIST"); + RemovePropW(hwnd, L"WNDLIST"); + if (list) + { + for (HWND *p = list; *p != NULL; p++) ShowWindowAsync(*p, SW_SHOWNA); + free(list); + } + RemovePropW(hwnd, L"LISTENER"); + + if (fnOldProc) SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)fnOldProc); + } + break; + case WM_SHOWWINDOW: + if (wParam) + { + hwndListener = (HWND)GetPropW(hwnd, L"LISTENER"); + if(hwndListener) + { + HWND *list = (HWND*)calloc(24, sizeof(HWND)); + if (!FindAllOwnedWindows(hwndListener, list, 24, 0) || !SetPropW(hwnd, L"WNDLIST", list)) free(list); + else for (HWND *p = list; *p != NULL; p++) ShowWindowAsync(*p, SW_HIDE); + ShowWindowAsync(hwndListener, SW_HIDE); + } + } + break; + } + + if (!fnOldProc) + { + return DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + return (IsWindowUnicode(hwnd)) ? CallWindowProcW(fnOldProc, hwnd, uMsg, wParam, lParam) : CallWindowProcA(fnOldProc, hwnd, uMsg, wParam, lParam); +} + +static void CALLBACK OnGrabbed_DisplayDiscInfo(HWND hwnd, CREATESTRUCT *lpcs, HWND *phwndInsertAfter, ULONG_PTR user) +{ + WNDPROC oldProc = (WNDPROC)(LONG_PTR)SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)DisplayDiscInfoWndProc); + if (oldProc) + { + SetPropW(hwnd, L"WNDPROC", oldProc); + SetPropW(hwnd, L"LISTENER", (HANDLE)user); + } + if(user) + { + RECT rc, rw; + if (GetClientRect((HWND)user, &rc)) + { + MapWindowPoints((HWND)user, HWND_DESKTOP, (POINT*)&rc, 2); + SetRect(&rw, lpcs->x, lpcs->y, lpcs->x + lpcs->cx, lpcs->y + lpcs->cy); + CalculatePopUpPos(&rc, &rw, CENTER_LEFT); + lpcs->x = rw.left; + lpcs->y = rw.top; + } + } + else ShowWindow((HWND)user, SW_HIDE); +} + +void Cddb_GetResultText(HRESULT hr, LPWSTR pszResult, INT cchResult, LPWSTR pszReason, INT cchReason) +{ + INT nResult, nReason; + + nResult = (S_OK == hr) ? IDS_SUCCESS : IDS_NOT_FOUND; + nReason = 0; + + if (FAILED(hr)) + { + switch(hr) + { + #ifndef IGNORE_API_GRACENOTE + case CDDBCTLNotInitialized: nReason = IDS_CDDB_NOT_INSTALLED; break; + case CDDBCTLBusy: nReason = IDS_CDDB_E_BUSY; break; + case CDDB_E_BADTOC: nReason = IDS_CDDB_E_BADTOC; break; + case E_ABORT: + case CDDBTRNCancelled: + nReason = IDS_CDDB_E_ABORT; break; + #endif + default: nReason = IDS_CDDB_E_FAIL; break; + } + } + + if (pszReason && cchReason) + { + if(nReason) + { INT len; + WASABI_API_LNGSTRINGW_BUF(IDS_REASON, pszReason, cchReason); + len = lstrlenW(pszReason); + cchReason -= len; + WASABI_API_LNGSTRINGW_BUF(nReason, pszReason + len, cchReason); + } + else pszReason[0] = 0x00; + } + + if (pszResult && cchResult) + { + WASABI_API_LNGSTRINGW_BUF(nResult, pszResult, cchResult); + } +} + +static HRESULT Cddb_FinishLookup(MEDIALOOKUP *pLookup, ICddbDisc *pDisc) +{ + HRESULT hr = E_FAIL; + #ifndef IGNORE_API_GRACENOTE + MEDIALOOKUP lookup_cpy; + DWORD delay = AUTOCLOSE_NOW; + + if (!pLookup) hr = E_INVALIDARG; + if (!evntBusy) hr = E_FAIL; + else hr = S_OK; + + if (NULL != pDisc) + pDisc->AddRef(); + + if (NULL != pLookup) + { + CopyMemory(&lookup_cpy, pLookup, sizeof(MEDIALOOKUP)); + SecureZeroMemory(pLookup, sizeof(MEDIALOOKUP)); + } + else + SecureZeroMemory(&lookup_cpy, sizeof(MEDIALOOKUP)); + + if (SUCCEEDED(hr)) + { + HRESULT hrInvoke; + + if(S_OK != lookup_cpy.result) + { + if (NULL != pDisc) + pDisc->Release(); + + pDisc = NULL; + } + + if (lookup_cpy.hwndInfo) + { + CddbProgressDlg_SetExtendedMode(lookup_cpy.hwndInfo, FALSE); + CddbProgressDlg_EnableAbortButton(lookup_cpy.hwndInfo, FALSE); + CddbProgressDlg_SetStatus(lookup_cpy.hwndInfo, MAKEINTRESOURCEW(IDS_PROCESSING), -1); + } + + if (FAILED(lookup_cpy.result)) + delay = 5000; + + hrInvoke = Cddb_InvokeProcessResult(&lookup_cpy, pDisc, &delay); + if (FAILED(hrInvoke) && S_OK == lookup_cpy.result) + lookup_cpy.result = hrInvoke; + } + + SET_BUSY(FALSE); + + if (SUCCEEDED(hr)) + { + if (lookup_cpy.hwndInfo) + { + RECT rc; + if (GetWindowRect(lookup_cpy.hwndInfo, &rc)) + { + g_lastcddbpos.x = rc.left; + g_lastcddbpos.y = rc.top; + } + + lookup_cpy.result = Cddb_DisplayResultDlg(lookup_cpy.hwndInfo, lookup_cpy.result, delay, + CDDB_UI_USE_PARENT | lookup_cpy.flags); + if (delay > 10 && 0 == (CDDB_UI_RESULT_MODAL & lookup_cpy.flags) || FAILED(lookup_cpy.result)) + { + CddbProgressDlg_ExitModal(lookup_cpy.hwndInfo, lookup_cpy.result, FALSE); + } + } + hr = lookup_cpy.result; + } + + if (NULL != lookup_cpy.bstrTOC) + SysFreeString(lookup_cpy.bstrTOC); + + if (lookup_cpy.threadId == GetCurrentThreadId()) + Cddb_UninitializeThread(); + else + { + if (PostThreadMessage(lookup_cpy.threadId, TM_UNINITTHREAD, 0, 0L)) + { + /* MSG msg; + DWORD status; + msg.message = NULL; + for(;;) + { + status = MsgWaitForMultipleObjectsEx(0, NULL, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE); + if (status == WAIT_OBJECT_0+1) + { + while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) + { + if (WM_QUIT == msg.message) break; + if (!CallMsgFilter(&msg, 0)) DispatchMessage(&msg); + } + } + else if (status == WAIT_OBJECT_0) + { + break; + } + } + if (WM_QUIT == msg.message) PostQuitMessage((int)msg.wParam);*/ + } + } + + if (NULL != pDisc) + pDisc->Release(); + #endif + return hr; +} + +static void CALLBACK CddbProgressDlg_OnAbort(HWND hwndDlg, BSTR bstrUser) +{ + #ifndef IGNORE_API_GRACENOTE + ICDDBControl *pControl; + HRESULT hr = Cddb_GetIControl((void**)&pControl); + if (SUCCEEDED(hr)) + { + LONG lVal; + pControl->Cancel(&lVal); + CddbProgressDlg_EnableAbortButton(hwndDlg, FALSE); + pControl->Release(); + } + + //CddbProgressDlg_Completed(hwndDlg, L"Aborted !!!", 4600); + #endif +} + +typedef struct _MODALDATA +{ + ICddbDisc *pDisc; + UINT flags; + BSTR bstrTOC; + +} MODALDATA; + +#ifndef IGNORE_API_GRACENOTE +HRESULT Cddb_DisplayDiscInfo(ICddbDisc *pDisc, CDDBUIFlags *pUIFlags, HWND hwndParent) +{ + HRESULT hr; + ICDDBControl *pControl; + BOOL bManual = FALSE; + hr = Cddb_GetIControl((void**)&pControl); + if (FAILED(hr)) return hr; + + if (hwndParent) + { + if (!BeginGrabCreateWindow(L"#32770", NULL, NULL, OnGrabbed_DisplayDiscInfo, (ULONG_PTR)hwndParent)) + { + ShowWindow(hwndParent, SW_HIDE); + bManual = TRUE; + } + } + + hr = pControl->DisplayDiscInfo(pDisc, *pUIFlags, pUIFlags); + if (hwndParent) + { + EndGrabCreateWindow(); + if (bManual) ShowWindow(hwndParent, SW_SHOW); + } + + return hr; +} + +static void CALLBACK CddbProgressDlg_OnSubmitNew(HWND hwndDlg, BSTR bstrUser) +{ + HRESULT hr; + ICDDBControl *pControl; + CDDBUIFlags uiflags; + MODALDATA *pData; + wchar_t szText[256] = {0}; + + pData = (MODALDATA*)CddbProgressDlg_GetUserData(hwndDlg); + if (!pData) + { + CddbProgressDlg_ExitModal(hwndDlg, E_INVALIDARG, TRUE); + return; + } + + CddbProgressDlg_ShowButton1(hwndDlg, NULL, NULL, NULL); + + SetWindowText(hwndDlg, WASABI_API_LNGSTRINGW_BUF(IDS_SUBMITDISC_TITLE, szText, sizeof(szText)/sizeof(wchar_t))); + CddbProgressDlg_Initialize(hwndDlg, MAKEINTRESOURCEW(IDS_QUERYING), CddbProgressDlg_OnAbort, NULL); + CddbProgressDlg_SetStatus(hwndDlg, MAKEINTRESOURCEW(IDS_INITIALIZING), -1); + + hr = Cddb_GetIControl((void**)&pControl); + if (SUCCEEDED(hr)) + { + hr = pControl->GetSubmitDisc(pData->bstrTOC, NULL, NULL, &pData->pDisc); + pControl->Release(); + } + + if (!pData->pDisc) + { + CddbProgressDlg_ExitModal(hwndDlg, hr, TRUE); + return; + } + + uiflags = (CDDBUIFlags)(UI_SUBMITNEW | UI_EDITMODE); + hr = Cddb_DisplayDiscInfo(pData->pDisc, &uiflags, hwndDlg); + + if (SUCCEEDED(hr) && (0 == ((uiflags & (UI_OK | UI_DATA_CHANGED)) == (UI_OK | UI_DATA_CHANGED)))) + hr = S_FALSE; + + if (S_OK != hr) + { + pData->pDisc->Release(); + pData->pDisc = NULL; + } + CddbProgressDlg_ExitModal(hwndDlg, hr, TRUE); + return; +} + +static void CALLBACK CddbProgressDlg_OnAcceptMatch(HWND hwndDlg, BSTR bstrUser) +{ + MODALDATA *pData; + pData = (MODALDATA*)CddbProgressDlg_GetUserData(hwndDlg); + if (!pData) + { + CddbProgressDlg_ExitModal(hwndDlg, E_INVALIDARG, TRUE); + return; + } + + pData->pDisc = (ICddbDisc*)(LONG_PTR)(LONG)(CddbProgressDlg_GetSelRecordIndex(hwndDlg) + 1); + CddbProgressDlg_ExitModal(hwndDlg, S_OK, TRUE); + return; +} +#endif + +HRESULT Cddb_DoLookup(LPCWSTR pszTOC, HWND hwndParent, CDDB_CB callback, UINT flags, ULONG_PTR user) +{ + #ifndef IGNORE_API_GRACENOTE + HRESULT hr; + + ICDDBControl *pControl; + CDDBMatchCode matchCode; + + if (!callback) return E_INVALIDARG; + + if (IS_BUSY(0)) + { + return (CSTR_EQUAL == CompareStringW(INVARIANT_LCID, 0, g_lookup.bstrTOC, -1, pszTOC, -1)) ? E_PENDING : CDDBCTLBusy; + } + + hr = Cddb_InitializeThread(); + if (FAILED(hr)) return hr; + + SET_BUSY(TRUE); + CLEARLOOKUP(); + + g_lookup.callback = callback; + g_lookup.user = user; + g_lookup.flags = flags; + g_lookup.threadId = GetCurrentThreadId(); + g_lookup.bstrTOC = SysAllocString(pszTOC); + eventMngr.SetUserParam((ULONG_PTR)&g_lookup); + + if (0 == (CDDB_NOCACHE & flags)) + { + ICddbDisc *pDisc; + g_lookup.result = Cddb_GetDiscFromCache(g_lookup.bstrTOC, &pDisc); + if (CDDBTRNDataStoreNotCached != g_lookup.result) + { + Cddb_FinishLookup(&g_lookup, pDisc); + if (pDisc) pDisc->Release(); + return S_OK; + } + } + + if (CDDB_NOINET == (CDDB_NOINET & flags)) + { + g_lookup.result = S_FALSE; + Cddb_FinishLookup(&g_lookup, NULL); + return S_OK; + } + + hwndParent = GetAdaptedParent(hwndParent); + + g_lookup.hwndInfo = CddbProgressDlg_Create(hwndParent, SW_HIDE); + hwndProgressListener = g_lookup.hwndInfo; + + CddbProgressDlg_Initialize(g_lookup.hwndInfo, MAKEINTRESOURCEW(IDS_QUERYING), CddbProgressDlg_OnAbort, NULL); + CddbProgressDlg_SetStatus(g_lookup.hwndInfo, MAKEINTRESOURCEW(IDS_INITIALIZING), -1); + + if (g_lookup.hwndInfo) + { + SetPopUpPos(g_lookup.hwndInfo, CENTER_LEFT); + ShowWindow(g_lookup.hwndInfo, SW_SHOWNA); + } + + g_lookup.result = Cddb_GetIControl((void**)&pControl); + if (FAILED(g_lookup.result)) + { + Cddb_FinishLookup(&g_lookup, NULL); + return S_OK; + } + + g_lookup.result = pControl->LookupMediaByToc(g_lookup.bstrTOC, TRUE, &matchCode); + + pControl->Release(); + if (FAILED(g_lookup.result)) + { + Cddb_FinishLookup(&g_lookup, NULL); + return S_OK; + } + + if (CDDB_UI_MODAL & flags) + { + RECT rc; + + hr = CddbProgressDlg_DoModal(g_lookup.hwndInfo, &rc); + if (SUCCEEDED(hr)) + { + SET_BUSY(FALSE); + CLEARLOOKUP(); + + g_lastcddbpos.x = rc.left; + g_lastcddbpos.y = rc.top; + } + } + return S_OK; + #else + return E_FAIL; + #endif +} + +static HRESULT Cddb_DoSubmitNewDlg(HWND hwndCaller, BSTR bstrTOC, UINT flags, ICddbDisc **ppDisc) +{ + RECT rw; + HRESULT hr; + HWND hwndInfo, hwndOldListener, hwndParent; + MODALDATA data; + wchar_t szText[256] = {0}; + + if (!ppDisc) return E_INVALIDARG; + + data.pDisc = NULL; + data.bstrTOC = bstrTOC; + data.flags = flags; + + hwndParent = (hwndCaller) ? GetParent(hwndCaller) : NULL; + if (!hwndParent) hwndParent = line.hMainWindow; + hwndInfo = CddbProgressDlg_Create(hwndParent, SW_HIDE); + + if (!hwndInfo || !CddbProgressDlg_SetUserData(hwndInfo, &data)) + { + if (hwndInfo) + DestroyWindow(hwndInfo); + + if (NULL != data.bstrTOC) + SysFreeString(data.bstrTOC); + return E_FAIL; + } + + SetWindowText(hwndInfo, WASABI_API_LNGSTRINGW_BUF(IDS_LOOKUPRESULT_TITLE, szText, sizeof(szText)/sizeof(wchar_t))); + CddbProgressDlg_Initialize(hwndInfo, MAKEINTRESOURCEW(IDS_NOT_FOUND), NULL, NULL); + CddbProgressDlg_Completed(hwndInfo, MAKEINTRESOURCEW(IDS_SUBMIT_OFFER), NULL, AUTOCLOSE_NEVER, S_OK); + #ifndef IGNORE_API_GRACENOTE + CddbProgressDlg_ShowButton1(hwndInfo, MAKEINTRESOURCEW(IDS_SUBMITNEW), CddbProgressDlg_OnSubmitNew, NULL); + #endif + + hwndOldListener = hwndProgressListener; + if (hwndCaller) + { + GetWindowRect(hwndCaller, &rw); + SetWindowPos(hwndInfo, HWND_TOP, rw.left, rw.top, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW); + ShowWindowAsync(hwndCaller, SW_HIDE); + } + else ShowWindow(hwndInfo, SW_SHOW); + + RECT rc; + hr = CddbProgressDlg_DoModal(hwndInfo, &rc); + + *ppDisc = data.pDisc; + + if (hwndCaller) + { + SetWindowPos(hwndCaller, HWND_TOP, rc.left, rc.top, 0, 0, SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOSIZE | SWP_SHOWWINDOW); + } + + hwndProgressListener = hwndOldListener; + return hr; +} + +#ifndef IGNORE_API_GRACENOTE +static HRESULT Cddb_DoFuzzyMatchDlg(HWND hwndCaller, UINT flags, ICddbDiscs *pDiscs, LONG *plVal) +{ + HRESULT hr; + LONG count; + ICddbDisc *pDisc(NULL); + BSTR bstrArtist, bstrTitle; + RECT rw; + HWND hwndInfo, hwndOldListener, hwndParent; + MODALDATA data; + wchar_t szText[256] = {0}; + + data.pDisc = NULL; + data.flags = flags; + + if (!plVal || !pDiscs) return E_INVALIDARG; + *plVal = 0L; + + hwndParent = (hwndCaller) ? GetParent(hwndCaller) : NULL; + if (!hwndParent) hwndParent = line.hMainWindow; + hwndInfo = CddbProgressDlg_Create(hwndParent, SW_HIDE); + + if (!hwndInfo || !CddbProgressDlg_SetUserData(hwndInfo, &data)) + { + if (hwndInfo) DestroyWindow(hwndInfo); + return E_FAIL; + } + + hwndOldListener = hwndProgressListener; + + SetWindowText(hwndInfo, WASABI_API_LNGSTRINGW_BUF(IDS_LOOKUPRESULT_TITLE, szText, sizeof(szText)/sizeof(wchar_t))); + CddbProgressDlg_Initialize(hwndInfo, MAKEINTRESOURCEW(IDS_FUZZYDISC_TITLE), NULL, NULL); + CddbProgressDlg_SetStatus(hwndInfo, MAKEINTRESOURCEW(IDS_INITIALIZING), -1); + + if (hwndCaller) + { + GetWindowRect(hwndCaller, &rw); + SetWindowPos(hwndInfo, HWND_TOP, rw.left, rw.top, 0, 0, SWP_NOSIZE); + ShowWindowAsync(hwndCaller, SW_HIDE); + } + ShowWindow(hwndInfo, SW_SHOWNA); + + if (FAILED(pDiscs->get_Count(&count))) count = 0; + for(long i = 1; i <= count; i++) + { + if (SUCCEEDED(pDiscs->GetDisc(i, &pDisc))) + { + if (FAILED(pDisc->get_Artist(&bstrArtist))) bstrArtist = NULL; + if (FAILED(pDisc->get_Title(&bstrTitle))) bstrTitle = NULL; + CddbProgressDlg_AddRecord(hwndInfo, bstrArtist, bstrTitle, NULL); + if (bstrArtist) SysFreeString(bstrArtist); + if (bstrTitle) SysFreeString(bstrTitle); + pDisc->Release(); + } + } + + CddbProgressDlg_ShowButton1(hwndInfo, MAKEINTRESOURCEW(IDS_ACCEPT), CddbProgressDlg_OnAcceptMatch, NULL); + CddbProgressDlg_SetExtendedMode(hwndInfo, TRUE); + CddbProgressDlg_Completed(hwndInfo, MAKEINTRESOURCEW(IDS_FOUND_MULTIPLE), NULL, AUTOCLOSE_NEVER, S_OK); + + UpdateWindow(hwndInfo); + SetForegroundWindow(hwndInfo); + + RECT rc; + hr = CddbProgressDlg_DoModal(hwndInfo, &rc); + g_lastcddbpos.x = rc.left; + g_lastcddbpos.y = rc.top; + *plVal = ((LONG)(LONG_PTR)data.pDisc); + + if (hwndCaller) + { + SetWindowPos(hwndCaller, HWND_TOP, rc.left, rc.top, 0, 0, SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOSIZE | SWP_SHOWWINDOW); + } + hwndProgressListener = hwndOldListener; + return hr; +} +#endif + +static void CALLBACK Cddb_OnCommandCompleted(LONG lCommandCode, HRESULT hCommandResult, VARIANT *pCommandData, UINT_PTR user) +{ + #ifndef IGNORE_API_GRACENOTE + switch(lCommandCode) + { + case CMD_LookupMediaByToc: Cddb_OnMediaLookupCompleted(hCommandResult, (CDDBMatchCode)pCommandData->lVal, (MEDIALOOKUP*)user); break; + case CMD_GetFullDiscInfo: Cddb_OnGetFullDiscInfoCompleted(hCommandResult, (ICddbDisc*)pCommandData->byref, (MEDIALOOKUP*)user); break; + case CMD_SubmitDisc: Cddb_OnSubmitDiscCompleted(hCommandResult, (MEDIALOOKUP*)user); break; + } + #endif +} + +static void CALLBACK Cddb_OnCommandProgress(LONG lCommandCode, LONG lProgressCode, LONG lBytesDone, LONG lBytesTotal, UINT_PTR user) +{ + #ifndef IGNORE_API_GRACENOTE + if (hwndProgressListener) + { + INT ids(0), nComplete(-1); + switch(lProgressCode) + { + case CMD_CONNECTING: ids = IDS_CDDB_PROGRESS_CONNECTING; break; + case CMD_SENDING: ids = IDS_CDDB_PROGRESS_SENDING; nComplete = lBytesTotal?((100 * lBytesDone)/lBytesTotal):100; break; + case CMD_RECEIVING: ids = IDS_CDDB_PROGRESS_RECEIVING; nComplete = lBytesTotal?((100 * lBytesDone)/lBytesTotal):100; break; + case CMD_CANCELLED: ids = IDS_CDDB_PROGRESS_CANCELLED; break; + case CMD_WAITING: ids = IDS_CDDB_PROGRESS_WAITING; lBytesTotal?((100 * lBytesDone)/lBytesTotal):100; break; + case CMD_COMPLETED: ids = IDS_CDDB_PROGRESS_COMPLETED; break; + } + if (ids) CddbProgressDlg_SetStatus(hwndProgressListener, MAKEINTRESOURCEW(ids), nComplete); + } + #endif +} + +#ifndef IGNORE_API_GRACENOTE +static void Cddb_OnMediaLookupCompleted(HRESULT result, CDDBMatchCode matchCode, MEDIALOOKUP *pLookup) +{ + ICDDBControl *pControl; + ICddbDisc *pDisc(NULL), *pfuzzyDisc(NULL); + ICddbDiscs *pDiscs(NULL); + LONG lVal; + wchar_t szText[256] = {0}; + if (!pLookup) return; + + SetWindowText(pLookup->hwndInfo, WASABI_API_LNGSTRINGW_BUF(IDS_LOOKUPRESULT_TITLE, szText, sizeof(szText)/sizeof(wchar_t))); + pLookup->result = result; + if (FAILED(pLookup->result)) + { + switch(pLookup->result) + { + case CDDBSVCServiceError: pLookup->result = CDDB_E_BADTOC; break;// + } + Cddb_FinishLookup(pLookup, NULL); + return; + } + + pLookup->result = Cddb_GetIControl((void**)&pControl); + if (FAILED(pLookup->result)) + { + Cddb_FinishLookup(pLookup, NULL); + return; + } + + switch(matchCode) + { + case MATCH_EXACT: + CddbProgressDlg_SetStatus(pLookup->hwndInfo, MAKEINTRESOURCEW(IDS_FOUND_EXACT), -1); + pLookup->result = pControl->GetMatchedDiscInfo((IUnknown**)&pDisc); + break; + case MATCH_MULTIPLE: + pLookup->result = S_MULTIPLE; + CddbProgressDlg_SetStatus(pLookup->hwndInfo, MAKEINTRESOURCEW(IDS_FOUND_MULTIPLE), -1); + if (0 != ((CDDB_UI_MULTIPLE | CDDB_RESOLVE_MULTIPLE) & pLookup->flags)) + { + lVal = 0; + pLookup->result = pControl->GetMatchedDiscInfo((IUnknown**)&pDiscs); + if (SUCCEEDED(pLookup->result)) + { + if (0 != (CDDB_UI_MULTIPLE & pLookup->flags)) + { + pLookup->result = Cddb_DoFuzzyMatchDlg(pLookup->hwndInfo, 0, pDiscs, &lVal); + if (S_OK != pLookup->result) lVal = 0; + } + if (0 == lVal && 0 != (CDDB_RESOLVE_MULTIPLE & pLookup->flags)) lVal = 1; + if (lVal) + { + CddbProgressDlg_Initialize(pLookup->hwndInfo, MAKEINTRESOURCEW(IDS_QUERYING), CddbProgressDlg_OnAbort, NULL); + CddbProgressDlg_SetStatus(pLookup->hwndInfo, MAKEINTRESOURCEW(IDS_INITIALIZING), -1); + pLookup->result = pDiscs->GetDisc(lVal, &pfuzzyDisc); + if (SUCCEEDED(pLookup->result)) + { + if (!pfuzzyDisc) pLookup->result = S_FALSE; + else + { + pLookup->result = pfuzzyDisc->IsSubmit(&lVal); + if (FAILED(pLookup->result) || !lVal) + { + pLookup->result = pControl->GetFullDiscInfo(pfuzzyDisc, TRUE, &pDisc); + pfuzzyDisc->Release(); + if (SUCCEEDED(pLookup->result)) + { + pDiscs->Release(); + pControl->Release(); + return; + } + } + else pDisc = pfuzzyDisc; + } + } + } + pDiscs->Release(); + } + } + break; + case MATCH_NONE: + pLookup->result = S_FALSE; + CddbProgressDlg_SetStatus(pLookup->hwndInfo, MAKEINTRESOURCEW(IDS_NOT_FOUND), -1); + if (CDDB_UI_NOMATCH & pLookup->flags) + { + pLookup->result = Cddb_DoSubmitNewDlg(pLookup->hwndInfo, pLookup->bstrTOC, 0, &pDisc); + if (S_OK == pLookup->result) + { + LONG lVal; + CddbProgressDlg_Initialize(pLookup->hwndInfo, MAKEINTRESOURCEW(IDS_SUBMITTING), CddbProgressDlg_OnAbort, NULL); + CddbProgressDlg_SetStatus(pLookup->hwndInfo, MAKEINTRESOURCEW(IDS_INITIALIZING), -1); + + pSubmitDisc = pDisc; + pLookup->result = pControl->SubmitDisc(pSubmitDisc, TRUE, &lVal); + if (SUCCEEDED(pLookup->result)) + { + pControl->Release(); + return; + } + + } + } + break; + } + pControl->Release(); + + Cddb_FinishLookup(pLookup, pDisc); + if (pDisc) pDisc->Release(); +} +#endif + +static void Cddb_OnGetFullDiscInfoCompleted(HRESULT result, ICddbDisc *pDisc, MEDIALOOKUP *pLookup) +{ + if (!pLookup) return; + pLookup->result = result; + Cddb_FinishLookup(pLookup, pDisc); +} + +static void Cddb_OnSubmitDiscCompleted(HRESULT result, MEDIALOOKUP *pLookup) +{ + #ifndef IGNORE_API_GRACENOTE + if (!pLookup) return; + pLookup->result = result; + if (FAILED(result)) + { + if (pSubmitDisc) pSubmitDisc->Release(); + pSubmitDisc = NULL; + } + Cddb_FinishLookup(pLookup, pSubmitDisc); + if (pSubmitDisc) + { + pSubmitDisc->Release(); + pSubmitDisc = NULL; + } + #endif +} + +HWND Cddb_GetInfoWindow(void) +{ + #ifndef IGNORE_API_GRACENOTE + return (IS_BUSY(0)) ? g_lookup.hwndInfo : NULL; + #else + return NULL; + #endif +} + +void Cdbb_DisplayInfo(LPCWSTR pszTitle, LPCWSTR pszCaption, LPCWSTR pszStatus, INT percentCompleted) +{ + HWND hwndInfo; + hwndInfo = Cddb_GetInfoWindow(); + if (!hwndInfo) return; + BOOL CddbProgressDlg_SetStatus(HWND hwnd, LPCWSTR pszStatus, INT nPercentCompleted); +} + +HRESULT Cddb_DisplayResultDlg(HWND hwndParent, HRESULT result, DWORD dwAutoCloseDelay, UINT flags) +{ + #ifndef IGNORE_API_GRACENOTE + HWND hwndInfo; + wchar_t szReason[256] = {0}, szResult[256] = {0}; + + if (CDDB_UI_USE_PARENT & flags) hwndInfo = hwndParent; + else + { + wchar_t szText[256] = {0}; + hwndParent = GetAdaptedParent(hwndParent); + hwndInfo = CddbProgressDlg_Create(hwndParent, SW_HIDE); + SetPopUpPos(hwndInfo, CENTER_LEFT); + + SetWindowText(hwndInfo, WASABI_API_LNGSTRINGW_BUF(IDS_LOOKUPRESULT_TITLE, szText, sizeof(szText)/sizeof(wchar_t))); + CddbProgressDlg_Initialize(hwndInfo, NULL, NULL, NULL); + CddbProgressDlg_Completed(hwndInfo, NULL, NULL, AUTOCLOSE_NEVER, S_OK); + } + if (!hwndInfo || !IsWindow(hwndInfo)) return E_FAIL; + + if (AUTOCLOSE_NOW == dwAutoCloseDelay && GetWindowThreadProcessId(hwndInfo, NULL) != GetCurrentThreadId()) + { + dwAutoCloseDelay = 1; // DestroyWindow is not working on other thread + ShowWindowAsync(hwndInfo, SW_HIDE); // in case we in modal loop on other + } + + if (dwAutoCloseDelay > 10) + { + Cddb_GetResultText(result, szResult, sizeof(szResult)/sizeof(wchar_t), + szReason, sizeof(szReason)/sizeof(wchar_t)); + ShowWindow(hwndInfo, SW_SHOW); + } + + CddbProgressDlg_Completed(hwndInfo, szResult, szReason, dwAutoCloseDelay, result); + + if (dwAutoCloseDelay > 10 && CDDB_UI_RESULT_MODAL == ((CDDB_UI_MODAL | CDDB_UI_RESULT_MODAL) & flags)) + { + CddbProgressDlg_DoModal(hwndInfo, NULL); + } + #endif + + return S_OK; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/CDDB.H b/Src/Plugins/Input/in_cdda/CDDB.H new file mode 100644 index 00000000..8685078d --- /dev/null +++ b/Src/Plugins/Input/in_cdda/CDDB.H @@ -0,0 +1,184 @@ +#ifndef NULLSOFT_IN_CDDA_CDDB_H +#define NULLSOFT_IN_CDDA_CDDB_H + +#define S_MULTIPLE MATCH_MULTIPLE + +#ifdef IGNORE_API_GRACENOTE +struct TRACKINFO +{ + TRACKINFO() + { + artist=0; + title=0; + genre=0; + tagID=0; + composer=0; + conductor=0; + extData=0; + remixing=0; + isrc=0; + } + TRACKINFO(const TRACKINFO ©); + TRACKINFO &operator =(const TRACKINFO ©); + ~TRACKINFO(); + + void Reset(); + + wchar_t *artist; + wchar_t *title; + wchar_t *genre; + wchar_t *tagID; + wchar_t *composer; + wchar_t *conductor; + wchar_t *extData; + wchar_t *remixing; + wchar_t *isrc; +}; +#endif + + struct DINFO +{ + DINFO() + { + label=0; + notes=0; + genre=0; + artist=0; + year=0; + tuid=0; + title=0; + composer=0; + conductor=0; + remixing=0; + compilation=false; + discnum=0; + numdiscs=0; + ntracks=0; + CDDBID=0; + nDiscLength=0; + populated=false; + memset(pnFrames, 0, sizeof(pnFrames)); + } + DINFO(const DINFO ©); + DINFO &operator =(const DINFO ©); + ~DINFO(); + void Reset(); + + wchar_t *title; + wchar_t *artist; + wchar_t *tuid; + wchar_t *year; + wchar_t *genre; + wchar_t *label; + wchar_t *notes; + bool compilation; + int discnum; + int numdiscs; + int ntracks; + wchar_t *conductor; + wchar_t *composer; + wchar_t *remixing; + #ifdef IGNORE_API_GRACENOTE + TRACKINFO tracks[100]; + #endif + + unsigned int CDDBID; + unsigned int pnFrames[100]; + unsigned int nDiscLength; + + bool populated; +}; + +extern char config_use_cddb; + + +#define CDDB_E_BADTOC 0x82FD0001 + +#define CDDB_NONE 0x0000 // +#define CDDB_NOCACHE 0x1000 // +#define CDDB_NOINET 0x2000 // + +#define CDDB_RESOLVE_MULTIPLE 0x0001 // selects first + + + +#define CDDB_UI_MODAL 0x0010 // +#define CDDB_UI_RESULT_MODAL 0x0020 // result window will stay modal +#define CDDB_UI_USE_PARENT 0x0040 // instead of creating child window from hwndParent - just reuse hwndParent + +#define CDDB_UI_NOMATCH 0x0100 // displays submit new dialog +#define CDDB_UI_MULTIPLE 0x0200 // displays multiple choices dialog + +#define AUTOCLOSE_NOW 0x00000000 +#define AUTOCLOSE_NEVER 0xFFFFFFFF + +int GetDiscID(MCIDEVICEID wDeviceID, DINFO* psDI); +int GetCDDBInfo(DINFO *ps, wchar_t device); + +#include "CDDBInterface.h" +#ifdef IGNORE_API_GRACENOTE +class ICddbDisc; +#endif +typedef HRESULT (CALLBACK *CDDB_CB)(HRESULT /*result*/, ICddbDisc* /*pDisc*/, DWORD* /*pdwAutoCloseDelay*/, ULONG_PTR /*user*/); + +#ifndef IGNORE_API_GRACENOTE +HRESULT DoCDDBDlg(DINFO *ps, HWND hwnd, int editopt); +void GetDiscInfo(ICddbDiscPtr pDisc, DINFO *ps); +bool GetRole(ICddbTrack *track, BSTR roleId, BSTR *str); +bool GetRole(ICddbDisc *track, BSTR roleId, BSTR *str); +extern ICDDBControl *pCDDBControl; + +void InitializeCddbCache(void); +void UninitializeCddbCache(void); +#endif + +HRESULT CddbCache_SetDisc(DINFO *pDiscInfo, HRESULT lookupResult); + +void Cddb_Initialize(void); +void Cddb_Uninitialize(void); + +HRESULT Cddb_InitializeThread(void); +HRESULT Cddb_UninitializeThread(void); + +LPCWSTR Cddb_CalculateTOC(DINFO *pDisc, LPWSTR pszTOC, size_t cchTOC); +HRESULT Cddb_DoLookup(LPCWSTR pszTOC, HWND hwndParent, CDDB_CB callback, UINT flags, ULONG_PTR user); + +#ifndef IGNORE_API_GRACENOTE +void DefaultValues(DINFO *ps); +#endif + +bool DoCDText(DINFO *ps, wchar_t device); + +HRESULT Cddb_GetIUIOptions(void** ppUIOptions); +#ifndef IGNORE_API_GRACENOTE +void ShutDownCDDB(); +HRESULT Cddb_GetDiscFromCache(BSTR bstrTOC, ICddbDisc **ppDisc); +bool CDEdit(CHAR cDevice, DINFO *ps, HWND hwnd); + +HRESULT Cddb_GetIControl(void **ppControl); +HRESULT Cddb_GetICacheManger(void **ppCache); + +HRESULT Cddb_DisplayDiscInfo(ICddbDisc *pDisc, CDDBUIFlags *pUIFlags, HWND hwndParent); + +void Cddb_GetResultText(HRESULT hr, LPWSTR pszResult, INT cchResult, LPWSTR pszReason, INT cchReason); +#endif + +HRESULT Cddb_DisplayResultDlg(HWND hwndParent, HRESULT result, DWORD dwAutoCloseDelay, UINT flags); // flags can be CDDB_MODAL | CDDB_DISPLAY_IN_PARENT + +#ifndef IGNORE_API_GRACENOTE + +// info calls work only in callback +HWND Cddb_GetInfoWindow(void); +void Cdbb_DisplayInfo(LPCWSTR pszTitle, LPCWSTR pszCaption, LPCWSTR pszStatus, INT percentCompleted); + +void StoreDisc(unsigned int cddb_id, ICddbDiscPtr pDisc); +#endif + +bool StoreCDText(unsigned int cddb_id, wchar_t device); +void StoreCDNoInfo(unsigned int cddb_id); + +bool QueryDINFO(unsigned int cddb_id, DINFO *info); +//#ifndef IGNORE_API_GRACENOTE +bool StoreDINFO(unsigned cddb_id, DINFO *info); +//#endif +#endif diff --git a/Src/Plugins/Input/in_cdda/CDDBInterface.h b/Src/Plugins/Input/in_cdda/CDDBInterface.h new file mode 100644 index 00000000..cf357baf --- /dev/null +++ b/Src/Plugins/Input/in_cdda/CDDBInterface.h @@ -0,0 +1,10 @@ +#ifndef CDDBInterface_h +#define CDDBInterface_h + +//#import "CDDBControl.tlb" no_namespace, named_guids, raw_interfaces_only +//#import "../gracenote/CDDBControlWinamp.dll" no_namespace, named_guids, raw_interfaces_only +#ifndef IGNORE_API_GRACENOTE +#include "../gracenote/cddbcontrolwinamp.tlh" +#endif + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/CDPlay.cpp b/Src/Plugins/Input/in_cdda/CDPlay.cpp new file mode 100644 index 00000000..da2bbadf --- /dev/null +++ b/Src/Plugins/Input/in_cdda/CDPlay.cpp @@ -0,0 +1 @@ +#include "CDPlay.h"
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/CDPlay.h b/Src/Plugins/Input/in_cdda/CDPlay.h new file mode 100644 index 00000000..49de6601 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/CDPlay.h @@ -0,0 +1,34 @@ +#ifndef NULLSOFT_CDPLAYH +#define NULLSOFT_CDPLAYH + +class C_CDPlay +{ +public: + virtual ~C_CDPlay() { } + + virtual int play(wchar_t drive, int track)=0; + virtual void stop()=0; + virtual void pause()=0; + virtual void unpause()=0; + virtual int getlength()=0; + virtual int getoutputtime()=0; + virtual void setoutputtime(int time_in_ms)=0; + virtual void setvolume(int _a_v, int _a_p)=0; + + //called by winampGetExtendedRead_* + virtual int open(wchar_t drive, int track) { return 1; } + virtual int read(char *dest, int len, int *killswitch) { return 0; } + + // queries if this is the currently playing drive & track, passing track == 0 just means check drive + bool IsPlaying(wchar_t drive, int track=0) + { + if (drive == g_drive && (!track)) + return true; + + return false; + } + + wchar_t g_drive; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/CDText.cpp b/Src/Plugins/Input/in_cdda/CDText.cpp new file mode 100644 index 00000000..21518a80 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/CDText.cpp @@ -0,0 +1,265 @@ +#include "main.h" +#include "cddb.h" +#include "api__in_cdda.h" +#ifndef IGNORE_API_GRACENOTE +#include "../primo/obj_primo.h" +#endif +//#include "CDPlay.h" +#include "DAEPlay.h" +#include "../nu/ns_wc.h" +#include "../nde/ndestring.h" + +#ifndef IGNORE_API_GRACENOTE +const char *ReadLine(const char *input, char *output, size_t size, int codepage) +{ + size--; // leave room for null terminator + while (input && *input && *input != '\r' && size) + { + char *next = CharNextExA(codepage, input, 0); + while (input != next) + { + if (size) + { + *output = *input; + output++; + *output = 0; // safe because we left room for the null terminator + size--; + } + input++; + } + } + + + if (*input == '\r') + input++; + if (*input == '\n') + input++; + + return input; +} + + +static bool CDText_Process(DINFO *ps, const char *title, const char *performers, const char *composers, int codepage) +{ + char thisTitle[1024] = {0}; + + const char *titles = title; + // first, get disc title + thisTitle[0] = 0; + titles = ReadLine(titles, thisTitle, 1024, codepage); + + if (thisTitle[0]) + { + ndestring_release(ps->title); + int count = MultiByteToWideChar(codepage, 0, thisTitle, -1, 0, 0); + ps->title = ndestring_malloc(count*sizeof(wchar_t)); + MultiByteToWideChar(codepage, 0, thisTitle, -1, ps->title, count); + } + + // now get track titles + int trackNum = 0; + while (titles && *titles) + { + if (trackNum == ps->ntracks) + break; + TRACKINFO &trackInfo = ps->tracks[trackNum]; + thisTitle[0] = 0; + titles = ReadLine(titles, thisTitle, 1024, codepage); + + if (thisTitle[0]) + { + ndestring_release(trackInfo.title); + int count = MultiByteToWideChar(codepage, 0, thisTitle, -1, 0, 0); + trackInfo.title = ndestring_malloc(count*sizeof(wchar_t)); + MultiByteToWideChar(codepage, 0, thisTitle, -1, trackInfo.title, count); + } + + trackNum++; + } + + titles = performers; + // now get disc artist + thisTitle[0] = 0; + titles = ReadLine(titles, thisTitle, 1024, codepage); + + if (thisTitle[0]) + { + ndestring_release(ps->artist); + int count = MultiByteToWideChar(codepage, 0, thisTitle, -1, 0, 0); + ps->artist = ndestring_malloc(count*sizeof(wchar_t)); + MultiByteToWideChar(codepage, 0, thisTitle, -1, ps->artist, count); + } + + // now get track artists + trackNum = 0; + while (titles && *titles) + { + if (trackNum == ps->ntracks) + break; + TRACKINFO &trackInfo = ps->tracks[trackNum]; + + thisTitle[0] = 0; + titles = ReadLine(titles, thisTitle, 1024, codepage); + + if (thisTitle[0]) + { + ndestring_release(trackInfo.artist); + int count = MultiByteToWideChar(codepage, 0, thisTitle, -1, 0, 0); + trackInfo.artist = ndestring_malloc(count*sizeof(wchar_t)); + MultiByteToWideChar(codepage, 0, thisTitle, -1, trackInfo.artist, count); + } + + trackNum++; + } + + titles = composers; + // now get disc composer + thisTitle[0] = 0; + titles = ReadLine(titles, thisTitle, 1024, codepage); + + if (thisTitle[0]) + { + ndestring_release(ps->composer); + int count = MultiByteToWideChar(codepage, 0, thisTitle, -1, 0, 0); + ps->composer = ndestring_malloc(count*sizeof(wchar_t)); + MultiByteToWideChar(codepage, 0, thisTitle, -1, ps->composer, count); + } + + // now get track composers + trackNum = 0; + while (titles && *titles) + { + if (trackNum == ps->ntracks) + break; + TRACKINFO &trackInfo = ps->tracks[trackNum]; + + thisTitle[0] = 0; + titles = ReadLine(titles, thisTitle, 1024, codepage); + + if (thisTitle[0]) + { + ndestring_release(trackInfo.composer); + int count = MultiByteToWideChar(codepage, 0, thisTitle, -1, 0, 0); + trackInfo.composer = ndestring_malloc(count*sizeof(wchar_t)); + MultiByteToWideChar(codepage, 0, thisTitle, -1, trackInfo.composer, count); + } + + trackNum++; + } + + ps->populated = true; + return true; +} + +bool DoCDText(DINFO *ps, char device) +{ + if (!device) + return false; + + if (config_use_veritas) + { + obj_primo *primo=0; + waServiceFactory *sf = line.service->service_getServiceByGuid(obj_primo::getServiceGuid()); + if (sf) primo = reinterpret_cast<obj_primo *>(sf->getInterface()); + + if (!primo) + return false; + + DWORD unit = device; + DWORD tracks; + if (primo->DiscInfoEx(&unit, 0, NULL, NULL, NULL, &tracks, NULL, NULL) != PRIMOSDK_OK) // CDTextInfoEJ suggest that this needs to be called first + { + sf->releaseInterface(primo); + return false; + } + if (ps->ntracks == 0) // go ahead and set if it's not set yet + ps->ntracks = tracks; + char titleE[8192] = "", performerE[8192] = "", composerE[8192] = "", titleJ[2000] = "", performerJ[2000] = "", composerJ[2000] = ""; + if (primo->CDTextInfoEJ(&unit, (PBYTE)titleE, (PBYTE)performerE, (PBYTE)composerE, (PBYTE)titleJ, (PBYTE)performerJ, (PBYTE)composerJ) == PRIMOSDK_OK) + { + sf->releaseInterface(primo); + // read titles + if (titleE[0]) + return CDText_Process(ps, titleE, performerE, composerE, 28591); + else + return CDText_Process(ps, titleJ, performerJ, composerJ, 932); + } + } + + return false; +} +#else +bool DoCDText(DINFO *ps, wchar_t device) +{ + if (!device) + return false; + + DAEPlay *dae = (g_cdplay && g_cdplay == daePlayer && g_cdplay->g_drive == device ? daePlayer : new DAEPlay); + if (dae) + { + if (dae != daePlayer) + { + if (dae->open(device, 1)) + { + delete(dae); + return false; + } + } + + DAEPlay::CDTextArray* cd_text = dae->getCDText(); + if ((int)cd_text > 0) + { + if (ps) + { + ps->ntracks = cd_text->size() - 1; + + ndestring_release(ps->title); + ps->title = ndestring_wcsdup(cd_text[DAEPlay::CD_TEXT_TITLE][0]); + + ndestring_release(ps->artist); + ps->artist = ndestring_wcsdup(cd_text[DAEPlay::CD_TEXT_PERFORMER][0]); + + // TODO match this to the supported list of genres... + /*ndestring_release(ps->genre); + ps->genre = ndestring_wcsdup(cd_text[DAEPlay::CD_TEXT_GENRE][0]);*/ + + ndestring_release(ps->composer); + ps->composer = ndestring_wcsdup(cd_text[DAEPlay::CD_TEXT_COMPOSER][0]); + + for (size_t i = 1; i < cd_text->size(); i++) + { + if (i == ps->ntracks + 1) + break; + + TRACKINFO &trackInfo = ps->tracks[i-1]; + + // TODO improve error handling + ndestring_release(trackInfo.artist); + trackInfo.artist = ndestring_wcsdup(cd_text[DAEPlay::CD_TEXT_PERFORMER][i]); + //free(cd_text[DAEPlay::CD_TEXT_PERFORMER][i]); + + ndestring_release(trackInfo.title); + trackInfo.title = ndestring_wcsdup(cd_text[DAEPlay::CD_TEXT_TITLE][i]); + //free(cd_text[DAEPlay::CD_TEXT_TITLE][i]); + + ndestring_release(trackInfo.genre); + trackInfo.genre = ndestring_wcsdup(cd_text[DAEPlay::CD_TEXT_GENRE][i]); + //free(cd_text[DAEPlay::CD_TEXT_GENRE][i]); + + ndestring_release(trackInfo.composer); + trackInfo.composer = ndestring_wcsdup(cd_text[DAEPlay::CD_TEXT_COMPOSER][i]); + //free(cd_text[DAEPlay::CD_TEXT_COMPOSER][i]); + } + + ps->populated = true; + } + + if (dae != daePlayer) delete(dae); + return true; + } + if (dae != daePlayer) delete(dae); + } + + return false; +} +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/CONFIG.Cpp b/Src/Plugins/Input/in_cdda/CONFIG.Cpp new file mode 100644 index 00000000..14a813fd --- /dev/null +++ b/Src/Plugins/Input/in_cdda/CONFIG.Cpp @@ -0,0 +1,261 @@ +#include "main.h" +#ifndef IGNORE_API_GRACENOTE +#include "cddb.h" +#else +extern char config_use_cddb; +//char config_use_cddb = 0; +#endif +#include "api__in_cdda.h" +#include "../winamp/wa_ipc.h" + +//int config_sample=1; +//int config_use_veritas=1; +//int config_rip_veritas=1; +//int config_maxextractspeed=4; +//int config_offset=0; +//int config_read_leadin=0; + +//int config_rip_buffersize=24; +//int config_rip_buffers=256; + +//int config_play_buffersize=1; +//int config_play_buffers=256; + +char *INI_FILE = 0; +char app_name[] = "CDDA/Line Input Driver"; + +static int _r_i(char *name, int def) +{ + name += 7; + return GetPrivateProfileIntA(app_name, name, def, INI_FILE); +} + +#define RI(x) (( x ) = _r_i(#x,( x ))) + +static void _w_i(char *name, int d) +{ + char str[120] = {0}; + wsprintfA(str, "%d", d); + name += 7; + WritePrivateProfileStringA(app_name, name, str, INI_FILE); +} +#define WI(x) _w_i(#x,( x )) + +void config_read() +{ + INI_FILE = (char*)SendMessage(line.hMainWindow, WM_WA_IPC, 0, IPC_GETINIFILE); + //RI(config_sample); + RI(config_use_cddb); + /*RI(config_use_veritas); + config_rip_veritas=config_use_veritas; + RI(config_rip_veritas);*/ + //RI(config_maxextractspeed); + + //RI(config_offset); + + //RI(config_rip_buffersize); + //RI(config_rip_buffers); + + //RI(config_play_buffersize); + //RI(config_play_buffers); + //RI(config_read_leadin); +} + +void config_write() +{ + //WI(config_sample); + WI(config_use_cddb); + //WI(config_use_veritas); + //WI(config_rip_veritas); + //WI(config_maxextractspeed); +} + +// TODO need to review all of this!!! +#if 0 +BOOL CALLBACK ripConfigProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + //if (config_rip_veritas) CheckDlgButton(hwndDlg,IDC_VERITAS,BST_CHECKED); + SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)L"0.5x"); + SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)L"1x"); + SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)L"2x"); + SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)L"4x"); + SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)L"8x"); + SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)L"16x"); + SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_UNLIMITED)); + + if (config_maxextractspeed<0)config_maxextractspeed=0; + if (config_maxextractspeed>6)config_maxextractspeed=6; + //if (config_maxextractspeed > 4 && getRegVer() < 1) config_maxextractspeed=4; + + SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_SETCURSEL,config_maxextractspeed,0); + return 0; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + /*case IDC_VERITAS: + config_rip_veritas = IsDlgButtonChecked(hwndDlg,IDC_VERITAS)?1:0; + break;*/ + case IDC_COMBO1: + { + int x=(INT)SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_GETCURSEL,0,0); + if (x != CB_ERR) + { + if (x<0)x=0; + if (x>6)x=6; + /*if (x > 4 && getRegVer() < 1) + { + SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_SETCURSEL,4,0); + x=4; + config_maxextractspeed=x; + wchar_t title[64] = {0}; + if (MessageBoxW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_PURCHASE_WINAMP_PRO_PROMPT), + WASABI_API_LNGSTRINGW_BUF(IDS_WINAMP_PRO_FEATURE,title,64),MB_YESNO) == IDYES) + { + SendMessage(line.hMainWindow,WM_WA_IPC,0,IPC_GETREGISTEREDVERSION); + } + } + else*/ config_maxextractspeed=x; + } + } + break; + } + return FALSE; + case WM_DESTROY: + config_write(); + return FALSE; + } + return 0; +} +#endif + +static long cddbResourceID=202; +static long cddbMaxFrames=46; +static long cddbHeight=80, cddbWidth=80; +static HMODULE cddbUI = 0; + + +static bool GetUI() +{ + #ifndef IGNORE_API_GRACENOTE + ICddbUIOptions *pUIOptions; + if (SUCCEEDED(Cddb_GetIUIOptions((void**)&pUIOptions))) + { + if (SUCCEEDED(pUIOptions->GetCurrent(UI_DISP_PROGRESS))) // not sure what this does, but it seems to be required + { + pUIOptions->get_ProgressResourceID(&cddbResourceID); + pUIOptions->get_Frames(&cddbMaxFrames); + pUIOptions->get_Bottom(&cddbHeight); + pUIOptions->get_Right(&cddbWidth); + pUIOptions->get_ResourceHINSTANCE((long *)&cddbUI); + } + pUIOptions->Release(); + return true; + } + #endif + return false; +} + +static HANDLE cddbImage =0; +static int cddbFrame = 0; + +BOOL CALLBACK ConfigProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_DRAWITEM: + if (wParam == IDC_CDDBICON) + { + DRAWITEMSTRUCT *drawItem = (DRAWITEMSTRUCT *) lParam; + if (cddbImage) + { + HDC hdcbm = CreateCompatibleDC(drawItem->hDC); + HGDIOBJ old = SelectObject(hdcbm, cddbImage); + BitBlt(drawItem->hDC, 0, 0, cddbWidth, cddbHeight, hdcbm, (cddbFrame * cddbWidth), 0, SRCCOPY); + SelectObject(hdcbm, old); + DeleteDC(hdcbm); + } + else + { + RECT r={0,0,cddbWidth,cddbHeight}; + FillRect(drawItem->hDC, &r, (HBRUSH)GetStockObject(GRAY_BRUSH)); + } + return TRUE; + } + break; + + case WM_DESTROY: + cddbImage=0; + break; + + case WM_INITDIALOG: + cddbFrame=0; + cddbImage=0; + { + IUnknown *pUnknown = NULL; + #ifndef IGNORE_API_GRACENOTE + Cddb_GetIControl((void**)&pUnknown); + #endif + if (!pUnknown) + { + ShowWindow(GetDlgItem(hwndDlg,IDC_CDDBNOTE),SW_SHOWNA); + ShowWindow(GetDlgItem(hwndDlg,IDC_CDDB),SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg,IDC_CDDBICON),SW_HIDE); + } + else pUnknown->Release(); + } + + //if (config_sample) CheckDlgButton(hwndDlg,IDC_SAMPLE,BST_CHECKED); + if (config_use_cddb&1) CheckDlgButton(hwndDlg,IDC_CDDB,BST_CHECKED); + //if (config_use_veritas) CheckDlgButton(hwndDlg,IDC_VERITAS,BST_CHECKED); + + if ((config_use_cddb&1) && GetUI()) + { + cddbImage = LoadImage(cddbUI, MAKEINTRESOURCE(cddbResourceID), IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE); + SetTimer(hwndDlg, 2, 65, NULL); + } + SetWindowPos(GetDlgItem(hwndDlg, IDC_CDDBICON), 0, 0, 0, cddbWidth, cddbHeight, SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER); + return TRUE; + + case WM_TIMER: + switch (wParam) + { + case 2: + if (cddbFrame < cddbMaxFrames-1) + { + cddbFrame++; + InvalidateRect(GetDlgItem(hwndDlg, IDC_CDDBICON), 0, TRUE); + } + else + KillTimer(hwndDlg, 2); + + break; + } + return 0; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_CDDBICON: + if (HIWORD(wParam) == STN_CLICKED) SendMessage(line.hMainWindow, WM_WA_IPC, (WPARAM)L"http://www.cddb.com/", IPC_OPEN_URL); + return 0; + case IDOK: + //config_sample = IsDlgButtonChecked(hwndDlg,IDC_SAMPLE)?1:0; + config_use_cddb = IsDlgButtonChecked(hwndDlg,IDC_CDDB)?1:0; + //config_use_veritas = IsDlgButtonChecked(hwndDlg,IDC_VERITAS)?1:0; + config_write(); + case IDCANCEL: + EndDialog(hwndDlg,1); + return FALSE; + } + return FALSE; + } + return 0; +} + +void config(HWND hwndParent) +{ + WASABI_API_DIALOGBOXW(IDD_DIALOG1,hwndParent,ConfigProc); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/DAEPlay.cpp b/Src/Plugins/Input/in_cdda/DAEPlay.cpp new file mode 100644 index 00000000..30495ab9 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/DAEPlay.cpp @@ -0,0 +1,423 @@ +#include "DAEPlay.h" +#include "api__in_cdda.h" +#include "../nu/AutoWide.h" +#include <strsafe.h> + +int DAEPlay::getTrackInfo() +{ + CDROM_TOC tableOfContents = {0}; + DWORD tocSize = sizeof(tableOfContents); + + if (!DeviceIoControl(hDrive, IOCTL_CDROM_READ_TOC, NULL, 0, &tableOfContents, tocSize, &tocSize, 0)) + { + return 1; + } + + for (int i = tableOfContents.FirstTrack - 1 ; i < tableOfContents.LastTrack ; i += 1) + { + if (i == g_track) + { + track_length = MSFToBlocks(tableOfContents.TrackData[i+1].Address) - (start_address = MSFToBlocks(tableOfContents.TrackData[i].Address)); + return 0; + } + } + + return 1; +} + +DAEPlay::CDTextArray* DAEPlay::getCDText() +{ + // if we've already cached it, return asap + if (cd_text != 0) + { + return cd_text; + } + + CDROM_TOC tableOfContents = {0}; + DWORD tocSize = sizeof(tableOfContents), returned = 0; + + if (!DeviceIoControl(hDrive, IOCTL_CDROM_READ_TOC, NULL, 0, &tableOfContents, tocSize, &tocSize, 0)) + { + // issue accessing drive + return (cd_text = (CDTextArray *)-1); + } + + // MMC-3 Draft Revision 10g: Table 222 Q Sub-channel control field + tableOfContents.TrackData[0].Control &= 5; + if (!(tableOfContents.TrackData[0].Control == 0/* || tableOfContents.TrackData[0 - 1].Control == 1*/)) + { + // invalid format + return (cd_text = (CDTextArray *)-1); + } + + CDROM_READ_TOC_EX tableOfContentsEx = {0}; + tableOfContentsEx.Format = CDROM_READ_TOC_EX_FORMAT_CDTEXT; + WORD tocSizeEx = 0; + // Read the contents to get the size of the actual data + if (!DeviceIoControl(hDrive, IOCTL_CDROM_READ_TOC_EX, &tableOfContentsEx, sizeof(tableOfContentsEx), &tocSizeEx, sizeof(tocSizeEx), &returned, 0)) + { + // issue accessing drive + return (cd_text = (CDTextArray *)-1); + } + + // The bytes are swapped so we need to switch them around + tocSizeEx = ((tocSizeEx>>8) | (tocSizeEx<<8)) + sizeof(tocSizeEx); + + // Allocate a buffer for reading the actual CD Text data block + char *pCDTextData = new char[tocSizeEx]; + if (!pCDTextData) + { + return (cd_text = (CDTextArray *)-1); + } + + memset(pCDTextData, 0, tocSizeEx); + + // Now read the data + if(!DeviceIoControl(hDrive, IOCTL_CDROM_READ_TOC_EX, &tableOfContentsEx, sizeof(tableOfContentsEx), pCDTextData, tocSizeEx, &returned, 0)) + { + delete []pCDTextData; + return (cd_text = (CDTextArray *)-1); + } + + tocSizeEx = (WORD)(returned - sizeof(CDROM_TOC_CD_TEXT_DATA)); + + if(!tocSizeEx) + { + delete []pCDTextData; + return (cd_text = (CDTextArray *)-1); + } + + // This is the stuff we really need. It's an array of packs with the data (mostly text) + CDROM_TOC_CD_TEXT_DATA_BLOCK* pCDTextBlock = ((CDROM_TOC_CD_TEXT_DATA*)(BYTE*)pCDTextData)->Descriptors; + + // As we go through the packets we'll store the strings in this array. The strings are often in different packets and need to be concatenated together. + cd_text = new CDTextArray[CD_TEXT_NUM_PACKS]; + UINT m_nGenreCode = -1; + + // Loop through all the packets extracting the data we need. Each packet starts with a packet type, the track number, language code, + // character offset, and character size. The packets end with a 2 byte CRC. We don't need most of this stuff to display the info. We can get + // the packets for any track and they'll have all the data for each track (it's duplicated). + for( ;tocSizeEx >= sizeof(CDROM_TOC_CD_TEXT_DATA_BLOCK); tocSizeEx -= sizeof(CDROM_TOC_CD_TEXT_DATA_BLOCK), pCDTextBlock++) + { + if (pCDTextBlock->TrackNumber > tableOfContents.LastTrack) // Track is beyond what is on the disc + continue; + + // Only looking for CD Text item packets + if ((pCDTextBlock->PackType -= 0x80) >= 0x10) + continue; + + // Genre is encoded with the code and the supplemental text in the same packet + if (m_nGenreCode == -1 && pCDTextBlock->PackType == CD_TEXT_GENRE) + { + // TODO + m_nGenreCode = pCDTextBlock->Text[0]*16 + pCDTextBlock->Text[1]; + /*CString Text = !pCDTextBlock->Unicode + ? CString(CStringA((CHAR*)pCDTextBlock->Text+2, CD_TEXT_FIELD_LENGTH-2)) + : CString(CStringW((WCHAR*)pCDTextBlock->WText+2, CD_TEXT_FIELD_LENGTH-2)); + cd_text[pCDTextBlock->PackType][0] = Text;*/ + } + else + { + // Parse the text. There could be more than one item in the text block separated by null bytes so we need to check the whole thing + // The text is in a block of up to 12 characters. We'll just keep adding to our buffers until we go through all of the packets. + int nLengthRemaining = CD_TEXT_FIELD_LENGTH; + UINT nTrack = pCDTextBlock->TrackNumber; + UINT nOffset = 0; + // We're at the end of text when: + // Used up 12 chars + // Got to a null + // On the last track + while (nTrack <= tableOfContents.LastTrack && nLengthRemaining > 0 && nOffset < CD_TEXT_FIELD_LENGTH) + { + wchar_t *text = (wchar_t*)calloc(nLengthRemaining + 1, sizeof(wchar_t)); + if (!text) continue; + + lstrcpyn(text, (pCDTextBlock->Unicode ? pCDTextBlock->WText + nOffset : AutoWide((char *)pCDTextBlock->Text + nOffset)), nLengthRemaining + 1); + + if (!cd_text[pCDTextBlock->PackType][nTrack]) + cd_text[pCDTextBlock->PackType][nTrack] = (wchar_t*)calloc(lstrlen(text) + 1, sizeof(wchar_t)); + else // TODO error handling + cd_text[pCDTextBlock->PackType][nTrack] = (wchar_t*)realloc(cd_text[pCDTextBlock->PackType][nTrack], (lstrlen(cd_text[pCDTextBlock->PackType][nTrack]) + lstrlen(text) + 1) * sizeof(wchar_t)); + + if (text[0]) + lstrcat(cd_text[pCDTextBlock->PackType][nTrack], text); + + nOffset += lstrlen(text) + 1; + nLengthRemaining = nLengthRemaining - lstrlen(text) - 1; + ++nTrack; + free(text); + } + } + } while (0); + + delete []pCDTextData; + return cd_text; +} + +int DAEPlay::threadProc2() +{ + while (1) + { + if (need_seek != -1) + { + current_sector = ((need_seek * CD_BLOCKS_PER_SECOND) / 1000) / DEF_SECTORS_PER_READ; + bytes_in_sbuf = 0; + line.outMod->Flush(need_seek); + need_seek = -1; + } + + if (fillBuffer(killswitch)) + { + PostMessage(line.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return 0; + } + + if (!bytes_in_sbuf && !killswitch) + { + //wait for output to be finished + line.outMod->Write(NULL, 0); + while (!killswitch && line.outMod->IsPlaying()) Sleep(10); + if (!killswitch) + { + CloseHandle(hDrive); + hDrive = INVALID_HANDLE_VALUE; + PostMessage(line.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + } + return 0; + } + + if (killswitch) + { + CloseHandle(hDrive); + hDrive = INVALID_HANDLE_VALUE; + return 0; + } + + char sample_buffer[576*4*2] = {0}; + int bytes = sizeof(sample_buffer) / 2; // enough room for dsp bullcrap + bytes = min((int)bytes_in_sbuf, (int)bytes); + memcpy(sample_buffer, sbuf, bytes); + if (bytes_in_sbuf > bytes) memcpy(sbuf, sbuf + bytes, bytes_in_sbuf - bytes); + bytes_in_sbuf -= bytes; + + line.VSAAddPCMData(sample_buffer, g_nch, 16, line.outMod->GetWrittenTime()); + line.SAAddPCMData(sample_buffer, g_nch, 16, line.outMod->GetWrittenTime()); + + if (line.dsp_isactive()) + bytes = line.dsp_dosamples((short *)sample_buffer, bytes / g_nch / 2, 16, g_nch, 44100) * (g_nch * 2); + + while (line.outMod->CanWrite() < bytes && !killswitch) Sleep(66); + if (killswitch) + { + CloseHandle(hDrive); + hDrive = INVALID_HANDLE_VALUE; + return 0; + } + + line.outMod->Write(sample_buffer, bytes); + } + + CloseHandle(hDrive); + hDrive = INVALID_HANDLE_VALUE; + return 0; +} + +void DAEPlay::stop() +{ + if (hThread) + { + killswitch = 1; + WaitForSingleObject(hThread, INFINITE); + } + + line.outMod->Close(); +} + +int DAEPlay::open(wchar_t drive, int track) //called by winampGetExtendedRead +{ + g_track = track-1; + if (g_track < 0) + return 1; + + g_drive = drive; + + wchar_t CDDevice[8]=L"\\\\.\\x:"; + CDDevice[4] = drive; + + if (hDrive != INVALID_HANDLE_VALUE) + CloseHandle(hDrive); + + hDrive = CreateFile(CDDevice, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hDrive == INVALID_HANDLE_VALUE) + return 1; + + // TODO only store the request track! + if (getTrackInfo()) + return 1; + + g_playlength = (track_length / CD_BLOCKS_PER_SECOND) * 1000; + + bytes_in_sbuf = 0; + + if (!sbuf) + sbuf = (unsigned char *)malloc(2352 * buf_size); + if (!sbuf) + return 1; + + data = new BYTE[DEF_SECTORS_PER_READ * CDROM_RAW_BYTES_PER_SECTOR]; + if (!data) + return 1; + + return 0; +} + +int DAEPlay::play(wchar_t drive, int track) //called by winamp2's normal(old) play() interface +{ + int old_drive = g_drive; + + if (open(drive, track)) return 1; + + // do this here as it helps to prevent an audio glitch on first playback and volume is set low + setvolume(a_v, a_p); + + int maxlat = line.outMod->Open(44100, g_nch, 16, -1, -1); + if (maxlat < 0) + { + g_drive = 0; + return 1; + } + + // to prevent re-spinning as we're not going to get cd-text later + // if it's not able to be obtained when first opening the device. + if (old_drive != drive) + { + if ((int)cd_text > 0) + delete []cd_text; + cd_text = NULL; + getCDText(); + } + + line.SetInfo(1411, 44, g_nch, 1); + line.SAVSAInit(maxlat, 44100); + line.VSASetInfo(g_nch, 44100); + line.is_seekable = 1; + + bytes_in_sbuf = 0; + current_sector = 0; + killswitch = 0; + DWORD thread_id = 0; + hThread = CreateThread(NULL, NULL, &threadProc, (LPVOID)this, NULL, &thread_id); + SetThreadPriority(hThread, AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + + //open the device thru MCI (for getfileinfo to work properly) + g_playtrack = track; + + return 0; +} + +int DAEPlay::fillBuffer(int kill) +{ + if (!kill && bytes_in_sbuf <= 0) + { + if (current_sector < (track_length / DEF_SECTORS_PER_READ)) + { + // Contains an offset into the CD-ROM disc where data will be read. You can calculate this offset by multiplying the starting sector number for the request times 2048. + RAW_READ_INFO _RawReadInfo = {0}; + _RawReadInfo.DiskOffset.QuadPart = ((current_sector * DEF_SECTORS_PER_READ) + start_address) * CDROM_COOKED_BYTES_PER_SECTOR; + _RawReadInfo.TrackMode = CDDA; + _RawReadInfo.SectorCount = DEF_SECTORS_PER_READ; + + DWORD data_length = DEF_SECTORS_PER_READ * CDROM_RAW_BYTES_PER_SECTOR; + if (!DeviceIoControl(hDrive, IOCTL_CDROM_RAW_READ, &_RawReadInfo, sizeof(_RawReadInfo), + data, data_length, &data_length, 0)) + { + CloseHandle(hDrive); + hDrive = INVALID_HANDLE_VALUE; + return 1; + } + + // TODO make sure the buffer size is enough for our needs + memcpy(sbuf, data, DEF_SECTORS_PER_READ * CDROM_RAW_BYTES_PER_SECTOR); + bytes_in_sbuf += DEF_SECTORS_PER_READ * CDROM_RAW_BYTES_PER_SECTOR; + } + current_sector++; + } + return 0; +} + +int DAEPlay::read(char *dest, int len, int *killswitch) //called by winampGetExtendedRead_getData +{ + int l = 0; + + // TODO make sure this will handle a CD ejection... + while (l < len && !*killswitch) + { + if (fillBuffer(*killswitch)) + { + return -1; + } + + if (!bytes_in_sbuf) break; + + int bytes = min(bytes_in_sbuf, len - l); + memcpy(dest + l, sbuf, bytes); + + if (bytes_in_sbuf > bytes) memcpy(sbuf, sbuf + bytes, bytes_in_sbuf - bytes); + bytes_in_sbuf -= bytes; + + l += bytes; + } + + return l; +} + +DAEPlay::DAEPlay() +{ + sbuf = NULL; + data = NULL; + cd_text = NULL; + bytes_in_sbuf = 0; + buf_size = 64; //make it configitem + g_track = -1; + + // fix these three as that's normal + g_nch = 2; + g_srate = 44100; + g_bps = 16; + + killswitch = 0; + hDrive = INVALID_HANDLE_VALUE; + hThread = NULL; + need_seek = -1; + current_sector = 0; + start_address = 0; + track_length = 0; +} + +DAEPlay::~DAEPlay() +{ + if (hDrive != INVALID_HANDLE_VALUE) + { + CloseHandle(hDrive); + hDrive = INVALID_HANDLE_VALUE; + } + + if (sbuf) + { + free(sbuf); + sbuf = NULL; + } + + if (data) + { + delete []data; + data = NULL; + } + + if ((int)cd_text > 0) + { + delete []cd_text; + } + cd_text = NULL; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/DAEPlay.h b/Src/Plugins/Input/in_cdda/DAEPlay.h new file mode 100644 index 00000000..940dd508 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/DAEPlay.h @@ -0,0 +1,112 @@ +#ifndef NULLSOFT_DAEPLAYH +#define NULLSOFT_DAEPLAYH + +#include "Main.h" +#include "CDPlay.h" +#include "../nu/AutoLock.h" +#include <map> +#include <winioctl.h> +#include "Ntddcdrm.h" + +#define CD_BLOCKS_PER_SECOND 75 +#define DEF_SECTORS_PER_READ 8 +#define CDROM_RAW_BYTES_PER_SECTOR 2352 +#define CDROM_COOKED_BYTES_PER_SECTOR 2048 + +using namespace Nullsoft::Utility; +class DAEPlay : public C_CDPlay +{ +public: + DAEPlay(); + ~DAEPlay(); + int open(wchar_t drive, int track); + int play(wchar_t drive, int track); + + static DWORD WINAPI threadProc(LPVOID lpParameter) + { + DAEPlay *wp = (DAEPlay *)lpParameter; + return wp->threadProc2(); + } + + int read(char *dest, int len, int *killswitch); + int threadProc2(); + void stop(); + + void pause() + { + line.outMod->Pause(1); + } + + void unpause() + { + line.outMod->Pause(0); + } + + int getlength() + { + return g_playlength; + } + + int getoutputtime() + { + return line.outMod->GetOutputTime(); + } + + void setoutputtime(int time_in_ms) + { + need_seek = time_in_ms; + } + + void setvolume(int _a_v, int _a_p) + { + line.outMod->SetVolume(_a_v); + line.outMod->SetPan(_a_p); + } + + typedef std::map<int, wchar_t*> CDTextArray; + CDTextArray* getCDText(); + + enum PacketTypes { + CD_TEXT_TITLE, + CD_TEXT_PERFORMER, + CD_TEXT_SONGWRITER, + CD_TEXT_COMPOSER, + CD_TEXT_ARRANGER, + CD_TEXT_MESSAGES, + CD_TEXT_DISC_ID, + CD_TEXT_GENRE, + CD_TEXT_TOC_INFO, + CD_TEXT_TOC_INFO2, + CD_TEXT_RESERVED_1, + CD_TEXT_RESERVED_2, + CD_TEXT_RESERVED_3, + CD_TEXT_RESERVED_4, + CD_TEXT_CODE, + CD_TEXT_SIZE, + CD_TEXT_NUM_PACKS + }; + +private: + + BYTE * data; + CDTextArray* cd_text; + unsigned char *sbuf; + long bytes_in_sbuf; + int buf_size; + int g_track, g_nch, g_srate, g_bps; + int killswitch; + HANDLE hDrive, hThread; + int need_seek; + + DWORD start_address, track_length, current_sector; + + int getTrackInfo(); + int fillBuffer(int kill); + + DWORD MSFToBlocks(UCHAR msf[4]) + { + return (((msf[1] * (CD_BLOCKS_PER_SECOND * 60)) + (msf[2] * CD_BLOCKS_PER_SECOND) + msf[3]) - 150); + } +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/DB.Cpp b/Src/Plugins/Input/in_cdda/DB.Cpp new file mode 100644 index 00000000..71bfdd99 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/DB.Cpp @@ -0,0 +1,306 @@ +#include "main.h" +#include "cddb.h" +#include "../Winamp/wa_ipc.h" +#include "../nu/AutoChar.h" +#include "../nu/AutoWide.h" +#include "api__in_cdda.h" + +#include <api/service/waservicefactory.h> +#include <shlwapi.h> +#include <atlbase.h> +#include "resource.h" +#include <commctrl.h> +#include <strsafe.h> + +char config_use_cddb = 1; + +#define MAX_CACHE_SIZE 8 + + +typedef struct _CBDATA +{ + DINFO *ps; + CHAR cLetter; + DWORD cddbid; +} CBDATA; + +typedef struct _CACHEREC +{ + DINFO discInfo; + HRESULT result; +} CACHEREC; + +static CACHEREC cddb_cache[MAX_CACHE_SIZE]; +static int cddb_cache_cursor = 0; + +void DefaultValues(DINFO *ps) +{ + ps->Reset(); + ps->compilation = false; + ps->discnum = 0; + ps->numdiscs = 0; + + ps->populated = true; +} + +static bool IsInternetAvailable() +{ + return !!SendMessage(line.hMainWindow, WM_WA_IPC, 0, IPC_INETAVAILABLE); +} + +static DWORD failid[26] ={ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +#define WRITEINFO(obj, field, name) { obj->get_ ## field(&str); fwprintf(fp, name L": %s\r\n", str.m_str); } +#define WRITEINFO_LONG(obj, field, name) { long val; obj->get_ ## field(&val); fwprintf(fp, name L": %d\r\n", val); } + +#ifndef IGNORE_API_GRACENOTE +static void DumpInfoFile(ICddbDiscPtr disc) +{ +#if 0 // keep this code, but we'll wait until a later version to implement more data + long numTracks; + FILE *fp = fopen("c:\\cdinfo.txt", "wt"); + // wchar_t BOM[] = {0xFEFF, 0}; + // fwrite(BOM, 2, 1, fp); + CComBSTR str; + + WRITEINFO(disc, Toc, L"Disc TOC"); + WRITEINFO(disc, Artist, L"Disc Artist"); + WRITEINFO(disc, Title, L"Disc Title"); + WRITEINFO(disc, Label, L"Disc Label"); + WRITEINFO(disc, Year, L"Disc Year"); + WRITEINFO(disc, MediaId, L"Disc MediaId"); + WRITEINFO(disc, Notes, L"Disc Notes"); + WRITEINFO(disc, GenreId, L"Disc GenreId"); + WRITEINFO(disc, SecondaryGenreId, L"Disc Secondary GenreId"); + WRITEINFO(disc, RegionId, L"Disc RegionId"); + WRITEINFO(disc, Revision, L"Disc Revision"); + WRITEINFO(disc, TotalInSet, L"Disc Discs"); + WRITEINFO(disc, NumberInSet, L"Disc Disc #"); + WRITEINFO(disc, LanguageId, L"Disc Language Id"); + WRITEINFO(disc, RevisionTag, L"Disc Revision Tag"); + WRITEINFO_LONG(disc, Compilation, L"Disc Compilation"); + + ICddbDisc2 *disc2; + disc->QueryInterface(&disc2); + WRITEINFO(disc2, ReleaseDate, L"Disc Release Date"); + WRITEINFO(disc2, RecordingDate, L"Disc Recording Date"); + WRITEINFO(disc2, ProductCode, L"Disc Product Code"); + WRITEINFO(disc2, YearComposed, L"Disc Year Composed"); + disc2->Release(); + + ICddbCredits *credits; + disc->get_Credits(&credits); + long creditCount; + credits->get_Count(&creditCount); + for (long c = 0;c < creditCount;c++) + { + ICddbCredit *credit; + credits->GetCredit(c + 1, &credit); + WRITEINFO(credit, Id, L"Credit ID"); + WRITEINFO(credit, Name, L"Credit Name"); + WRITEINFO(credit, Notes, L"Credit Notes"); + credit->Release(); + } + credits->Release(); + + ICddbTracks *tracks; + disc->get_Tracks(&tracks); + tracks->get_Count(&numTracks); + for (int i = 0;i < numTracks;i++) + { + ICddbTrack *track; + tracks->GetTrack(i + 1, &track); + fputws(L"-----------------\r\n", fp); + WRITEINFO(track, Title, L"Track Title"); + WRITEINFO(track, Artist, L"Track Artist"); + WRITEINFO(track, Year, L"Track Year"); + WRITEINFO(track, Label, L"Track Label"); + WRITEINFO(track, Notes, L"Track Notes"); + WRITEINFO(track, GenreId, L"Track GenreId"); + WRITEINFO(track, SecondaryGenreId, L"Track SecondaryGenreId"); + WRITEINFO(track, Lyrics, L"Track Lyrics"); + WRITEINFO(track, BeatsPerMinute, L"Track BPM"); + WRITEINFO(track, ISRC, L"Track ISRC"); + + ICddbTrack2 *track2; + track->QueryInterface(&track2); + WRITEINFO(track2, ReleaseDate, L"Release Date"); + WRITEINFO(track2, RecordingDate, L"Recording Date"); + WRITEINFO(track2, YearComposed, L"Year Composed"); + track2->Release(); + + ICddbCredits *credits; + track->get_Credits(&credits); + if (credits) + { + long creditCount; + credits->get_Count(&creditCount); + for (long c = 0;c < creditCount;c++) + { + ICddbCredit *credit; + credits->GetCredit(c + 1, &credit); + WRITEINFO(credit, Id, L"Credit ID"); + WRITEINFO(credit, Name, L"Credit Name"); + WRITEINFO(credit, Notes, L"Credit Notes"); + credit->Release(); + } + credits->Release(); + } + track->Release(); + } + + tracks->Release(); + fclose(fp); +#endif +} +#endif + +#ifdef IGNORE_API_GRACENOTE +static HRESULT CALLBACK Cddb_ResultCallback(HRESULT result, ICddbDisc *pDisc, DWORD *pdwAutoCloseDelay, ULONG_PTR user) +{ + int index; + CACHEREC *pRec; + CBDATA *pData = (CBDATA*)user; + if (!pData) return E_POINTER; + DINFO *pDI = NULL; + + for (index = 0, pRec = &cddb_cache[0]; index < MAX_CACHE_SIZE && 0 != pRec->discInfo.CDDBID; pRec++, index++) + { + if (pRec->discInfo.CDDBID == pData->cddbid) + { + pDI = &pRec->discInfo; + + break; + } + } + if (!pDI) return S_OK; + + if (S_OK == result) + { + #ifndef IGNORE_API_GRACENOTE + if (pDisc) + { + DumpInfoFile(pDisc); + GetDiscInfo(pDisc, pDI); + StoreDisc(pDI->CDDBID, pDisc); + ICddbCacheManager* pCache; + HRESULT hr = Cddb_GetICacheManger((void**)&pCache); + if (SUCCEEDED(hr)) + { + CComBSTR toc; + pDisc->get_Toc(&toc); + pCache->StoreDiscByToc(toc, pDisc); + pCache->Release(); + } + pDI->populated = true; + } + #endif + } + else + { + #ifndef IGNORE_API_GRACENOTE + if (DoCDText(pDI, pData->cLetter)) + { + StoreCDText(pDI->CDDBID, pData->cLetter); + result = S_OK; + } + #endif + } + + CddbCache_SetDisc(pDI, result); + + if (pData->ps->CDDBID == pDI->CDDBID) + { + *pData->ps = *pDI; + } + return S_OK; +} +#endif + +void InitializeCddbCache(void) +{ + ZeroMemory(cddb_cache, sizeof(CACHEREC)*MAX_CACHE_SIZE); + cddb_cache_cursor = 0; +} + +void UninitializeCddbCache(void) +{ + ZeroMemory(cddb_cache, sizeof(CACHEREC)*MAX_CACHE_SIZE); + cddb_cache_cursor = 0; +} + +HRESULT CddbCache_SetDisc(DINFO *pDiscInfo, HRESULT lookupResult) +{ + int index; + CACHEREC *pRec; + if (!pDiscInfo || 0 == pDiscInfo->CDDBID) return E_FAIL; + + for (index = 0, pRec = &cddb_cache[0]; index < MAX_CACHE_SIZE && 0 != pRec->discInfo.CDDBID; pRec++, index++) + { + if (pRec->discInfo.CDDBID == pDiscInfo->CDDBID) break; + } + if (0 == pRec->discInfo.CDDBID || MAX_CACHE_SIZE == index) + { + pRec = &cddb_cache[cddb_cache_cursor]; + cddb_cache_cursor++; + if (cddb_cache_cursor >= MAX_CACHE_SIZE) cddb_cache_cursor = 0; + } + + if (pRec) + { + pRec->result = lookupResult; + pRec->discInfo = *pDiscInfo; + } + + return S_OK; +} + +int GetCDDBInfo(DINFO *ps, wchar_t device) +{ + int index; + CACHEREC *pRec; + //HRESULT hr; + + //wchar_t szTOC[2048] = {0}; + + if (ps->populated) return 0; + + /* first, look in our memory cache */ + for (index = 0, pRec = &cddb_cache[0]; index < MAX_CACHE_SIZE && 0 != pRec->discInfo.CDDBID; pRec++, index++) + { + if (pRec->discInfo.CDDBID == ps->CDDBID) + { + if (S_OK == pRec->result) + *ps = pRec->discInfo; + else + DefaultValues(ps); + return 0; // no need to notify about changes + + } + } + + /* look it up in the local database */ + if (QueryDINFO(ps->CDDBID, ps)) + { + CddbCache_SetDisc(ps, S_OK); + return 0; + } + + /* check CDDB & CD-Text */ + /*CddbCache_SetDisc(ps, E_PENDING); + if (Cddb_CalculateTOC(ps, szTOC, sizeof(szTOC)/sizeof(wchar_t))) + { + ULONG flags; + CBDATA data; + data.ps = ps; + data.cLetter = device; + data.cddbid = ps->CDDBID; + flags = CDDB_UI_MODAL | CDDB_UI_MULTIPLE | CDDB_UI_NOMATCH; + if (!config_use_cddb || !IsInternetAvailable()) flags |= CDDB_NOINET; + hr = Cddb_DoLookup(szTOC, line.hMainWindow, Cddb_ResultCallback, flags, (ULONG_PTR)&data); + if (FAILED(hr)) Cddb_DisplayResultDlg(line.hMainWindow, hr, AUTOCLOSE_NEVER, flags); + } + else hr = CDDB_E_BADTOC; + if (FAILED(hr)) CddbCache_SetDisc(ps, hr);*/ + return 1;//SUCCEEDED(hr); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/EditCDInfo.cpp b/Src/Plugins/Input/in_cdda/EditCDInfo.cpp new file mode 100644 index 00000000..07cd3769 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/EditCDInfo.cpp @@ -0,0 +1,447 @@ +#include "main.h" +#include "cddb.h" +#include "../nu/ListView.h" +#include "cddbinterface.h" +#include "../Agave/Language/api_language.h" +#include "../Winamp/wa_ipc.h" +#include <atlbase.h> +#include <strsafe.h> + +int _g_disable_cddb; + +#if 0 +static DINFO *p; +static char dialogDevice; +W_ListView trackList; + +static void Populate(HWND hDlg) +{ + int x; + trackList.Clear(); + SetDlgItemTextW(hDlg, IDC_TITLE, p->title); + SetDlgItemTextW(hDlg, IDC_PUBLISHER, p->label); + SetDlgItemTextW(hDlg, IDC_ARTIST, p->artist); + SetDlgItemTextW(hDlg, IDC_GENRE, p->genre); + SetDlgItemTextW(hDlg, IDC_YEAR, p->year); + CheckDlgButton(hDlg, IDC_COMPILATION, p->compilation?BST_CHECKED:BST_UNCHECKED); + for (x = 0; x < p->ntracks; x ++) + { + trackList.InsertItem(x, p->tracks[x].artist, 0); + trackList.SetItemText(x, 1, p->tracks[x].title); + } +} + +static HRESULT CALLBACK Cddb_ResultCallback(DWORD cddbId, HRESULT result, ICddbDisc *pDisc, ULONG_PTR user) +{ + CBDATA *pData = (CBDATA*)user; + DefaultValues(pData->p); + + if(S_OK == result) + { + + } + else if (S_FALSE == result) + { + + } + else if (S_MULTIPLE == result) + { + + } + Populate(pData->hwnd); + return S_OK; +} + +static LRESULT CALLBACK EditProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_NOTIFYFORMAT: + return NFR_UNICODE; + + case WM_INITDIALOG: + trackList.setwnd(GetDlgItem(hDlg, IDC_TRACKS)); + + RECT rect1, rect2; + GetWindowRect(GetDlgItem(hDlg, IDC_EDITARTIST), &rect1); + GetWindowRect(GetDlgItem(hDlg, IDC_EDITTEXT), &rect2); + + trackList.AddCol(WASABI_API_LNGSTRINGW(plugin.hDllInstance,IDS_ARTIST), rect2.left-rect1.left-3); + trackList.AddCol(WASABI_API_LNGSTRINGW(plugin.hDllInstance,IDS_TITLE), (rect2.right-rect2.left)-18); + + if (_g_disable_cddb) EnableWindow(GetDlgItem(hDlg, IDC_GETCDDB), 0); + SendDlgItemMessage(hDlg, IDC_TITLE, EM_LIMITTEXT, 512, 0); + SendDlgItemMessage(hDlg, IDC_PUBLISHER, EM_LIMITTEXT, 512, 0); + SendDlgItemMessage(hDlg, IDC_ARTIST, EM_LIMITTEXT, 512, 0); + SendDlgItemMessage(hDlg, IDC_YEAR, EM_LIMITTEXT, 8, 0); + SendDlgItemMessage(hDlg, IDC_GENRE, EM_LIMITTEXT, 64, 0); + SendDlgItemMessage(hDlg, IDC_EDITTEXT, EM_LIMITTEXT, 512, 0); + SendDlgItemMessage(hDlg, IDC_EDITARTIST, EM_LIMITTEXT, 512, 0); + + if (GetCDDBInfo(p, dialogDevice)) + { + int x; + SetDlgItemText(hDlg, IDC_TITLE, WASABI_API_LNGSTRINGW(line.hDllInstance,IDS_TITLE)); + SetDlgItemText(hDlg, IDC_ARTIST, WASABI_API_LNGSTRINGW(line.hDllInstance,IDS_ALBUM_ARTIST)); + for (x = 0; x < p->ntracks; x ++) + { + wchar_t buf[128] = {0}; + StringCchPrintfW(buf, 128, L"Track %d", x + 1); + trackList.InsertItem(x, "Artist", 0); + trackList.SetItemText(x, 1, buf); + } + } + else + { + Populate(hDlg); + } + + return 1; + case WM_NOTIFY: + { + LPNMHDR l = (LPNMHDR)lParam; + switch (l->code) + { + case LVN_ITEMCHANGED: + { + LPNMLISTVIEW lvNotif = (LPNMLISTVIEW)l; + if (lvNotif->uNewState & LVIS_SELECTED) + { + wchar_t buf[512] = {0}; + trackList.GetText(trackList.GetNextSelected(-1), 0, buf, 512); + SetDlgItemTextW(hDlg, IDC_EDITARTIST, buf); + trackList.GetText(trackList.GetNextSelected(-1), 1, buf, 512); + SetDlgItemTextW(hDlg, IDC_EDITTEXT, buf); + } + } + break; + } + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_GETCDDB: + { + wchar_t szTOC[2048] = {0}; + if (Cddb_CalculateTOC(p, szTOC, sizeof(szTOC)/sizeof(wchar_t))) + { + CBDATA data = { p, hDlg }; + HRESULT hr = Cddb_DoLookup(p->CDDBID, szTOC, hDlg, Cddb_ResultCallback, + CDDB_NOCACHE | CDDB_MODAL | CDDB_UI_MULTIPLE, (ULONG_PTR)&data); + } + else hr = CDDB_E_BADTOC; + } + break; + case IDC_CDTEXT: + if (DoCDText(p, dialogDevice)) + { + Populate(hDlg); + } + break; + case IDCANCEL: EndDialog(hDlg, 1); break; + case IDOK: + { + int x; + GetDlgItemTextW(hDlg, IDC_TITLE, p->title, TITLE_SIZE); + GetDlgItemTextW(hDlg, IDC_PUBLISHER, p->label, 511); p->label[511] = 0; + GetDlgItemTextW(hDlg, IDC_ARTIST, p->artist, ARTIST_SIZE); + GetDlgItemTextW(hDlg, IDC_GENRE, p->genre, 63); p->genre[63] = 0; + GetDlgItemTextW(hDlg, IDC_YEAR, p->year, 8); + p->compilation = !!IsDlgButtonChecked(hDlg, IDC_COMPILATION); + for (x = 0; x < p->ntracks; x ++) + { + trackList.GetText(x, 0, p->tracks[x].artist, ARTIST_SIZE); + trackList.GetText(x, 1, p->tracks[x].title, TITLE_SIZE); + } + AddToDatabase(p); + } + + EndDialog(hDlg, 0); + break; + case IDC_REMOVE: + RemoveFromDatabase(p); + EndDialog(hDlg, 1); break; + + case IDC_EDITTEXT: + if (HIWORD(wParam) == EN_CHANGE) + { + wchar_t buf[512] = {0}; + int p = trackList.GetNextSelected(-1); + if (p != LB_ERR) + { + GetDlgItemTextW(hDlg, IDC_EDITTEXT, buf, 512); + trackList.SetItemText(p, 1, buf); + } + } + break; + + case IDC_EDITARTIST: + if (HIWORD(wParam) == EN_CHANGE) + { + wchar_t buf[512] = {0}; + int p = trackList.GetNextSelected(-1); + if (p != LB_ERR) + { + GetDlgItemTextW(hDlg, IDC_EDITARTIST, buf, 512); + trackList.SetItemText(p, 0, buf); + + if (!IsDlgButtonChecked(hDlg, IDC_COMPILATION)) + { + wchar_t buf2[512] = {0}; + GetDlgItemTextW(hDlg, IDC_ARTIST, buf2, 512); + if (lstrcmp(buf, buf2)) + CheckDlgButton(hDlg, IDC_COMPILATION, BST_CHECKED); + } + } + } + break; + case IDC_ARTIST: + if (HIWORD(wParam) == EN_CHANGE && !IsDlgButtonChecked(hDlg, IDC_COMPILATION)) + { + wchar_t buf[512] = {0}; + GetDlgItemTextW(hDlg, IDC_ARTIST, buf, 512); + + for (int x = 0; x < p->ntracks; x ++) + { + trackList.SetItemText(x, 0, buf); + } + if (trackList.GetNextSelected(-1) != LB_ERR) + { + SetDlgItemTextW(hDlg, IDC_EDITARTIST, buf); + + } + } + break; + + } + return 0; + } + return 0; +} + +void DBEdit(DINFO *ps, HWND hwnd, int l, char device) +{ + static int i = 0; + if (i) return ; + i = 1; + p = ps; + dialogDevice = device; + _g_disable_cddb = l; + if (!WASABI_API_DIALOGBOX(IDD_EDIT, hwnd, (DLGPROC)EditProc)) + { + PostMessage(line.hMainWindow, WM_USER, (WPARAM)L"cda://", 247 /*IPC_REFRESHPLCACHE*/); + } + i = 0; +} + +#endif +#if 0 + +typedef struct _EDITDATA +{ + DINFO *p; + INT modified; + CHAR cDevice; + +} EDITDATA; + +#define GET_DATA(__hwnd) (EDITDATA*)(LONG_PTR)GetWindowLongPtrW((__hwnd), GWLP_USERDATA); + + +#define SET_IF(hwndDlg, id, data) if (data) SetDlgItemText(hwndDlg, id, data); else SetDlgItemText(hwndDlg, id, L""); +static void Fill(HWND hwndDlg, const DINFO *info) +{ + SET_IF(hwndDlg, IDC_TITLE, info->title); + SET_IF(hwndDlg, IDC_ARTIST, info->artist); + + if (info->discnum) + SetDlgItemInt(hwndDlg, IDC_DISC, info->discnum, FALSE); + else + SetDlgItemText(hwndDlg, IDC_DISCS, L""); + + if (info->numdiscs) + SetDlgItemInt(hwndDlg, IDC_DISCS, info->numdiscs, FALSE); + else + SetDlgItemText(hwndDlg, IDC_DISCS, L""); + + SET_IF(hwndDlg, IDC_YEAR, info->year); + SET_IF(hwndDlg, IDC_LABEL, info->label); + SET_IF(hwndDlg, IDC_NOTES, info->notes); + SET_IF(hwndDlg, IDC_GENRE, info->genre); + + CheckDlgButton(hwndDlg, IDC_COMPILATION, (info->compilation)?BST_CHECKED:BST_UNCHECKED); + SendDlgItemMessage(hwndDlg, IDC_TRACKLIST, LB_RESETCONTENT, 0, 0); + + for (int x = 0; x < info->ntracks; x ++) + { + wchar_t buf[1100] = {0}; + if (!info->tracks[x].title) + StringCchPrintfW(buf, 1100, L"%d.", x+1); + else if (info->tracks[x].artist && info->tracks[x].artist[0] && wcscmp(info->tracks[x].artist, info->artist)) + StringCchPrintfW(buf, 1100, L"%d. %s - %s", x+1, info->tracks[x].artist, info->tracks[x].title); + else + StringCchPrintfW(buf, 1100, L"%d. %s", x+1, info->tracks[x].title); + SendDlgItemMessageW(hwndDlg, IDC_TRACKLIST, LB_ADDSTRING, 0, (LPARAM)buf); + + } +} + +static HRESULT CALLBACK Cddb_LookupCallback(HRESULT result, ICddbDisc *pDisc, DWORD *pdwAutoCloseDelay, ULONG_PTR user) +{ + HWND hwnd = (HWND)user; + EDITDATA *pData = GET_DATA(hwnd); + if (!pData) return E_INVALIDARG; + + if(S_OK == result) + { + pData->modified = 1; + DefaultValues(pData->p); + GetDiscInfo(pDisc, pData->p); + StoreDisc(pData->p->CDDBID, pDisc); + ICddbCacheManager* pCache; + HRESULT hr = Cddb_GetICacheManger((void**)&pCache); + if (SUCCEEDED(hr)) + { + CComBSTR toc; + pDisc->get_Toc(&toc); + hr = pCache->StoreDiscByToc(toc, pDisc); + pCache->Release(); + } + Fill(hwnd, pData->p); + } + else + { + *pdwAutoCloseDelay = AUTOCLOSE_NEVER; + } + CddbCache_SetDisc(pData->p, result); + return S_OK; +} + +static HRESULT CALLBACK Cddb_EditCallback(HRESULT result, ICddbDisc *pDisc, DWORD *pdwAutoCloseDelay, ULONG_PTR user) +{ + HWND hwnd = (HWND)user; + EDITDATA *pData = GET_DATA(hwnd); + if (!pData) return E_INVALIDARG; + + if (FAILED(result)) + { + *pdwAutoCloseDelay = AUTOCLOSE_NEVER; + return S_OK; + } + + if (SUCCEEDED(result)) + { + HRESULT hr(S_OK); + wchar_t szTOC[2048] = {0}; + ICDDBControl *pControl; + CDDBUIFlags uiFlags = UI_EDITMODE; + + if (!Cddb_CalculateTOC(pData->p, szTOC, sizeof(szTOC)/sizeof(wchar_t))) hr = CDDB_E_BADTOC; + if (SUCCEEDED(hr)) hr = Cddb_GetIControl((void**)&pControl); + if (SUCCEEDED(hr)) + { + if (!pDisc) + { + uiFlags = UI_SUBMITNEW; + hr = pControl->GetSubmitDisc(szTOC, 0, 0, &pDisc); + if (FAILED(hr)) pDisc = NULL; + } + else pDisc->AddRef(); + + if (pDisc) + { + Cddb_DisplayDiscInfo(pDisc, &uiFlags, hwnd); + if (uiFlags & UI_DATA_CHANGED) + { + ICddbCacheManager* pCache; + hr = Cddb_GetICacheManger((void**)&pCache); + if (SUCCEEDED(hr)) + { + hr = pCache->StoreDiscByToc(szTOC, pDisc); + pCache->Release(); + } + + pData->modified = 1; + DefaultValues(pData->p); + GetDiscInfo(pDisc, pData->p); + StoreDisc(pData->p->CDDBID, pDisc); + Fill(hwnd, pData->p); + CddbCache_SetDisc(pData->p, S_OK); + } + pDisc->Release(); + } + pControl->Release(); + } + + } + return S_OK; +} + +static LRESULT CALLBACK EditProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + EDITDATA *pData; + + switch (message) + { + case WM_INITDIALOG: + { + pData = (EDITDATA*)lParam; + if (!pData) { EndDialog(hwndDlg, 0); return 0; } + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); + pData->modified = 0; + Fill(hwndDlg, pData->p); + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_CDDB: + { + wchar_t szTOC[2048] = {0}; + pData = GET_DATA(hwndDlg); + + if (pData && Cddb_CalculateTOC(pData->p, szTOC, sizeof(szTOC)/sizeof(wchar_t))) + { + UINT flags = CDDB_NOCACHE | CDDB_UI_MODAL | CDDB_UI_MULTIPLE | CDDB_UI_RESULT_MODAL; + HRESULT hr = Cddb_DoLookup(szTOC, hwndDlg, Cddb_LookupCallback, flags, (ULONG_PTR)hwndDlg); + if (FAILED(hr)) Cddb_DisplayResultDlg(hwndDlg, hr, AUTOCLOSE_NEVER, flags); + } + } + break; + case IDC_EDIT: + { + wchar_t szTOC[2048] = {0}; + pData = GET_DATA(hwndDlg); + + if (pData && Cddb_CalculateTOC(pData->p, szTOC, sizeof(szTOC)/sizeof(wchar_t))) + { + UINT flags = CDDB_UI_MODAL | CDDB_UI_MULTIPLE | CDDB_UI_RESULT_MODAL; + HRESULT hr = Cddb_DoLookup(szTOC, hwndDlg, Cddb_EditCallback, flags, (ULONG_PTR)hwndDlg); + if (FAILED(hr)) Cddb_DisplayResultDlg(hwndDlg, hr, AUTOCLOSE_NEVER, flags); + } + } + break; + case IDCANCEL: + pData = (EDITDATA*)(LONG_PTR)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + EndDialog(hwndDlg, (pData) ? pData->modified : 0); + break; + } + break; + } + return 0; +} +bool CDEdit(CHAR cDevice, DINFO *ps, HWND hwnd) +{ + EDITDATA data; + ZeroMemory(&data, sizeof(EDITDATA)); + data.cDevice = cDevice; + data.p = ps; + + if (WASABI_API_DIALOGBOXPARAM(IDD_EDIT, hwnd, (DLGPROC)EditProc, (LPARAM)&data)) + { + PostMessageW(line.hMainWindow, WM_USER, (WPARAM)L"cda://", IPC_REFRESHPLCACHE); + } + return ( 0 != data.modified); +} +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/ExtendedFileInfo.cpp b/Src/Plugins/Input/in_cdda/ExtendedFileInfo.cpp new file mode 100644 index 00000000..2b620294 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/ExtendedFileInfo.cpp @@ -0,0 +1,1241 @@ +#define SKIP_OVER +#include "main.h" +#include "api__in_cdda.h" + +#include "CDPlay.h" +#include "DAEPlay.h" +#include "MCIPlay.h" +#include "WindacPlay.h" + +#include "cddb.h" +#include "../nu/AutoWide.h" +#include "../nu/AutoChar.h" +#include "../nde/ndestring.h" +#include "../nu/ListView.h" +#ifndef IGNORE_API_GRACENOTE +#include "../primo/obj_primo.h" +#endif +#include "../Winamp/wa_ipc.h" +#include <api/service/waservicefactory.h> +#include <shlwapi.h> +#include <atlbase.h> +#include <strsafe.h> + +#if 0 +BOOL CALLBACK ripConfigProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +#endif + +static int cachedev_used; +static MCIDEVICEID cachedev; +static wchar_t last_fn[8]; +extern "C" __declspec(dllexport) int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, int destlen) +{ + s_last_error = NULL; + if (!_stricmp(data, "type")) + { + lstrcpynW(dest, L"0", destlen); //audio + return 1; + } + else if (!_stricmp(data, "family")) + { + LPCWSTR e, p(NULL); + DWORD lcid; + e = PathFindExtensionW(fn); + if (L'.' != *e) return 0; + e++; + lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); + if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"CDA", -1)) p = WASABI_API_LNGSTRINGW(IDS_FAMILY_STRING); + if (p && S_OK == StringCchCopyW(dest, destlen, p)) return 1; + return 0; + } + else if (!_stricmp(data, "ext_cdda")) + { + lstrcpynW(dest, L"1", destlen); //audio + return 1; + } + + // TODO determine if we even keep any of this... + /* + else if (!_stricmp(data, "cdda_config_text")) + { + WASABI_API_LNGSTRINGW_BUF(IDS_RIPPING,dest,destlen); + return 1; + } + else if (!_strnicmp(data, "cdda_cf_", 8)) + { + HWND parent = (HWND)atoi(data + 8); + dest[0] = 0; + if (parent && IsWindow(parent)) + { + parent = WASABI_API_CREATEDIALOGW(IDD_PREFS_CDRIP, parent, ripConfigProc); + _itow((int)parent, dest, 10); + } + return 1; + } + */ + + if (!lstrcmpiW(PathFindExtensionW(fn), L".cda") && !_wcsnicmp(fn + 1, L":\\track", 7)) // stupid hack, converts x:\\trackXX.cda to cda://x,XX + { + static wchar_t fakebuf[128]; + StringCchPrintf(fakebuf, 128, L"cda://%c,%d", fn[0], _wtoi(PathFindFileNameW(fn) + 5)); + fn = fakebuf; + } + + if (!_wcsnicmp(fn, L"cda://", 6)) // determine length of cd track via MCI + { + int track = lstrlenW(fn) > 8 ? _wtoi(fn + 8) : 0; + int device = fn[6]; + if (device >= 'a' && device <= 'z') device += 'A' -'a'; + MCIDEVICEID dev2 = 0; + + if (cachedev_used) dev2 = cachedev; + + if (!_stricmp(data, "discid") || !_stricmp(data, "cddbid")) + { + dest[0] = 0; +#ifdef DISCID + DiscId *disc = discid_new(); + wchar_t drive[4] = {device, L':'}; + + if (!discid_read_sparse(disc, drive, 0)) { + discid_free(disc); + return 0; + } + + if (!_stricmp(data, "cddbid")) + lstrcpynW(dest, AutoWide(discid_get_freedb_id(disc)), destlen); + else + lstrcpynW(dest, AutoWide(discid_get_id(disc)), destlen); + + discid_free(disc); +#endif + } + else if (!_stricmp(data, "cdengine")) + { + dest[0] = 0; + if (g_cdplay && device && g_cdplay->IsPlaying(device)) + { + if (g_cdplay == daePlayer) + lstrcpynW(dest, L"DAE", destlen); + else if (g_cdplay == windacPlayer) + { + if (hDLL) + lstrcpynW(dest, L"ASPI", destlen); + else + lstrcpynW(dest, L"SPTI", destlen); + } + else if (g_cdplay == mciPlayer) + lstrcpynW(dest, L"MCI", destlen); + } + return 1; + } + else if (!_stricmp(data, "<begin>")) + { + if (!CDOpen(&cachedev, device, L"cache")) + cachedev = 0; + + if (NULL != dest && destlen > 1) + { + dest[0] = (NULL != cachedev) ? L'1' : L'0'; + dest[1] = L'\0'; + } + + cachedev_used = 1; + //OutputDebugString("begin device caching\n"); + } + else if (!_stricmp(data, "<end>")) + { + if (cachedev_used && cachedev) + CDClose(&cachedev); + //OutputDebugString("end device caching\n"); + cachedev_used = 0; + cachedev = 0; + } + else if (!_stricmp(data, "<eject>")) + { + if (!cachedev_used) + { + if (!CDOpen(&dev2, device, L"eject")) + dev2 = 0; + } + if (dev2) + { + CDEject(dev2); + if (dev2 != cachedev) + CDClose(&dev2); + } + } + else if (!_stricmp(data, "ntracks")) + { + if (!cachedev_used) + { + if (!CDOpen(&dev2, device, L"ntracks")) dev2 = 0; + } + if (dev2) + { + _itow(CDGetTracks(dev2), dest, 10); + if (dev2 != cachedev) CDClose(&dev2); + } + else + { + if (NULL != dest && destlen > 1) + { + dest[0] = L'0'; + dest[1] = L'\0'; + } + } + } + else if (!_stricmp(data, "tracktype")) + { + if (!cachedev_used) + { + if (!CDOpen(&dev2, device, L"tracktype")) dev2 = 0; + } + if (dev2) + { + MCI_STATUS_PARMS sMCIStatus; + sMCIStatus.dwItem = MCI_CDA_STATUS_TYPE_TRACK; + sMCIStatus.dwTrack = track; + if (mciSendCommand(dev2, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK | MCI_WAIT, (DWORD_PTR) &sMCIStatus)) + WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN,dest,destlen); + else lstrcpynW(dest, (sMCIStatus.dwReturn != MCI_CDA_TRACK_AUDIO ? L"data" : L"audio"), destlen); + + if (dev2 != cachedev) CDClose(&dev2); + } + } + else if (!_stricmp(data, "length")) + { + if (!cachedev_used) + { + if (!CDOpen(&dev2, device, L"length")) dev2 = 0; + } + if (dev2) + { + MCI_SET_PARMS sMCISet; + sMCISet.dwTimeFormat = MCI_FORMAT_MILLISECONDS; + MCISendCommand(dev2, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)(LPVOID) &sMCISet); + _itow(CDGetTrackLength(dev2, track), dest, 10); + sMCISet.dwTimeFormat = MCI_FORMAT_TMSF; + MCISendCommand(dev2, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)(LPVOID) &sMCISet); + + if (dev2 != cachedev) + CDClose(&dev2); + } + } + else if (!_stricmp(data, "album") || !_stricmp(data, "artist") || + !_stricmp(data, "year") || !_stricmp(data, "genre") || + !_stricmp(data, "title") || !_stricmp(data, "comment") || + !_stricmp(data, "tuid") || !_stricmp(data, "albumartist") || + !_stricmp(data, "publisher") || !_stricmp(data, "disc") || + !_stricmp(data, "conductor") || !_stricmp(data, "composer") || + !_stricmp(data, "remixing") || !_stricmp(data, "isrc") || + !_stricmp(data, "GracenoteFileID") || !_stricmp(data, "GracenoteExtData") + ) + { + int cached = 0; + //cache our myps + static DINFO myps; + /*static unsigned int last_time; + unsigned int now = GetTickCount();*/ + cached = !_wcsnicmp(fn, last_fn, 7); + // TODO disabled as this is causing more access issues than it seems to help... + /*if (cached) + { + if (now > last_time + 1000) cached = 0; + if (now < last_time - 1000) cached = 0; // counter wrapped + }*/ + + if (!cached) + { + lstrcpynW(last_fn, fn, 8); + + memset(&myps, 0, sizeof(myps)); + if (!cachedev_used) + { + if (CDOpen(&dev2, device, L"extinfo")) + { + GetDiscID(dev2, &myps); + CDClose(&dev2); + } + } + else GetDiscID(dev2, &myps); + } + + if (myps.CDDBID) + { + // try to get CDDB information and then revert to CD-Text if CDDB is not available + if (GetCDDBInfo(&myps, device)) + { + wchar_t cache[] = {L"cda://x"}; + cache[6] = device; + PostMessage(line.hMainWindow, WM_WA_IPC, (WPARAM)cache, IPC_REFRESHPLCACHE); + } + else if (!cached && !myps.populated) + { + if (DoCDText(&myps, device)) + { + wchar_t cache[] = {L"cda://x"}; + cache[6] = device; + PostMessage(line.hMainWindow, WM_WA_IPC, (WPARAM)cache, IPC_REFRESHPLCACHE); + } + } + + if (!_stricmp(data, "album")) + { + if (myps.title) + lstrcpynW(dest, myps.title, destlen); + else + dest[0]=0; + } + else if (!_stricmp(data, "artist") && track>0 && track<100) + { + if (myps.tracks[track-1].artist) + lstrcpynW(dest, myps.tracks[track-1].artist, destlen); + else if (myps.artist) + lstrcpynW(dest, myps.artist, destlen); + else + dest[0]=0; + } + else if (!_stricmp(data, "albumartist")) + { + if (myps.artist) + lstrcpynW(dest, myps.artist, destlen); + else if (track == 0) + WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN_ARTIST,dest,destlen); + else + dest[0]=0; + } + else if (!_stricmp(data, "title") && track>0 && track<100) + { + if (myps.tracks[track-1].title) + lstrcpynW(dest, myps.tracks[track-1].title, destlen); + else + StringCchPrintfW(dest, destlen, WASABI_API_LNGSTRINGW(IDS_TRACK_X), track); + } + else if (!_stricmp(data, "year")) + { + if (myps.year) + lstrcpynW(dest, myps.year, destlen); + else + dest[0]=0; + } + else if (!_stricmp(data, "genre")) + { + if (myps.genre) + lstrcpynW(dest, myps.genre, destlen); + else + dest[0]=0; + } + else if (!_stricmp(data, "comment")) + { + if (myps.notes) + lstrcpynW(dest, myps.notes, destlen); + else + dest[0]=0; + } + else if (!_stricmp(data, "publisher")) + { + if (myps.label) + lstrcpynW(dest, myps.label, destlen); + else + dest[0]=0; + } + else if (!_stricmp(data, "composer")) + { + if (track>0 && myps.tracks[track-1].composer) + lstrcpynW(dest, myps.tracks[track-1].composer, destlen); + else if (myps.composer) + lstrcpynW(dest, myps.composer, destlen); + else + dest[0]=0; + } + else if (!_stricmp(data, "remixing")) + { + if (track>0 && myps.tracks[track-1].remixing) + lstrcpynW(dest, myps.tracks[track-1].remixing, destlen); + else if (myps.composer) + lstrcpynW(dest, myps.remixing, destlen); + else + dest[0]=0; + } + else if (!_stricmp(data, "conductor")) + { + if (track>0 && myps.tracks[track-1].conductor) + lstrcpynW(dest, myps.tracks[track-1].conductor, destlen); + else if (myps.conductor) + lstrcpynW(dest, myps.conductor, destlen); + else + dest[0]=0; + } + else if (!_stricmp(data, "isrc") && track>0 && track<100) + { + if (myps.tracks[track-1].isrc) + lstrcpynW(dest, myps.tracks[track-1].isrc, destlen); + else + dest[0]=0; + } + else if (!_stricmp(data, "GracenoteFileID") && track>0 && track<100) + { + if (myps.tracks[track-1].tagID) + lstrcpynW(dest, myps.tracks[track-1].tagID, destlen); + else + dest[0]=0; + } + else if (!_stricmp(data, "GracenoteExtData") && track>0 && track<100) + { + if (myps.tracks[track-1].extData) + lstrcpynW(dest, myps.tracks[track-1].extData, destlen); + else + dest[0]=0; + } + else if (!_stricmp(data, "tuid")) + { + if (myps.tuid) + lstrcpynW(dest, myps.tuid, destlen); + else + dest[0]=0; + } + else if (!_stricmp(data, "disc")) + { + if (myps.numdiscs > 0) + StringCchPrintf(dest, destlen, L"%u/%u", myps.discnum, myps.numdiscs); + else if (myps.discnum) + StringCchPrintf(dest, destlen, L"%u", myps.discnum); + else + dest[0] = 0; + } + else + { +// last_time = GetTickCount(); + return 0;// some error condition (such as track == 0) got us here + } + } + else + { + if (!_stricmp(data, "title") && track>0 && track<100) + { + StringCchPrintfW(dest, destlen, WASABI_API_LNGSTRINGW(IDS_TRACK_X), track); + } + last_fn[0] = 0x00; + } +// last_time = GetTickCount(); + } + else if (!_stricmp(data, "cdtype")) + { + dest[0] = 0; + #ifndef IGNORE_API_GRACENOTE + DWORD retCode = PRIMOSDK_OK + 1; + obj_primo *primo=0; + waServiceFactory *sf = line.service->service_getServiceByGuid(obj_primo::getServiceGuid()); + if (sf) primo = reinterpret_cast<obj_primo *>(sf->getInterface()); + if (primo) + { + DWORD unit = (DWORD)fn[6]; + DWORD mediumtype; + DWORD mediumformat; + DWORD erasable; + DWORD tracks, used, free; + retCode = primo->DiscInfo(&unit, &mediumtype, &mediumformat, &erasable, &tracks, &used, &free); + if (retCode == PRIMOSDK_OK) + { + if (mediumformat > 0xf0 && mediumformat < 0xff) lstrcpynW(dest, L"DVD", destlen); + else lstrcpynW(dest, L"CD", destlen); + if (mediumtype == PRIMOSDK_BLANK || mediumtype == PRIMOSDK_COMPLIANTGOLD) StringCchCatW(dest, destlen, L"R"); + if (erasable) StringCchCatW(dest, destlen, L"W"); + } + sf->releaseInterface(primo); + } + return (retCode == PRIMOSDK_OK); + #else + return 0; + #endif + } + else if (!_stricmp(data, "cdtype2")) + { + dest[0] = 0; + #ifndef IGNORE_API_GRACENOTE + DWORD retCode = PRIMOSDK_OK + 1; + obj_primo *primo=0; + waServiceFactory *sf = line.service->service_getServiceByGuid(obj_primo::getServiceGuid()); + if (sf) primo = reinterpret_cast<obj_primo *>(sf->getInterface()); + if (primo) + { + DWORD unit = (DWORD)fn[6]; + DWORD mediumType; + DWORD mediumFormat; + DWORD erasable; + DWORD tracks, used, free; + DWORD medium = -1, protectedDVD = -1, mediumEx = -1; + DWORD rfu; + retCode = primo->DiscInfoEx(&unit, 1, &mediumType, &mediumFormat, &erasable, &tracks, &used, &free); + if (retCode == PRIMOSDK_OK) + { + primo->DiscInfo2(&unit, &medium, &protectedDVD, NULL, &mediumEx, &rfu); + //format: mediumType;mediumFormat;mediumEx;protectedDVD;erasable;tracks;used;free + StringCchPrintfW(dest, destlen, L"%d;%d;%d;%d;%d;%d;%d;%d", mediumType, mediumFormat, mediumEx, protectedDVD, erasable, tracks, used, free); + } + sf->releaseInterface(primo); + } + return (retCode == PRIMOSDK_OK); + #else + return 0; + #endif + } + else if (!_stricmp(data, "cdlengths")) + { + dest[0] = 0; + #ifndef IGNORE_API_GRACENOTE + obj_primo *primo=0; + waServiceFactory *sf = line.service->service_getServiceByGuid(obj_primo::getServiceGuid()); + if (sf) primo = reinterpret_cast<obj_primo *>(sf->getInterface()); + if (primo) + { + DWORD unit = (DWORD)fn[6]; + DWORD mediumtype; + DWORD mediumformat; + DWORD erasable; + DWORD tracks, used, free; + if (primo->DiscInfo(&unit, &mediumtype, &mediumformat, &erasable, &tracks, &used, &free) == PRIMOSDK_OK) + { + StringCchPrintfW(dest, destlen, L"%d,%d", free, used); + } + sf->releaseInterface(primo); + } + #endif + } + else if (!_stricmp(data, "cdspeeds")) + { + dest[0] = 0; + #ifndef IGNORE_API_GRACENOTE + DWORD retCode = PRIMOSDK_OK + 1; + obj_primo *primo=0; + waServiceFactory *sf = line.service->service_getServiceByGuid(obj_primo::getServiceGuid()); + if (sf) primo = reinterpret_cast<obj_primo *>(sf->getInterface()); + if (primo) + { + DWORD unit = (DWORD)fn[6]; + DWORD cdspeeds[32]; + DWORD dvdspeeds[32]; + DWORD capabilities; + retCode = primo->UnitSpeeds(&unit, (unsigned long *) & cdspeeds, (unsigned long *) & dvdspeeds, &capabilities); + if (retCode == PRIMOSDK_OK) + { + wchar_t *p = dest; + //reading speeds + int i; + for (i = 0;cdspeeds[i] != 0xFFFFFFFF;i++); + i++; + //CD-R speeds + for (;cdspeeds[i] != 0xFFFFFFFF;i++) + { + StringCchPrintfW(p, destlen, L"%d", cdspeeds[i]); + StringCchCatW(p, destlen, L"/"); + p += lstrlenW(p); + } + *p = ';'; + p++; + i++; + //CD-RW speeds + for (;cdspeeds[i] != 0xFFFFFFFF;i++) + { + StringCchPrintfW(p, destlen, L"%d", cdspeeds[i]); + StringCchCatW(p, destlen, L"/"); + p += lstrlenW(p); + } + } + sf->releaseInterface(primo); + } + return (retCode == PRIMOSDK_OK); + #else + return 0; + #endif + } + else if (!_stricmp(data, "cdinfo")) + { + dest[0] = 0; + #ifndef IGNORE_API_GRACENOTE + DWORD retCode = PRIMOSDK_OK + 1; + obj_primo *primo=0; + waServiceFactory *sf = line.service->service_getServiceByGuid(obj_primo::getServiceGuid()); + if (sf) primo = reinterpret_cast<obj_primo *>(sf->getInterface()); + if (primo) + { + DWORD unit = (DWORD)fn[6]; + DWORD type; + BYTE szUnitDescr[64 + 1] = {0,}; // Unit Vendor, Model and FW. version + + retCode = primo->UnitInfo(&unit, &type, szUnitDescr, NULL); + if (retCode == PRIMOSDK_OK) + { + StringCchPrintfW(dest, destlen, L"%d;%s", type, (wchar_t *)AutoWide((char *)szUnitDescr)); + } + sf->releaseInterface(primo); + } + return (retCode == PRIMOSDK_OK); + #else + return 0; + #endif + } + else if (!_stricmp(data, "cdlock")) + { + dest[0] = 0; + #ifndef IGNORE_API_GRACENOTE + if (!m_veritas_handle) + { + waServiceFactory *sf = line.service->service_getServiceByGuid(obj_primo::getServiceGuid()); + if (sf) m_veritas_handle = reinterpret_cast<obj_primo *>(sf->getInterface()); + } + + if (!m_veritas_handle) + return 0; + + m_nblock++; + DWORD unit = (DWORD)fn[6]; + m_veritas_handle->UnitLock(&unit, PRIMOSDK_LOCK); + lstrcpynW(dest, L"locked", destlen); + return 1; + #else + return 0; + #endif + } + else if (!_stricmp(data, "cdunlock")) + { + if (!m_nblock) return 0; + + #ifndef IGNORE_API_GRACENOTE + if (!m_veritas_handle) + { + waServiceFactory *sf = line.service->service_getServiceByGuid(obj_primo::getServiceGuid()); + if (sf) m_veritas_handle = reinterpret_cast<obj_primo *>(sf->getInterface()); + } + + if (!m_veritas_handle) + return 0; + + DWORD unit = (DWORD)fn[6]; + m_veritas_handle->UnitLock(&unit, PRIMOSDK_UNLOCK); + m_nblock--; + lstrcpynW(dest, L"unlocked", destlen); + return 1; + #else + return 0; + #endif + } + else if (!_stricmp(data, "track") && track) + { + StringCchPrintfW(dest, destlen, L"%d", track); + return 1; + } + else if (!_stricmp(data, "bitrate") && track) + { + StringCchPrintfW(dest, destlen, L"%d", 1411/*200*/); + return 1; + } + else + return 0; + + return 1; + } + return 0; +} + +static wchar_t m_eiw_lastdrive; +//#ifndef IGNORE_API_GRACENOTE +static DINFO setInfo; +//#endif + +void ParseIntSlashInt(const wchar_t *string, int *part, int *parts); + +extern "C" __declspec(dllexport) int winampSetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *val) +{ +//#ifndef IGNORE_API_GRACENOTE + s_last_error = NULL; + if (!lstrcmpiW(PathFindExtensionW(fn), L".cda") && !_wcsnicmp(fn + 1, L":\\track", 7)) // stupid hack, converts x:\\trackXX.cda to cda://x,XX + { + static wchar_t fakebuf[128]; + StringCchPrintf(fakebuf, 128, L"cda://%c,%d", fn[0], _wtoi(PathFindFileNameW(fn) + 5)); + fn = fakebuf; + } + + wchar_t drive = 0; + int tracknum = -1; + if (!ParseName(fn, drive, tracknum)) + return 0; + + if (drive < 'A' || drive > 'Z') return 0; + + if (drive != m_eiw_lastdrive) + { + setInfo.Reset(); + setInfo.populated=false; + m_eiw_lastdrive = 0; + MCIDEVICEID dev2 = 0; + if (!CDOpen(&dev2, drive, L"setinfo")) dev2 = 0; + + if (dev2) + { + int ret = GetDiscID(dev2, &setInfo); + CDClose(&dev2); + if (!ret) + { + GetCDDBInfo(&setInfo, drive); + m_eiw_lastdrive = drive; + } + } + } + + if (!m_eiw_lastdrive) + return 0; + + #define DO_TRACK(comparetag, field) if (tracknum > 0 && tracknum < 100 && !_stricmp(data, comparetag)) {\ + ndestring_release(setInfo.tracks[tracknum-1]. ## field);\ + setInfo.tracks[tracknum-1]. ## field=0;\ + if (val && *val) setInfo.tracks[tracknum-1]. ## field=ndestring_wcsdup(val);\ + } + + #define DO_DISC(comparetag, field) if (!_stricmp(data, comparetag)) {\ + ndestring_release(setInfo. ## field);\ + setInfo. ## field=0;\ + if (val && *val) setInfo. ## field=ndestring_wcsdup(val);\ + } + + if (tracknum > 0 && tracknum < 100 && !_stricmp(data, "artist")) + { + if (val && setInfo.tracks[tracknum-1].artist == 0 && setInfo.artist && !_wcsicmp(setInfo.artist, val)) + val=0; + + ndestring_release(setInfo.tracks[tracknum-1].artist); + setInfo.tracks[tracknum-1].artist=0; + if (val && *val) setInfo.tracks[tracknum-1].artist=ndestring_wcsdup(val); + } + else DO_TRACK("title", title) + else DO_TRACK("composer", composer) + else DO_TRACK("conductor", conductor) + else DO_TRACK("GracenoteFileID", tagID) + else DO_TRACK("GracenoteExtData", extData) + else DO_DISC("album", title) + else DO_DISC("albumartist", artist) + else DO_DISC("genre", genre) + else DO_DISC("year", year) + else DO_DISC("comment", notes) + else DO_DISC("publisher", label) + else DO_DISC("tuid", tuid) + else DO_DISC("composer", composer) + else DO_DISC("conductor", conductor) + else if (!_stricmp(data, "disc")) + ParseIntSlashInt(val , &setInfo.discnum, &setInfo.numdiscs); + else +//#endif + return 0; + return 1; +} + +extern "C" __declspec(dllexport) int winampWriteExtendedFileInfo() +{ + s_last_error = NULL; + // write it out + if (m_eiw_lastdrive) + { + //#ifndef IGNORE_API_GRACENOTE + CddbCache_SetDisc(&setInfo, S_OK); + StoreDINFO(setInfo.CDDBID, &setInfo); + m_eiw_lastdrive = 0; + last_fn[0]=0; + if (cachedev_used) + { + CDClose(&cachedev); + cachedev_used=0; + } + return 1; + //#endif + } + return 0; +} + +// return 1 if you want winamp to show it's own file info dialogue, 0 if you want to show your own (via In_Module.InfoBox) +// if returning 1, remember to implement winampGetExtendedFileInfo("formatinformation")! +extern "C" __declspec(dllexport) int winampUseUnifiedFileInfoDlg(const wchar_t * fn) +{ + return 1; +} + +#define SET_IF(hwndDlg, id, data) if (data) SetDlgItemText(hwndDlg, id, data); else SetDlgItemText(hwndDlg, id, L""); +static void Fill(HWND hwndDlg, const DINFO *info) +{ + SET_IF(hwndDlg, IDC_TITLE, info->title); + SET_IF(hwndDlg, IDC_ARTIST, info->artist); + + if (info->discnum) + SetDlgItemInt(hwndDlg, IDC_DISC, info->discnum, FALSE); + else + SetDlgItemText(hwndDlg, IDC_DISCS, L""); + + if (info->numdiscs) + SetDlgItemInt(hwndDlg, IDC_DISCS, info->numdiscs, FALSE); + else + SetDlgItemText(hwndDlg, IDC_DISCS, L""); + + SET_IF(hwndDlg, IDC_YEAR, info->year); + SET_IF(hwndDlg, IDC_LABEL, info->label); + SET_IF(hwndDlg, IDC_NOTES, info->notes); + SET_IF(hwndDlg, IDC_GENRE, info->genre); + + SendDlgItemMessage(hwndDlg, IDC_TRACKLIST, LB_RESETCONTENT, 0, 0); + + #ifndef IGNORE_API_GRACENOTE + for (int x = 0; x < info->ntracks; x ++) + { + wchar_t buf[1100] = {0}; + if (!info->tracks[x].title) + StringCchPrintfW(buf, 1100, L"%d.", x+1); + else if (info->tracks[x].artist && info->tracks[x].artist[0] && wcscmp(info->tracks[x].artist, info->artist)) + StringCchPrintfW(buf, 1100, L"%d. %s - %s", x+1, info->tracks[x].artist, info->tracks[x].title); + else + StringCchPrintfW(buf, 1100, L"%d. %s", x+1, info->tracks[x].title); + SendDlgItemMessageW(hwndDlg, IDC_TRACKLIST, LB_ADDSTRING, 0, (LPARAM)buf); + } + #endif +} + +#ifndef IGNORE_API_GRACENOTE +struct LookupData +{ + LookupData(HWND hwndDlg) + { + hwnd=hwndDlg; + dlgItem=0; + device=0; + tracknum=0; + use=false; + disc=0; + memset(szTOC, 0, sizeof(szTOC)); + } + ~LookupData() + { + if (disc) + disc->Release(); + } + HWND hwnd; + int dlgItem; + wchar_t szTOC[2048]; + char device; + int tracknum; + bool use; + ICddbDisc *disc; + DINFO info; +}; + +static HRESULT CALLBACK Cddb_LookupCallback(HRESULT result, ICddbDisc *pDisc, DWORD *pdwAutoCloseDelay, ULONG_PTR user) +{ + LookupData *data = (LookupData *)user; + + if (S_OK == result) + { + data->info.Reset(); + data->info.populated=false; + if (data->disc) + data->disc->Release(); + data->disc=pDisc; + data->disc->AddRef(); + GetDiscInfo(pDisc, &data->info); + Fill(data->hwnd, &data->info); + + ICddbCacheManager* pCache; + HRESULT hr = Cddb_GetICacheManger((void**)&pCache); + if (SUCCEEDED(hr)) + { + pCache->StoreDiscByToc(data->szTOC, data->disc); + pCache->Release(); + } + } + else + { + *pdwAutoCloseDelay = AUTOCLOSE_NEVER; + } + + return S_OK; +} + +static HRESULT CALLBACK Cddb_EditCallback(HRESULT result, ICddbDisc *pDisc, DWORD *pdwAutoCloseDelay, ULONG_PTR user) +{ + LookupData *data = (LookupData *)user; + + if (FAILED(result)) + { + *pdwAutoCloseDelay = AUTOCLOSE_NEVER; + return S_OK; + } + + if (SUCCEEDED(result)) + { + HRESULT hr(S_OK); + ICDDBControl *pControl; + CDDBUIFlags uiFlags = UI_EDITMODE; + + if (SUCCEEDED(hr)) hr = Cddb_GetIControl((void**)&pControl); + if (SUCCEEDED(hr)) + { + if (!pDisc) + { + uiFlags = UI_SUBMITNEW; + hr = pControl->GetSubmitDisc(data->szTOC, 0, 0, &pDisc); + if (FAILED(hr)) pDisc = NULL; + } + else + pDisc->AddRef(); + + if (pDisc) + { + HWND parent = GetParent(data->hwnd); + Cddb_DisplayDiscInfo(pDisc, &uiFlags, parent); + if (uiFlags & UI_DATA_CHANGED) + { + ICddbCacheManager* pCache; + hr = Cddb_GetICacheManger((void**)&pCache); + if (SUCCEEDED(hr)) + { + pCache->StoreDiscByToc(data->szTOC, pDisc); + pCache->Release(); + } + + data->info.Reset(); + data->info.populated=false; + if (data->disc) + data->disc->Release(); + data->disc=pDisc; + data->disc->AddRef(); + GetDiscInfo(pDisc, &data->info); + Fill(data->hwnd, &data->info); + } + pDisc->Release(); + } + pControl->Release(); + } + } + return S_OK; +} + +bool SubmitEdit(ICddbDisc *pDisc) +{ + //CDDBUIFlags uiFlags = UI_EDITMODE; + ICDDBControl *pControl; + + HRESULT hr = Cddb_GetIControl((void**)&pControl); + if (SUCCEEDED(hr)) + { + + long val; + pControl->SubmitDisc(pDisc, 0, &val); + pControl->Release(); + if (val == 0) + return true; + } + return false; +} +#endif + +#define SEND_DISC(field, val) SendMessage(hwndParent,WM_USER, (WPARAM)field,(LPARAM)(val?val:L"")); +#define SEND_DISC_OR_TRACK(field, disc_val, track_val) { if (track_val) SendMessage(hwndParent,WM_USER, (WPARAM)field,(LPARAM)track_val); else if (disc_val) SendMessage(hwndParent,WM_USER, (WPARAM)field,(LPARAM)disc_val); else SendMessage(hwndParent,WM_USER, (WPARAM)field,(LPARAM)L"");} +#define SEND_TRACK(field, val) SendMessage(hwndParent,WM_USER, (WPARAM)field,(LPARAM)(val?val:L"")); + +#ifndef IGNORE_API_GRACENOTE +static void NotifyParent_MusicID(HWND hwndParent, const LookupData *data) +{ + DINFO disc = data->info; + TRACKINFO dummy; + TRACKINFO &track = data->tracknum?disc.tracks[data->tracknum-1]:dummy; + + SEND_DISC(L"album", disc.title); + SEND_DISC(L"albumartist", disc.artist); + SEND_DISC_OR_TRACK(L"artist", disc.artist, track.artist); + SEND_DISC(L"tuid", disc.tuid); + SEND_DISC(L"year", disc.year); + SEND_DISC(L"genre", disc.genre); + SEND_DISC(L"publisher", disc.label); + SEND_DISC(L"comment", disc.notes); + SEND_DISC_OR_TRACK(L"conductor", disc.conductor, track.conductor); + SEND_DISC_OR_TRACK(L"composer", disc.composer, track.composer); + + wchar_t disc_temp[64] = {0}; + if (disc.numdiscs) + StringCchPrintfW(disc_temp, 64, L"%d/%d", disc.discnum, disc.numdiscs); + else if (disc.discnum) + StringCchPrintfW(disc_temp, 64, L"%d", disc.discnum); + else + disc_temp[0]=0; + SEND_DISC(L"disc", disc_temp); + + SEND_TRACK(L"title", track.title); + SEND_TRACK(L"GracenoteFileID", track.tagID); + SEND_TRACK(L"GracenoteExtData", track.extData); +} + +static INT_PTR CALLBACK MusicID_Proc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_INITDIALOG: + { + LookupData *data = new LookupData(hwndDlg); + wchar_t *filename = (wchar_t *)lParam; + if (ParseName(AutoChar(filename), data->device, data->tracknum)) // TODO: remove AutoChar here, I'm just being lazy + { + MCIDEVICEID d = 0; + if (CDOpen(&d, data->device, L"MusicID_Dlg")) + { + GetDiscID(d, &data->info); + CDClose(&d); + + if (Cddb_CalculateTOC(&data->info, data->szTOC, sizeof(data->szTOC)/sizeof(wchar_t))) + { + ICddbCacheManager *pCache; + if (SUCCEEDED(Cddb_GetICacheManger((void**)&pCache))) + { + if (SUCCEEDED(pCache->FetchDiscByToc(data->szTOC, &data->disc))) + { + GetDiscInfo(data->disc, &data->info); + Fill(hwndDlg, &data->info); + } + pCache->Release(); + } + } + } + } + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)data); + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + LookupData *data = (LookupData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if (data->use && data->disc) + { + StoreDisc(data->info.CDDBID, data->disc); + CddbCache_SetDisc(&data->info, S_OK); + } + } + break; + case IDC_LOOKUP: + { + LookupData *data = (LookupData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if (data) + { + data->dlgItem = LOWORD(wParam); + HWND parent = GetParent(hwndDlg); + UINT flags = CDDB_NOCACHE | CDDB_UI_MODAL | CDDB_UI_MULTIPLE | CDDB_UI_RESULT_MODAL; + HRESULT hr = Cddb_DoLookup(data->szTOC, parent, Cddb_LookupCallback, flags, (ULONG_PTR)data); + if (FAILED(hr)) Cddb_DisplayResultDlg(parent, hr, AUTOCLOSE_NEVER, flags); + } + } + break; + case IDC_USE: + { + LookupData *data = (LookupData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + data->use=true; + NotifyParent_MusicID(GetParent(hwndDlg), data); + } + break; + case IDC_EDIT_GRACENOTE: + { + LookupData *data = (LookupData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + data->dlgItem = LOWORD(wParam); + HWND parent = GetParent(hwndDlg); + UINT flags = CDDB_UI_MODAL | CDDB_UI_MULTIPLE | CDDB_UI_RESULT_MODAL; + HRESULT hr = Cddb_DoLookup(data->szTOC, parent, Cddb_EditCallback, flags, (ULONG_PTR)data); + if (FAILED(hr)) Cddb_DisplayResultDlg(parent, hr, AUTOCLOSE_NEVER, flags); + } + break; + case IDC_SUBMIT: + { + LookupData *data = (LookupData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if (data && data->disc) + SubmitEdit(data->disc); + } + break; + } + break; + case WM_DESTROY: + { + LookupData *data = (LookupData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + delete data; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)0); + } + break; + } + return 0; +} +#endif + +struct CDTextData +{ +public: + CDTextData() + { + device=0; + tracknum=0; + use=false; + } + DINFO info; + wchar_t device; + int tracknum; + bool use; +}; + +static void Send_CDText(HWND hwndParent, const CDTextData *data) +{ + DINFO disc = data->info; + TRACKINFO dummy; + TRACKINFO &track = data->tracknum ? disc.tracks[data->tracknum-1] : dummy; + SEND_DISC(L"album", disc.title); + SEND_DISC(L"albumartist", disc.artist); + SEND_DISC_OR_TRACK(L"artist", disc.artist, track.artist); + SEND_DISC_OR_TRACK(L"composer", disc.composer, track.composer); + SEND_TRACK(L"title", track.title); + SEND_DISC_OR_TRACK(L"genre", disc.genre, track.genre); +} + +static void FillDialog_CDText(HWND hwndDlg, const DINFO &info) +{ + SET_IF(hwndDlg, IDC_ARTIST, info.artist); + SET_IF(hwndDlg, IDC_ALBUM, info.title); + SET_IF(hwndDlg, IDC_COMPOSER, info.composer); + + W_ListView listview(GetDlgItem(hwndDlg, IDC_TRACKS)); + + listview.Clear(); + + for (int i=0;i<info.ntracks;i++) + { + const TRACKINFO &track = info.tracks[i]; + wchar_t num[64] = {0}; + StringCchPrintfW(num, 64, L"%d", i+1); + int index = listview.AppendItem(num, 0); + if (track.artist) + listview.SetItemText(index, 1, track.artist); + if (track.title) + listview.SetItemText(index, 2, track.title); + if (track.genre) + listview.SetItemText(index, 3, track.genre); + if (track.composer) + listview.SetItemText(index, 4, track.composer); + } +} + +static INT_PTR CALLBACK CDText_Proc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_INITDIALOG: + { + W_ListView listview; + listview.setwnd(GetDlgItem(hwndDlg, IDC_TRACKS)); + + listview.AddCol(WASABI_API_LNGSTRINGW(IDS_TRACK), 50); + listview.AddCol(WASABI_API_LNGSTRINGW(IDS_ARTIST), 150); + listview.AddCol(WASABI_API_LNGSTRINGW(IDS_TITLE), 150); + // TODO + listview.AddCol(L"Genre"/*WASABI_API_LNGSTRINGW(IDS_GENRE)*/, 150); + listview.AddCol(WASABI_API_LNGSTRINGW(IDS_COMPOSER), 150); + + CDTextData *data = new CDTextData; + wchar_t *filename = (wchar_t *)lParam; + if (ParseName(filename, data->device, data->tracknum)) + { + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)data); + } + else + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0); + + // this is slow if there's no CD Text + PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_LOOKUP, BN_CLICKED), (LPARAM)GetDlgItem(hwndDlg, IDC_LOOKUP)); + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + CDTextData *data = (CDTextData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if (data && data->use) + StoreCDText(data->info.CDDBID, data->device); + } + break; + case IDC_LOOKUP: + { + CDTextData *data = (CDTextData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if (data && DoCDText(&data->info, data->device)) + { + FillDialog_CDText(hwndDlg, data->info); + } + } + break; + case IDC_USE: + { + CDTextData *data = (CDTextData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if (data) + { + data->use=true; + Send_CDText(GetParent(hwndDlg), data); + } + } + break; + } + break; + case WM_DESTROY: + { + CDTextData *data = (CDTextData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + delete data; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)0); + } + break; + } + return 0; +} + +// should return a child window of 513x271 pixels (341x164 in msvc dlg units), or return NULL for no tab. +// Fill in name (a buffer of namelen characters), this is the title of the tab (defaults to "Advanced"). +// filename will be valid for the life of your window. n is the tab number. This function will first be +// called with n == 0, then n == 1 and so on until you return NULL (so you can add as many tabs as you like). +// The window you return will recieve WM_COMMAND, IDOK/IDCANCEL messages when the user clicks OK or Cancel. +// when the user edits a field which is duplicated in another pane, do a SendMessage(GetParent(hwnd),WM_USER,(WPARAM)L"fieldname",(LPARAM)L"newvalue"); +// this will be broadcast to all panes (including yours) as a WM_USER. +extern "C" __declspec(dllexport) HWND winampAddUnifiedFileInfoPane(int n, const wchar_t * filename, HWND parent, wchar_t *name, size_t namelen) +{ + if (!lstrcmpiW(PathFindExtensionW(filename), L".cda") && !_wcsnicmp(filename + 1, L":\\track", 7)) // stupid hack, converts x:\\trackXX.cda to cda://x,XX + { + static wchar_t fakebuf[128]; + StringCchPrintf(fakebuf, 128, L"cda://%c,%d", filename[0], _wtoi(PathFindFileNameW(filename) + 5)); + filename = fakebuf; + } + + #ifndef IGNORE_API_GRACENOTE + switch (n) + { + case 0: // MusicID + StringCchCopyW(name, namelen, L"MusicID"); // benski> this is purposefully not translatable + return WASABI_API_CREATEDIALOGPARAMW(IDD_MUSICID,parent,MusicID_Proc,(LPARAM)_wcsdup(filename)); + case 1: // CD Text + WASABI_API_LNGSTRINGW_BUF(IDS_CDTEXT,name, namelen); + return WASABI_API_CREATEDIALOGPARAMW(IDD_CDTEXT,parent,CDText_Proc,(LPARAM)_wcsdup(filename)); + default: + return 0; + } + #else + switch (n) + { + case 0: // CD Text + { + //if (DoCDText(0, filename[6])) // this is slow if there's no CD Text + { + WASABI_API_LNGSTRINGW_BUF(IDS_CDTEXT,name, namelen); + return WASABI_API_CREATEDIALOGPARAMW(IDD_CDTEXT,parent,CDText_Proc,(LPARAM)_wcsdup(filename)); + } + return 0; + } + default: + return 0; + } + #endif +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/ExtendedRead.cpp b/Src/Plugins/Input/in_cdda/ExtendedRead.cpp new file mode 100644 index 00000000..007b6a65 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/ExtendedRead.cpp @@ -0,0 +1,145 @@ +#include "main.h" + +#include "CDPlay.h" +#include "DAEPlay.h" +#include "MCIPlay.h" +#include "WindacPlay.h" + +#include "api__in_cdda.h" +#include "workorder.h" +#include "CDDB.h" + +//returns handle!=0 if successful, 0 if error +//size will return the final nb of bytes written to the output, -1 if unknown +//TODO> add output format stuff (srate, nch, bps) +extern "C" __declspec(dllexport) intptr_t winampGetExtendedRead_openW(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) +{ + s_last_error = NULL; + C_CDPlay *wp = NULL; + int ret = 1; + + wchar_t device = 0; + int track = -1; + if (!ParseName(fn, device, track)) + return 0; + + if (playStatus[device].IsRipping() || (g_cdplay && g_cdplay->IsPlaying(device))) + { + wchar_t title[32] = {0}; + MessageBoxW(NULL, WASABI_API_LNGSTRINGW(IDS_CD_CURRENTLY_IN_USE), + WASABI_API_LNGSTRINGW_BUF(IDS_DRIVE_IN_USE,title,32), + MB_ICONWARNING | MB_OK); + return -1; + } + + // Get a ICddbDisc object for MusicID + #ifndef IGNORE_API_GRACENOTE + ICddbDiscPtr pDisc = NULL; + OpenMusicIDWorkOrder(); + if (workorder) + { + MCIDEVICEID submitDev = 0; + if (!CDOpen(&submitDev, device, L"MusicID")) submitDev = 0; + if (submitDev) + { + DINFO info; + ret = GetDiscID(submitDev, &info); + CDClose(&submitDev); + if (ret == 0) + { + wchar_t szTOC[2048] = {0}; + if (Cddb_CalculateTOC(&info, szTOC, sizeof(szTOC)/sizeof(wchar_t))) + Cddb_GetDiscFromCache(szTOC, &pDisc); + } + } + } + + if (config_rip_veritas) + { + VeritasPlay *vp = new VeritasPlay(true); + wp = vp; + ret = wp->open(device, track); + if (ret) + { + delete(wp); + wp = NULL; + } + else if (workorder && pDisc != 0) // see if MusicID is interested + { + void *handle = 0; + workorder->GetSigHandle(&handle, pDisc, track); + vp->submitHandle = handle; + } + } + #endif + + if (ret) + { + DAEPlay *dae = new DAEPlay; + if (dae) + { + wp = dae; + ret = wp->open(device, track); + if (ret) + { + delete(wp); + wp = NULL; + } + } + else + { + wp = NULL; + } + } + + if (ret) + { + wp = new WindacPlay; + if (wp->open(device, track)) + { + delete(wp); + return 0; + } + } + + if (size && wp) + { + double s = (double)wp->getlength() / 1000; + s *= (44100 * 4); + *size = (int)s; + } + if (srate) *srate = 44100; + if (nch) *nch = 2; + if (bps) *bps = 16; + playStatus[device].RippingStarted(); + return (intptr_t)wp; +} + +//returns nb of bytes read. -1 if read error (like CD ejected). if (ret<len), EOF is assumed +extern "C" __declspec(dllexport) int winampGetExtendedRead_getData(intptr_t handle, char *dest, int len, int *killswitch) +{ + s_last_error = NULL; + C_CDPlay *wp = (C_CDPlay*)handle; + return (wp ? wp->read(dest, len, killswitch) : -1); +} + +extern "C" __declspec(dllexport) void winampGetExtendedRead_close(intptr_t handle) +{ + s_last_error = NULL; + C_CDPlay *wp = (C_CDPlay*)handle; + if (wp) + { + playStatus[wp->g_drive].RippingStopped(); + delete wp; + wp = 0; + } +} + +// extended info writing (used by gen_ml to update the local CDDB database after a burn) + +extern "C" __declspec(dllexport) char * winampGetExtendedRead_lasterror() +{ + char * retval = s_last_error; + s_last_error = NULL; + return retval; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/FuncTypedefs.h b/Src/Plugins/Input/in_cdda/FuncTypedefs.h new file mode 100644 index 00000000..e465bc07 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/FuncTypedefs.h @@ -0,0 +1,94 @@ +#ifndef NULLSOFT_FUNCTYPEDEFSH +#define NULLSOFT_FUNCTYPEDEFSH + + +typedef unsigned char * PBYTE; +// typedef UINT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT); + +// we Need all these as a minimum: +// 1 arg +// typedef DWORD (CALLBACK * LPFNDLLFUNC1)(DWORD); +typedef DWORD (CALLBACK * NoArgCallback)(); +// DWORD WINAPI PrimoSDK_End(VOID); + + +typedef DWORD (CALLBACK * OneArgCallback)(DWORD); +/* +DWORD PrimoSDK_Trace(DWORD dwTrace); +DWORD PrimoSDK_CloseImage(DWORD dwHandle); +DWORD PrimoSDK_ReleaseHandle(DWORD dwHandle); +*/ +// typedef DWORD (CALLBACK1a * )(PDWORD); +typedef DWORD (CALLBACK * OneArgCallback2)(PDWORD); +/* +DWORD PrimoSDK_Init(PDWORD pdwRelease); +DWORD PrimoSDK_GetHandle(PDWORD pdwHandle); +*/ +// 2 +typedef DWORD (CALLBACK * TwoArgCallback)(DWORD, PBYTE); +// DWORD PrimoSDK_AddFolder (DWORD dwHandle, PBYTE szFolder); +// typedef DWORD (CALLBACK2a)(DWORD, DWORD); +typedef DWORD (CALLBACK * TwoArgCallback2)(DWORD, DWORD); +// DWORD PrimoSDK_VerifyImage(DWORD dwHandle, DWORD dwSpeed); +typedef DWORD (CALLBACK * TwoArgCallback3)(DWORD, PDWORD); +// 3 +// typedef DWORD (CALLBACK3 ) +typedef DWORD (CALLBACK * ThreeArgCallback)(DWORD, PDWORD, DWORD); + +// DWORD WINAPI PrimoSDK_UnitLock(DWORD dwHandle, PDWORD pdwUnit, DWORD dwFlags); +/* +DWORD PrimoSDK_EraseMedium(DWORD dwHandle, PDWORD pdwUnit, DWORD dwFlags); +DWORD PrimoSDK_UnitAIN(DWORD dwHandle, PDWORD pdwUnit, DWORD dwFlags); +DWORD PrimoSDK_MoveMedium(DWORD dwHandle, PDWORD pdwUnit, DWORD dwFlags); +*/ +// typedef DWORD (CALLBACK3b )(DWORD, PBYTE, PBYTE); +typedef DWORD (CALLBACK * ThreeArgCallback2 )(DWORD, PBYTE, PBYTE); +// DWORD PrimoSDK_AddFile(DWORD dwHandle, PBYTE szFileOnCD, PBYTE szSourceFile); + +typedef DWORD (CALLBACK * ThreeArgCallback3 )(DWORD, DWORD, DWORD); + +// 4 +typedef DWORD (CALLBACK * FourArgCallback)(DWORD, PDWORD, DWORD, PBYTE); +// DWORD PrimoSDK_UnitVxBlock(DWORD dwHandle, PDWORD pdwUnit, DWORD dwFlags, PBYTE szAppName); +typedef DWORD (CALLBACK * FourArgCallback2 )( DWORD, DWORD, DWORD, PDWORD); +// DWORD PrimoSDK_WriteImage(DWORD dwHandle, DWORD dwFlags, DWORD dwSpeed, PDWORD pdwSize); +// 5 +typedef DWORD (CALLBACK * FourArgCallback3 )( DWORD, DWORD, PDWORD, PDWORD); +// DWORD WINAPI PrimoSDK_RunningStatus(DWORD dwHandle, DWORD dwFlags,PDWORD pdwCurSector, PDWORD pdwTotSector); + +typedef DWORD (CALLBACK *FourArgCallback4)( DWORD, PBYTE, DWORD, PDWORD); + +// typedef DWORD (CALLBACK5 )( DWORD, PDWORD, PDWORD, PBYTE, PDWORD); +typedef DWORD (CALLBACK * FiveArgCallback)( DWORD, PDWORD, PDWORD, PBYTE, PDWORD); +// DWORD PrimoSDK_UnitInfo(DWORD dwHandle, PDWORD pdwUnit, PDWORD pdwType, PBYTE szDescr, PDWORD pdwReady); + +//typedef DWORD (CALLBACK5b )( DWORD, PDWORD, PDWORD, PDWORD, PDWORD); +typedef DWORD (CALLBACK * FiveArgCallback2)( DWORD, PDWORD, PDWORD, PDWORD, PDWORD); +// DWORD PrimoSDK_UnitSpeeds(DWORD dwHandle, PDWORD pdwUnit, PDWORD pdwCDSpeeds, PDWORD pdwDVDSpeeds, PDWORD pdwCapabilities); +typedef DWORD (CALLBACK * FiveArgCallback3 )( DWORD, PBYTE, DWORD, PDWORD, PDWORD); +// DWORD WINAPI PrimoSDK_NextExtractAudioBuffer(DWORD, PBYTE, DWORD, PDWORD, PDWORD); +// 6 +//typedef DWORD (CALLBACK6 ) (DWORD, PDWORD, PDWORD, PDWORD, PDWORD, PDWORD); +typedef DWORD (CALLBACK * SixArgCallback) (DWORD, PDWORD, PDWORD, PDWORD, PDWORD, PDWORD); +// DWORD PrimoSDK_UnitInfo2(DWORD dwHandle, PDWORD pdwUnit, PDWORD pdwTypes, PDWORD pdwClass, PDWORD pdwBusType, PDWORD pdwRFU); +// 7 +// typedef DWORD (CALLBACK7 )( DWORD, PDWORD, PBYTE, DWORD, DWORD, DWORD, PBYTE); +typedef DWORD (CALLBACK * SevenArgCallback)( DWORD, PDWORD, PBYTE, DWORD, DWORD, DWORD, PBYTE); +// DWORD PrimoSDK_NewImage(DWORD dwHandle, PDWORD pdwUnits, PBYTE szVolumeName, DWORD dwTrackToLoad, DWORD dwFlags, DWORD dwSwapThreshold, PBYTE szTemp); +typedef DWORD (CALLBACK * SevenArgCallback2)( DWORD, DWORD, PDWORD, PDWORD, PDWORD, PDWORD, PDWORD); +// DWORD WINAPI PrimoSDK_TrackInfo(DWORD, DWORD, PDWORD, PDWORD, PDWORD, PDWORD, PDWORD); +typedef DWORD (CALLBACK * SevenArgCallback3)( DWORD, PDWORD, PDWORD, PDWORD, PDWORD, PDWORD, PDWORD); +// DWORD WINAPI PrimoSDK_DiscInfo2(DWORD, PDWORD, PDWORD, PDWORD, PDWORD, PDWORD, PDWORD); + +// 8 +//typedef DWORD (CALLBACK8 )( DWORD, PDWORD, PDWORD, PDWORD, PDWORD, PDWORD, PDWORD, PDWORD); +typedef DWORD (CALLBACK * EightArgCallback )( DWORD, PDWORD, PDWORD, PDWORD, PDWORD, PDWORD, PDWORD, PDWORD); +// DWORD PrimoSDK_DiscInfo(DWORD dwHandle, PDWORD pdwUnit, PDWORD pdwMediumType, PDWORD pdwMediumFormat, PDWORD pdwErasable, PDWORD pdwTracks, PDWORD pdwUsed, PDWORD pdwFree); + +typedef DWORD (CALLBACK * EightArgCallback2 )(DWORD, PDWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD); +// DWORD WINAPI PrimoSDK_ExtractAudioToBuffer(DWORD, PDWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD); + +typedef DWORD (CALLBACK * EightArgCallback3)(DWORD, PDWORD, PBYTE, PBYTE, PBYTE, PBYTE, PBYTE, PBYTE); + +typedef DWORD (CALLBACK * NineArgCallback)(DWORD,PDWORD,DWORD,PDWORD,PDWORD,PDWORD,PDWORD,PDWORD,PDWORD); +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/MAIN.H b/Src/Plugins/Input/in_cdda/MAIN.H new file mode 100644 index 00000000..0d5dd767 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/MAIN.H @@ -0,0 +1,81 @@ +#ifndef NULLSOFT_MAINH +#define NULLSOFT_MAINH +#include <windows.h> +#include <stdio.h> +#include "../Winamp/in2.h" +#include "audio.h" +#include "resource.h" +#include "PlayStatus.h" +#include "../nu/AutoLock.h" +#include <mmsystem.h> + +#define WM_WA_MPEG_EOF WM_USER+2 + +extern In_Module line; + +extern volatile int done; +extern int paused; +extern int g_lastpos; +extern int m_nblock; + +extern DWORD MainThreadId; + +void config(HWND hwndParent); +void config_read(); +//extern int config_sample; + +//extern int config_rip_buffersize; // number of sectors to read at once when ripping +//extern int config_rip_buffers; // number of buffers to use when ripping +//extern int config_play_buffersize; // number of sectors to read at once when playing +//extern int config_play_buffers; // number of buffers to use when playing + +//extern int config_maxextractspeed; + +// TODO review this for the DAE mode... +//extern int config_offset; // number of samples of offset when ripping (like EAC) +//extern int config_read_leadin; + +extern int g_playlength; +extern int g_playtrack; + +void WaitForEvent(HANDLE hEvent, DWORD msMaxWaitTime); +MCIERROR MCISendCommand(MCIDEVICEID IDDevice, UINT uMsg, DWORD fdwCommand, DWORD_PTR dwParam); +int isMediaPresent(MCIDEVICEID wDeviceID); +void CDClose(MCIDEVICEID* lpDeviceID); +BOOL CDOpen(MCIDEVICEID* lpDeviceID, int device, const wchar_t *alias = 0); +BOOL CDID(MCIDEVICEID wDeviceID, wchar_t *id, size_t len); +void CDClose(MCIDEVICEID* lpDeviceID); +unsigned int CDGetTracks(MCIDEVICEID wDeviceID); +unsigned int CDGetCurrTrack(MCIDEVICEID wDeviceID); +int CDPlay(MCIDEVICEID wDeviceID, unsigned int nTrack, BOOL bResume, unsigned int nMin, unsigned int nSec, unsigned int endms); +void CDStop(MCIDEVICEID wDeviceID); +void CDEject(MCIDEVICEID wDeviceID); +void CDPause(MCIDEVICEID wDeviceID); +unsigned int CDGetTrackLength(MCIDEVICEID wDeviceID, + unsigned int nTrack); + +int getSCSIIDFromDrive(char driveletter, int *host, int *id, int *lun); +extern char * s_last_error; +void CloseTables(); + +const char *ReadLine(const char *input, char *output, size_t size, int codepage); + +bool ParseName(const wchar_t *fn, wchar_t &device, int &trackNum); + +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = + { 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } }; + +class C_CDPlay; +class WindacPlay; +class DAEPlay; +class MciPlay; + +extern C_CDPlay *g_cdplay; +extern WindacPlay *windacPlayer; +extern DAEPlay *daePlayer; +extern MciPlay *mciPlayer; + +extern int a_v, a_p; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/MCI.Cpp b/Src/Plugins/Input/in_cdda/MCI.Cpp new file mode 100644 index 00000000..26132b12 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/MCI.Cpp @@ -0,0 +1,152 @@ +#include "main.h" + +MCIERROR MCISendCommand(MCIDEVICEID IDDevice, UINT uMsg, DWORD fdwCommand, DWORD_PTR dwParam) +{ + MCIERROR nErr = mciSendCommand(IDDevice, uMsg, fdwCommand, dwParam); +#if 0 + if (nErr) + { + wchar_t szError[256] = {0}; + mciGetErrorString(nErr, szError, 256); + MessageBox(NULL,szError,L"MCI CD Error",MB_OK); + } +#endif + return nErr; +} + +int isMediaPresent(MCIDEVICEID wDeviceID) +{ + MCI_STATUS_PARMS p; + p.dwItem=MCI_STATUS_MEDIA_PRESENT; + if (MCISendCommand(wDeviceID,MCI_STATUS,MCI_STATUS_ITEM,(DWORD_PTR)&p)) return 1; + return p.dwReturn?1:0; +} + +BOOL CDOpen(MCIDEVICEID* lpDeviceID, int device, const wchar_t *alias) +{ + //OutputDebugString("Calling cdopen()\n"); + MCI_OPEN_PARMS sMCIOpen; + MCI_SET_PARMS sMCISet; + wchar_t zDevice[3]={(wchar_t)device,':',0}; + DWORD nErr; + + sMCIOpen.dwCallback = 0; + sMCIOpen.wDeviceID = 0; + sMCIOpen.lpstrAlias = alias; + sMCIOpen.lpstrDeviceType = (LPCTSTR) MCI_DEVTYPE_CD_AUDIO; + sMCIOpen.lpstrElementName = zDevice; + nErr = MCISendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_SHAREABLE | + MCI_OPEN_TYPE_ID | MCI_OPEN_ELEMENT | + (alias ? MCI_OPEN_ALIAS : 0), (DWORD_PTR) &sMCIOpen); + if (nErr) + { + nErr = MCISendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | + MCI_OPEN_TYPE_ID | MCI_OPEN_ELEMENT | + (alias ? MCI_OPEN_ALIAS : 0), (DWORD_PTR) &sMCIOpen); + if (nErr) + { + return FALSE; + } + } + + sMCISet.dwTimeFormat = MCI_FORMAT_TMSF; + MCISendCommand (sMCIOpen.wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR) &sMCISet); + + *lpDeviceID = sMCIOpen.wDeviceID; + + return TRUE; +} + +void CDClose(MCIDEVICEID* lpDeviceID) +{ + MCI_GENERIC_PARMS sMCIGeneric; + + sMCIGeneric.dwCallback = (DWORD_PTR) line.hMainWindow; + MCISendCommand(*lpDeviceID, MCI_CLOSE, MCI_WAIT, (DWORD_PTR) &sMCIGeneric); + + *lpDeviceID = 0; +} + +void CDEject(MCIDEVICEID wDeviceID) +{ + MCI_SET_PARMS mciset; + MCISendCommand (wDeviceID, MCI_SET, MCI_SET_DOOR_OPEN,(DWORD_PTR) &mciset); +} + +unsigned int CDGetTracks(MCIDEVICEID wDeviceID) +{ + MCI_STATUS_PARMS sMCIStatus; + sMCIStatus.dwItem = MCI_STATUS_NUMBER_OF_TRACKS; + if (MCISendCommand (wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT,(DWORD_PTR) &sMCIStatus)) return -1; + return sMCIStatus.dwReturn; +} + +unsigned int CDGetCurrTrack(MCIDEVICEID wDeviceID) +{ + MCI_STATUS_PARMS sMCIStatus; + + sMCIStatus.dwItem = MCI_STATUS_POSITION; + MCISendCommand (wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT,(DWORD_PTR) &sMCIStatus); + + return ((int) MCI_TMSF_TRACK (sMCIStatus.dwReturn)); +} + +int CDPlay(MCIDEVICEID wDeviceID, unsigned int nTrack, BOOL bResume, unsigned int nMin, unsigned int nSec, unsigned int endms) +{ + MCI_PLAY_PARMS sMCIPlay; + unsigned int nActualTrack = nTrack; + + sMCIPlay.dwFrom = MCI_MAKE_TMSF (nActualTrack, nMin, nSec, 0); + sMCIPlay.dwTo = MCI_MAKE_TMSF (nActualTrack, endms/60000, (endms/1000)%60,0); + if (!bResume) + { + return MCISendCommand (wDeviceID, MCI_PLAY, MCI_FROM | MCI_TO ,(DWORD_PTR) &sMCIPlay); + } + else + { + return MCISendCommand (wDeviceID, MCI_PLAY, MCI_FROM | MCI_TO, (DWORD_PTR) (LPVOID) &sMCIPlay); + } +} + +void CDStop(MCIDEVICEID wDeviceID) +{ + MCISendCommand(wDeviceID, MCI_STOP, 0, 0); +} + +void CDPause(MCIDEVICEID wDeviceID) +{ + MCISendCommand(wDeviceID, MCI_PAUSE, 0,0); +} + +unsigned int CDGetTrackLength(MCIDEVICEID wDeviceID, unsigned int nTrack) +{ + MCI_STATUS_PARMS sMCIStatus; + int r; + + sMCIStatus.dwItem = MCI_STATUS_POSITION ; + sMCIStatus.dwTrack = nTrack+1; + if (MCISendCommand (wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK | MCI_WAIT,(DWORD_PTR) &sMCIStatus)) // if error than last track + { + sMCIStatus.dwItem = MCI_STATUS_LENGTH; + sMCIStatus.dwTrack = nTrack; + if (MCISendCommand (wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK | MCI_WAIT,(DWORD_PTR) &sMCIStatus)) return -1000; + return sMCIStatus.dwReturn; + } + + r=sMCIStatus.dwReturn; + sMCIStatus.dwItem = MCI_STATUS_POSITION ; + sMCIStatus.dwTrack = nTrack; + if (MCISendCommand (wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK | MCI_WAIT,(DWORD_PTR) &sMCIStatus)) return -1000; + return r-sMCIStatus.dwReturn; +} + +BOOL CDID(MCIDEVICEID wDeviceID, wchar_t *id, size_t len) +{ + MCI_INFO_PARMS sMCIInfo; + sMCIInfo.dwCallback=0; + sMCIInfo.dwRetSize=len; + sMCIInfo.lpstrReturn=id; + DWORD nErr= MCISendCommand(wDeviceID, MCI_INFO, MCI_INFO_MEDIA_IDENTITY, (DWORD_PTR)&sMCIInfo); + + return !nErr; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/MCIPlay.h b/Src/Plugins/Input/in_cdda/MCIPlay.h new file mode 100644 index 00000000..fe8e43ad --- /dev/null +++ b/Src/Plugins/Input/in_cdda/MCIPlay.h @@ -0,0 +1,182 @@ +#ifndef NULLSOFT_MCIPLAYH +#define NULLSOFT_MCIPLAYH + +#include "CDPlay.h" +#include "main.h" + +class MciPlay : public C_CDPlay { +public: + MciPlay() + { + posinms=0; + } + + int play(wchar_t drive, int track) + { + g_drive = drive; + + { + MCI_SET_PARMS sMCISet; + g_playtrack = track; + + if (!CDOpen(&playdev, drive, L"mci")) + { + playdev=0; + g_drive = 0; + return 1; + } + sMCISet.dwTimeFormat = MCI_FORMAT_MILLISECONDS; + MCISendCommand (playdev, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR) (LPVOID) &sMCISet); + g_playlength=CDGetTrackLength(playdev,g_playtrack); + sMCISet.dwTimeFormat = MCI_FORMAT_TMSF; + MCISendCommand (playdev, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR) (LPVOID) &sMCISet); + if (CDPlay(playdev,g_playtrack,FALSE,0,0,g_playlength)) + { + CDClose(&playdev); + playdev=0; + g_drive = 0; + return 1; + } + line.is_seekable=1; + } + line.SetInfo(1411,44,2,1); + line.SAVSAInit(0,44100); + line.VSASetInfo(2,44100); + setvolume(a_v, a_p); + { + short dta[576*2] = {0}; + line.VSAAddPCMData(dta,2,16,0); + line.SAAddPCMData(dta,2,16,0); + } + if (audioInit(/*config_sample*/)) + { + } + return 0; + } + + void stop() + { + //CDStop(playdev); + if (!done) CDPause(playdev); + CDClose(&playdev); + audioQuit(); + } + + void pause() + { + posinms=audioGetPos(); + CDPause(playdev); + audioPause(1); + } + + void unpause() + { + int pos=audioGetPos(); + CDPlay(playdev, g_playtrack, TRUE,pos/60000,(pos/1000)%60,g_playlength); + audioPause(0); + } + + int getlength() + { + return g_playlength; + } + + int getoutputtime() + { + int p; + if (paused) + { + return posinms; + } + p=audioGetPos(); + if (GetCurrentThreadId()==MainThreadId) + { + if ((p > g_playlength || (p-g_lastpos > 2000 && !isMediaPresent(playdev))) && !done) + { + done=1; + PostMessage(line.hMainWindow,WM_WA_MPEG_EOF,0,0); + } + if (g_lastpos > p+2000) + g_lastpos=p; + } + return p; + } + + void setoutputtime(int time_in_ms) + { + CDPlay(playdev,g_playtrack,FALSE,time_in_ms/60000,(time_in_ms/1000)%60,g_playlength); + if (paused) + { + audioPause(0); + } + audioSetPos(time_in_ms); + g_lastpos=time_in_ms; + if (paused) + { + audioPause(1); + CDPause(playdev); + } + done=0; + } + + void setvolume(int _a_v, int _a_p) { + HMIXER hmix; + int vol1, vol2; + if (_a_v < 0) _a_v = 0; + if (_a_v > 255) _a_v = 255; + if (_a_p < -127) _a_p = -127; + if (_a_p > 127) _a_p = 127; + vol1 = vol2 = (_a_v*2048) / 255; + if (_a_p > 0) + { + vol1 *= (127-_a_p); + vol1 /= 127; + } + else if (_a_p < 0) + { + vol2 *= (_a_p+127); + vol2 /= 127; + } + + if (mixerOpen(&hmix,0,0,0,0) == MMSYSERR_NOERROR) + { + MIXERLINE ml={sizeof(ml),0}; + ml.dwComponentType=MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC; + if (mixerGetLineInfo((HMIXEROBJ)hmix,&ml,MIXER_GETLINEINFOF_COMPONENTTYPE) == MMSYSERR_NOERROR) + { + MIXERLINECONTROLS mlc = {sizeof(mlc),ml.dwLineID,}; + MIXERCONTROL mc={sizeof(mc),}; + mlc.cControls=1; + mlc.cbmxctrl=sizeof(mc); + mlc.pamxctrl=&mc; + mlc.dwControlType=MIXERCONTROL_CONTROLTYPE_VOLUME; + if (mixerGetLineControls((HMIXEROBJ)hmix,&mlc,MIXER_GETLINECONTROLSF_ONEBYTYPE) == MMSYSERR_NOERROR) + { + MIXERCONTROLDETAILS mcd = {sizeof(mcd),mc.dwControlID,ml.cChannels,}; + MIXERCONTROLDETAILS_UNSIGNED v[2]; + mcd.cbDetails=sizeof(MIXERCONTROLDETAILS_UNSIGNED); + mcd.paDetails=v; + + v[0].dwValue=mc.Bounds.dwMinimum + (vol1*(mc.Bounds.dwMaximum-mc.Bounds.dwMinimum))/2048; + v[1].dwValue=mc.Bounds.dwMinimum + (vol2*(mc.Bounds.dwMaximum-mc.Bounds.dwMinimum))/2048; + + if (mixerSetControlDetails((HMIXEROBJ)hmix,&mcd,0) == MMSYSERR_NOERROR) + { + // yay we're done! + } + // else MessageBox(NULL,"mixerSetLineControlDetails()","Error",MB_OK); + } + //else MessageBox(NULL,"mixerGetLineControls()","Error",MB_OK); + } +// else MessageBox(NULL,"mixerGetLineInfo()","Error",MB_OK); + mixerClose(hmix); + } + //else MessageBox(NULL,"MixerOpen()","Error",MB_OK); + } + +private: + int posinms; + MCIDEVICEID playdev; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/Main.cpp b/Src/Plugins/Input/in_cdda/Main.cpp new file mode 100644 index 00000000..c867a660 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/Main.cpp @@ -0,0 +1,598 @@ +//#define PLUGIN_NAME "Nullsoft CD Plug-in" +#define PLUGIN_VERSION L"4.7" + +#include "main.h" +#include "cddb.h" + +#include "CDPlay.h" +#include "DAEPlay.h" +#include "MCIPlay.h" +#include "WindacPlay.h" + +#include "PlayStatus.h" +#include "../nu/AutoWide.h" +#include "../nu/AutoChar.h" +#include "../Winamp/strutil.h" +#include "../winamp/wa_ipc.h" +#include <shlwapi.h> +#include "api__in_cdda.h" +#include "workorder.h" +#include <strsafe.h> +using namespace Nullsoft::Utility; +Nullsoft::Utility::LockGuard *playDeviceGuard = 0; +char * s_last_error = NULL; + +static wchar_t playDriveLetter; +//extern int config_maxextractspeed; +api_config *AGAVE_API_CONFIG=0; + +#ifndef _DEBUG +BOOL WINAPI _DllMainCRTStartup(HINSTANCE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) +{ + DisableThreadLibraryCalls(hInst); + return TRUE; +} +#endif + +#ifdef IGNORE_API_GRACENOTE +static DINFO g_ps; +#endif +int g_ps_inuse = 0; +int g_playtrack, g_playlength; +wchar_t lastfn[1024] = {0}; +int paused; +void _setvolume(); +DWORD MainThreadId; + +extern char INI_FILE[]; +extern char app_name[]; + +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_CD_PLUGIN_OLD,text,1024); + StringCchPrintf(message, 1024, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), + line.description, TEXT(__DATE__)); + DoAboutMessageBox(hwndParent,text,message); +} + +void quit(); +int init(); + +int isourfile(const in_char *fn) +{ + if (!_wcsnicmp(fn, L"cda://", 6)) return 1; + return 0; +} + +volatile int done = 0; + +int g_lastpos = 0; +C_CDPlay *g_cdplay = 0; + +const wchar_t *filename(const wchar_t *fn) +{ + const wchar_t *s = scanstr_backcW(fn, L"\\/", 0); + if (!s) return fn; + return (s + 1); +} + +bool ParseName(const wchar_t *fn, wchar_t &device, int &trackNum) +{ + wchar_t track[16] = L"1"; + if (!_wcsnicmp(fn, L"cda://", 6)) + { + fn += 6; + wchar_t d[16] = {0}; + wchar_t *p = d; + while (fn && *fn && *fn != L',' && (p - d < 15)) *p++ = *fn++; + if (p) *p = 0; + device = toupper(d[0]); + if (*fn == L',') fn++; + lstrcpyn(track, fn, ARRAYSIZE(track)); + trackNum = _wtoi(track); + return true; + } + else if (!_wcsicmp(extensionW(fn), L"cda")) + { + const wchar_t *f = filename(fn); + if (!_wcsnicmp(f, L"track", 5)) f += 5; + wchar_t t[16] = {0}; + wchar_t *p = t; + while (f && *f && *f != L'.' && (p - t < 15)) *p++ = *f++; + lstrcpyn(track, t, ARRAYSIZE(track)); + device = toupper(fn[0]); + trackNum = _wtoi(track); + return true; + } + return false; +} + +WindacPlay *windacPlayer = 0; +DAEPlay *daePlayer = 0; +MciPlay *mciPlayer = 0; + +int play(const in_char *fn) +{ + done = 0; + lstrcpyn(lastfn, fn, 1024); + line.is_seekable = 0; + g_lastpos = 0; + + int track = -1; + if (!ParseName(fn, playDriveLetter, track)) + return 1; + + if (playStatus[playDriveLetter].IsRipping() || (g_cdplay && g_cdplay->IsPlaying(playDriveLetter))) + { + wchar_t title[32] = {0}; + MessageBoxW(NULL, WASABI_API_LNGSTRINGW(IDS_CD_CURRENTLY_IN_USE), + WASABI_API_LNGSTRINGW_BUF(IDS_DRIVE_IN_USE,title,32), MB_OK); + return 1; + } + + if (g_cdplay) delete g_cdplay; g_cdplay = NULL; + + //first, try DAE + if (!daePlayer) daePlayer = new DAEPlay; + g_cdplay = daePlayer; + + int ret = (g_cdplay ? g_cdplay->play(playDriveLetter, track) : 0); + if (ret != 0) + { + if (g_cdplay) delete g_cdplay; g_cdplay = daePlayer = NULL; + + //second, try Windac + if (!windacPlayer) windacPlayer = new WindacPlay; + g_cdplay = windacPlayer; + ret = (g_cdplay ? g_cdplay->play(playDriveLetter, track) : 0); + if (ret != 0) + { + if (g_cdplay) delete g_cdplay; g_cdplay = windacPlayer = NULL; + + //try MCI + if (!mciPlayer) mciPlayer = new MciPlay; + g_cdplay = mciPlayer; + int ret = (g_cdplay ? g_cdplay->play(playDriveLetter, track) : 1); + if (ret != 0) + { + //no luck + if (g_cdplay) delete g_cdplay; g_cdplay = mciPlayer = NULL; + return ret; + } + } + } + paused = 0; + return 0; +} + + +void pause() +{ + if (g_cdplay) g_cdplay->pause(); + paused = 1; +} +void unpause() +{ + if (g_cdplay) g_cdplay->unpause(); + paused = 0; +} +int ispaused() +{ + return paused; +} + +void stop() +{ + if (g_cdplay) + { + g_cdplay->stop(); + g_cdplay = NULL; + } + + done = 0; + line.SAVSADeInit(); +} + +int getlength() +{ + if (g_cdplay) return g_cdplay->getlength(); + return -1000; +} + +int getoutputtime() +{ + if (g_cdplay) return g_cdplay->getoutputtime(); + return 0; + //return audioGetPos(); +} +void setoutputtime(int time_in_ms) +{ + if (g_cdplay) g_cdplay->setoutputtime(time_in_ms); +} + +void setvolume(int volume) +{ + if (volume != -666) + { + a_v = volume; + } + if (g_cdplay) g_cdplay->setvolume(a_v, a_p); +} + +void setpan(int pan) +{ + a_p = pan; + if (g_cdplay) g_cdplay->setvolume(a_v, a_p); +} + +int infoDlg(const in_char *fn, HWND hwnd) +{ + return 0; +#if 0 // switched to unified file info dialog in 5.53 + if (!_stricmp(extension(fn), "cda") || !_strnicmp(fn, "cda://", 6)) + { + if (!g_ps_inuse) + { + char device; + int res=1; + MCIDEVICEID d = 0; + g_ps_inuse = 1; + device = fn[_strnicmp(fn, "cda://", 6) ? 0 : 6]; + if (device >= 'a' && device <= 'z') device += 'A' -'a'; + + CDOpen(&d, device, L"infoDlg"); + memset(&g_ps, 0, sizeof(g_ps)); + res = GetDiscID(d, &g_ps); + CDClose(&d); + if (!res) + res = GetCDDBInfo(&g_ps, device); + + //if (!res) DBEdit(&g_ps, hwnd, 0, device); + //if (!res) + { + if (CDEdit(device, &g_ps, hwnd)) + { + g_ps_inuse = 0; + return INFOBOX_EDITED; + } + } + g_ps_inuse = 0; + } + } + return INFOBOX_UNCHANGED; +#endif +} + + +void getfileinfo(const in_char *filename, in_char *title, int *length_in_ms) +{ +#if 0 + int track; + char device; + MCIDEVICEID dev2 = 0; + if (length_in_ms) *length_in_ms = -1000; + + if (!filename || !*filename) // currently playing + { + if (!_stricmp(extension(lastfn), "cda") || !_strnicmp(lastfn, "cda://", 6)) + { + #ifdef IGNORE_API_GRACENOTE + if (title) + { + lstrcpynA(title, "CD Track", GETFILEINFO_TITLE_LENGTH); + if (!g_ps_inuse) + { + g_ps_inuse = 1; + memset(&g_ps, 0, sizeof(g_ps)); + + if (CDOpen(&dev2, playDriveLetter, L"getfileinfo")) + { + wchar_t wtitle[256] = {0}; + int ret = GetDiscID(dev2, &g_ps); + CDClose(&dev2); + if (!ret && GetCDDBInfo(&g_ps, 0)) // TODO: get device letter + PostMessage(line.hMainWindow, WM_USER, (WPARAM) L"cda://", 247/*IPC_REFRESHPLCACHE*/); + if (wtitle[0]) + lstrcpynA(title, AutoChar(wtitle), GETFILEINFO_TITLE_LENGTH); + + } + g_ps_inuse = 0; + } + } + if (length_in_ms) *length_in_ms = g_playlength; + #endif + } + + return ; + } + + if (title) + { + const char *p = filename + strlen(filename); + while (p >= filename && *p != '\\') p--; + lstrcpynA(title, ++p, GETFILEINFO_TITLE_LENGTH); + } + track = 0; + + if (!_strnicmp(filename, "cda://", 6)) // determine length of cd track via MCI + { + track = atoi(filename + 8); + device = filename[6]; + if (device >= 'a' && device <= 'z') device += 'A' -'a'; + + if (length_in_ms) + { + if (CDOpen(&dev2, device, L"getfileinfo")) + { + MCI_SET_PARMS sMCISet; + sMCISet.dwTimeFormat = MCI_FORMAT_MILLISECONDS; + MCISendCommand(dev2, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)(LPVOID) &sMCISet); + *length_in_ms = CDGetTrackLength(dev2, track); + sMCISet.dwTimeFormat = MCI_FORMAT_TMSF; + MCISendCommand(dev2, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)(LPVOID) &sMCISet); + } + } + } + else // determine from RIFF structure of .CDA + { + HMMIO hmmio; + hmmio = mmioOpenA((char *)filename, NULL, MMIO_READ | MMIO_ALLOCBUF); + device = filename[0]; + if (device >= 'a' && device <= 'z') device += 'A' -'a'; + if (hmmio) + { + MMCKINFO mmckinfoParent; // parent chunk information + mmckinfoParent.fccType = mmioFOURCC('C', 'D', 'D', 'A'); + if (mmioDescend(hmmio, (LPMMCKINFO) &mmckinfoParent, NULL, MMIO_FINDRIFF) == MMSYSERR_NOERROR) + { + MMCKINFO mmckinfoSubchunk; // subchunk information structure + mmckinfoSubchunk.ckid = mmioFOURCC('f', 'm', 't', ' '); + if (mmioDescend(hmmio, &mmckinfoSubchunk, &mmckinfoParent, MMIO_FINDCHUNK) == MMSYSERR_NOERROR) + { + char *format; + DWORD dwFmtSize; // size of "FMT" chunk + dwFmtSize = mmckinfoSubchunk.cksize; + format = (char *) GlobalAlloc(GPTR, dwFmtSize); + if (mmioRead(hmmio, (HPSTR) format, dwFmtSize) == (int)dwFmtSize) + { + mmioAscend(hmmio, &mmckinfoSubchunk, 0); + track = *((short int *)format + 1); + if (length_in_ms) + { + int length = *((int *)format + 3); + int l = length % 75; + length /= 75; + length *= 1000; + length += (l * 1000) / 75; + *length_in_ms = length; + } + } + GlobalFree(format); + } + } + mmioClose(hmmio, 0); + } + } + + #ifdef IGNORE_API_GRACENOTE + if (title && track) + { + if (0 && !g_ps_inuse) + { + g_ps_inuse = 1; + memset(&g_ps, 0, sizeof(g_ps)); + if (!dev2) + { + CDOpen(&dev2, device, L"getfileinfo"); + } + if (dev2) + { + StringCchPrintfA(title, GETFILEINFO_TITLE_LENGTH, "CD Track %d", track); + wchar_t wtitle[256] = L""; + int ret = GetDiscID(dev2, &g_ps); + CDClose(&dev2); + dev2=0; + if (!ret && GetCDDBInfo(&g_ps, device)) + PostMessage(line.hMainWindow, WM_USER, (WPARAM) L"cda://", 247 /*IPC_REFRESHPLCACHE*/); + if (wtitle[0]) + lstrcpynA(title, AutoChar(wtitle), GETFILEINFO_TITLE_LENGTH); + } + g_ps_inuse = 0; + } + } + #endif + if (dev2) CDClose(&dev2); +#endif +} + +void eq_set(int on, char data[10], int preamp) +{} + +In_Module line = +{ + IN_VER_RET, + "nullsoft(in_cdda.dll)", + 0, // hMainWindow + 0, // hDllInstance + 0, + 0, // is_seekable + 1, // uses output plugins + about,//config, + about, + init, + quit, + getfileinfo, + infoDlg, + isourfile, + play, + pause, + unpause, + ispaused, + stop, + getlength, + getoutputtime, + setoutputtime, + setvolume, + setpan, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, // dsp shit + eq_set, + NULL, // setinfo + NULL // out_mod +}; + +int m_nblock = 0; + +extern "C" +{ + __declspec(dllexport) In_Module * winampGetInModule2() + { + s_last_error = NULL; + return &line; + } + + + + +#if 0 // TODO? + + __declspec(dllexport) int winampWriteExtendedFileInfo() + { + s_last_error = NULL; + // write it out + if (m_eiw_lastdrive) + { + AddToDatabase(&setInfo); + m_eiw_lastdrive = 0; + return 1; + } + return 0; + } +#endif +}; + +// wasabi based services for localisation support +api_language *WASABI_API_LNG = 0; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; +#ifndef IGNORE_API_GRACENOTE +api_gracenote *AGAVE_API_GRACENOTE = 0; +#endif +api_application *WASABI_API_APP = 0; + +void SetFileExtensions(void) +{ + static char fileExtensionsString[1200] = {0}; // "CDA\0CDDA Audio Tracks (*.CDA)\0" + char* end = 0; + StringCchCopyExA(fileExtensionsString, 1200, "CDA", &end, 0, 0); + StringCchCopyExA(end+1, 1200, WASABI_API_LNGSTRING(IDS_CDDA_AUDIO_TRACKS), 0, 0, 0); + line.FileExtensions = fileExtensionsString; +} + +int init() +{ + if (!IsWindow(line.hMainWindow)) + return IN_INIT_FAILURE; + + //CoInitialize(0); + + #ifndef IGNORE_API_GRACENOTE + Cddb_Initialize(); + InitializeCddbCache(); + #endif + + // loader so that we can get the localisation service api for use + waServiceFactory *sf = line.service->service_getServiceByGuid(languageApiGUID); + if (sf) WASABI_API_LNG = reinterpret_cast<api_language*>(sf->getInterface()); + + sf = line.service->service_getServiceByGuid(AgaveConfigGUID); + if (sf) AGAVE_API_CONFIG = reinterpret_cast<api_config*>(sf->getInterface()); + + #ifndef IGNORE_API_GRACENOTE + sf = line.service->service_getServiceByGuid(gracenoteApiGUID); + if (sf) AGAVE_API_GRACENOTE = reinterpret_cast<api_gracenote*>(sf->getInterface()); + #endif + + sf = line.service->service_getServiceByGuid(applicationApiServiceGuid); + if (sf) WASABI_API_APP = reinterpret_cast<api_application*>(sf->getInterface()); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(line.hDllInstance,InCDDALangGUID); + + static wchar_t szDescription[256]; + StringCchPrintfW(szDescription,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_CD_PLUGIN),PLUGIN_VERSION); + line.description = (char*)szDescription; + + SetFileExtensions(); + + playDeviceGuard = new Nullsoft::Utility::LockGuard; + playStatusGuard = new Nullsoft::Utility::LockGuard; + + MainThreadId = GetCurrentThreadId(); + config_read(); + return IN_INIT_SUCCESS; +} + +void quit() +{ + #ifndef IGNORE_API_GRACENOTE + ShutdownMusicIDWorkOrder(); + #endif + + if (playStatusGuard) + { + delete playStatusGuard; + playStatusGuard = 0; + } + + if (windacPlayer) + { + delete windacPlayer; + windacPlayer = NULL; + } + + if (daePlayer) + { + delete daePlayer; + daePlayer = NULL; + } + + if (mciPlayer) + { + delete mciPlayer; + mciPlayer = NULL; + } + + #ifndef IGNORE_API_GRACENOTE + ShutDownCDDB(); + #endif + + waServiceFactory *sf = line.service->service_getServiceByGuid(languageApiGUID); + if (sf) sf->releaseInterface(WASABI_API_LNG); + + sf = line.service->service_getServiceByGuid(AgaveConfigGUID); + if (sf) sf->releaseInterface(AGAVE_API_CONFIG); + + #ifndef IGNORE_API_GRACENOTE + sf = line.service->service_getServiceByGuid(gracenoteApiGUID); + if (sf) sf->releaseInterface(AGAVE_API_GRACENOTE); + + Cddb_Uninitialize(); + UninitializeCddbCache(); + #endif + + CloseTables(); + //CoUninitialize(); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/PlayStatus.cpp b/Src/Plugins/Input/in_cdda/PlayStatus.cpp new file mode 100644 index 00000000..533292f4 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/PlayStatus.cpp @@ -0,0 +1,23 @@ +#include "PlayStatus.h" + +//std::map<char, DriveStatus> playStatus; +PlayStatus playStatus; +Nullsoft::Utility::LockGuard *playStatusGuard = 0; + +DriveStatus::DriveStatus() : ripping(false) +{} + +void DriveStatus::RippingStarted() +{ + ripping = true; +} + +void DriveStatus::RippingStopped() +{ + ripping = false; +} + +bool DriveStatus::IsRipping() const +{ + return ripping; +} diff --git a/Src/Plugins/Input/in_cdda/PlayStatus.h b/Src/Plugins/Input/in_cdda/PlayStatus.h new file mode 100644 index 00000000..9e6ed1de --- /dev/null +++ b/Src/Plugins/Input/in_cdda/PlayStatus.h @@ -0,0 +1,28 @@ +#ifndef NULLSOFT_PLAYSTATUSH +#define NULLSOFT_PLAYSTATUSH +#pragma warning(disable:4786) +//#include <map> +#include "../nu/AutoLock.h" + +class DriveStatus +{ +public: + DriveStatus(); + + void RippingStarted(), RippingStopped(); + + bool IsRipping() const; +private: + bool ripping; +}; + +class PlayStatus +{ +public: + DriveStatus &operator [](int index) { return driveStatus[index-'A']; } + DriveStatus driveStatus[26]; +}; +extern PlayStatus playStatus; +//extern std::map<char, DriveStatus> playStatus; +extern Nullsoft::Utility::LockGuard *playStatusGuard; +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/VeritasPlay.cpp b/Src/Plugins/Input/in_cdda/VeritasPlay.cpp new file mode 100644 index 00000000..efb10d9a --- /dev/null +++ b/Src/Plugins/Input/in_cdda/VeritasPlay.cpp @@ -0,0 +1,636 @@ +#include "Main.h" +#include <windows.h> +#include "VeritasPlay.h" +#include "CDDB.h" +#include "workorder.h" +#include "api.h" + +VeritasPlay::VeritasPlay(bool _ripping) +: opened(false), ripping(_ripping), +primo(0), padStart(false), padEnd(false) +{ + submitHandle=0; + hThread = NULL; + overflowBuffer = NULL; + overflow = 0; + buffers = NULL; + currentBuffer = 0; +} + +VeritasPlay::~VeritasPlay() +{ + if (opened) + Abort(); + + Close(); + + DestroyBuffers(); + + + if (primo) + { + waServiceFactory *sf = (line.service ? line.service->service_getServiceByGuid(obj_primo::getServiceGuid()) : NULL); + if (sf) sf->releaseInterface(primo); + } + + primo = 0; + + if (submitHandle) + { + int x = workorder->CloseSig(submitHandle); + submitHandle=0; + } +} + +void VeritasPlay::CreateBuffers() +{ + if (buffers) + return; + + if (ripping) + { + buf_size = config_rip_buffersize; + nb_veritas_buf = config_rip_buffers; + } + else + { + buf_size = config_play_buffersize; + nb_veritas_buf = config_play_buffers; + } + + overflowBuffer = new BYTE[2352 * buf_size]; + overflow = 0; + + buffers = new VeritasBuffer[nb_veritas_buf]; + for (int i = 0;i < nb_veritas_buf;i++) + { + buffers[i].Create(buf_size); + } +} + +void VeritasPlay::DestroyBuffers() +{ + if (buffers) + { + for (int i = 0;i < nb_veritas_buf;i++) + { + buffers[i].Destroy(); + } + delete [] buffers; + } + + buffers = 0; + + delete overflowBuffer; + overflowBuffer = 0; +} + +void VeritasPlay::setvolume(int a_v, int a_p) +{ + line.outMod->SetVolume(a_v); + line.outMod->SetPan(a_p); +} + +void VeritasPlay::setoutputtime(int time_in_ms) +{ + need_seek = time_in_ms; +} + +void VeritasPlay::stop() +{ + killswitch = 1; + + Close(); + + if (hThread) + { + WaitForEvent(hThread, INFINITE); + hThread=0; + } + + line.outMod->Close(); +} + +void VeritasPlay::SeekAndFlush() +{ + int destsector = start_sector + ((need_seek * 75) / 1000); + Abort(); + openVeritasTrack(destsector, end_sector - destsector); + decode_pos_ms = need_seek; + // wait for a buffer before we flush + DWORD cursec = 0, totsec = 0; + while (true) + { + if (primo->RunningStatus(PRIMOSDK_GETSTATUS, &cursec, &totsec) != PRIMOSDK_RUNNING + && primo->UnitStatus(&unit, NULL, NULL, NULL, NULL) == PRIMOSDK_UNITERROR) + return; + + // check how far we've gone + if (cursec < buffers[currentBuffer].sector) + Sleep(1); + else + break; + } + + overflow = 0; + lastseek = destsector; + line.outMod->Flush(need_seek); + need_seek = -1; +} + + +void VeritasPlay::Seek() +{ + int destsector = start_sector + ((need_seek * 75) / 1000); + Abort(); + openVeritasTrack(destsector, end_sector - destsector); + + overflow = 0; + decode_pos_ms = need_seek; + need_seek = -1; + + lastseek = destsector; +} + +void VeritasPlay::Abort() +{ + AbortAsync(); + WaitForAbort(66); +} + +void VeritasPlay::AbortAsync() +{ + primo->RunningStatus(PRIMOSDK_ABORT, NULL, NULL); +} + +void VeritasPlay::WaitForAbort(int time) +{ + while (primo->RunningStatus(PRIMOSDK_GETSTATUS, NULL, NULL) == PRIMOSDK_RUNNING) + Sleep(time); + opened=false; +} + +int VeritasPlay::openVeritasTrack(DWORD start, DWORD length) +{ + int speedChoice = config_maxextractspeed; + DWORD speed; + if (ripping) + { + switch (speedChoice) + { + case 0: // 0.5x + case 1: // 1x + speed = 1; // can't do 0.5x in the SDK + break; + case 2: // 2x + speed = 2; + break; + case 3: // 4x + speed = 4; + break; + case 4: // 8x + speed = 8; + break; + case 5: // 16x + if (getRegVer() <= 0) + speed = 8; + else + speed = 16; + break; + default: + if (speedChoice < 0) + speed = PRIMOSDK_MIN; + else + { + if (getRegVer() <= 0) + speed = 8; + else + speed = PRIMOSDK_MAX; + } + break; + } + } + else + speed = 4;//PRIMOSDK_MAX; + + if (primo->ExtractAudioToBuffer(&unit, start, length, speed, 0, 0, 0) != PRIMOSDK_OK) + return 0; + + for (int i = 0;i < nb_veritas_buf;i++) + { + buffers[i].internal = buffers[i].buffer; + if (i==0 && padStart) + { + buffers[i].offset = buf_size*2352 + config_offset*4; + buffers[i].readSize = buf_size*2352; + memset((char *)(buffers[i].internal)+buffers[i].offset, 0, buffers[i].readSize); + buffers[i].sector=0; + continue; + } + if (i==0 && ripping) + buffers[i].offset = ((2352 + config_offset*4) % 2352); + if (primo->NextExtractAudioBuffer(buffers[i].buffer, buf_size*2352, &buffers[i].readSize, &buffers[i].sector) != PRIMOSDK_OK) + return 0; + } + + currentBuffer = 0; + opened = true; + return 1; +} + +int VeritasPlay::CopyOverflow(char *sample_buffer, int len) +{ + if (overflow) + { + len = min(len, overflow); + memset(sample_buffer, 0, len); + memcpy(sample_buffer, overflowBuffer, len); + overflow -= len; + return len; + } + return 0; +} + +void VeritasPlay::OutputOverflow() +{ + while (overflow) + { + char sample_buffer[576*4*2] = {0}; + int bytes = 576 * 4; + int len = min(bytes, overflow); + memset(sample_buffer, 0, bytes); + memcpy(sample_buffer, overflowBuffer, len); + Output(sample_buffer, bytes); + overflow -= len; + } +} +#include <assert.h> +void VeritasPlay::OutputBuffer(VeritasBuffer &buffer) +{ + char sample_buffer[576*4*2] = {0}; + size_t bytes = 576 * 4; + char *bufferPosition = sample_buffer; + if (overflow) + { + assert(overflow < (long)bytes); + memcpy(bufferPosition, overflowBuffer, overflow); + bytes -= overflow; + bufferPosition += overflow; + overflow = 0; + } + BYTE *bufferInput = buffer.buffer; + while (buffer.readSize) + { + if (buffer.readSize < bytes) // if we don't have enough left, save it to overflow + { + // if there was overflow last time, and the passed buffer didn't fill us up + // then we'll have to save both + BYTE *temp = overflowBuffer; + int samplesLeft = 576 * 4 - bytes; + if (samplesLeft) + { + memcpy(temp, sample_buffer, samplesLeft); + temp += samplesLeft; + overflow += samplesLeft; + } + + // copy any leftovers of the passed buffer + memcpy(temp, bufferInput, buffer.readSize); + bufferInput += buffer.readSize; + overflow += buffer.readSize; + buffer.readSize = 0; + return ; + } + else + { + memcpy(bufferPosition, bufferInput, bytes); + bufferPosition = sample_buffer; + bufferInput += bytes; + buffer.readSize -= bytes; + bytes = 576 * 4; + Output(sample_buffer, 576*4); + } + } +} + +size_t VeritasPlay::CopyBuffer(VeritasBuffer &buffer, char *&sample_buffer, int &bytes) +{ + // since a buffer is only copied once, this is safe + buffer.readSize -= buffer.offset; + buffer.internal += buffer.offset; + buffer.offset=0; + size_t len = min((size_t)bytes, buffer.readSize); + + memcpy(sample_buffer, buffer.internal, len); + buffer.internal += len; + buffer.readSize -= len; + sample_buffer += len; + bytes -= len; + return len; +} + +void VeritasPlay::Output(char *buffer, int len) +{ + line.VSAAddPCMData(buffer, g_nch, 16, line.outMod->GetWrittenTime() /*decode_pos_ms*/); + line.SAAddPCMData(buffer, g_nch, 16, line.outMod->GetWrittenTime() /*decode_pos_ms*/); + + int bytes = len; + if (line.dsp_isactive()) + bytes = line.dsp_dosamples((short *)buffer, len / g_nch / 2, 16, g_nch, 44100) * (g_nch * 2); + + while (line.outMod->CanWrite() < bytes && !killswitch) Sleep(10); + + line.outMod->Write(buffer, bytes); + + decode_pos_ms += ((len / g_nch / 2) * 1000) / 44100; +} + +int VeritasPlay::read(char *dest, int len, int *killswitch) //called by winampGetExtendedRead_getData +{ + bool noAbort=false; + int bytesCopied = 0; + + speedLimiter.Limit(killswitch); + while (!*killswitch) + { + DWORD cursec = 0, totsec = 0; + int r = primo->RunningStatus(PRIMOSDK_GETSTATUS, &cursec, &totsec); + + switch(r) + { + case PRIMOSDK_RUNNING: + break; + case PRIMOSDK_OK: + noAbort=true; + break; + default: + goto readabort; // benski> Mr. Faulkner's high school BASIC class prepared me for the real world! + } + + int a = primo->UnitStatus(&unit, NULL, NULL, NULL, NULL); + switch(a) + { + case PRIMOSDK_OK: + break; + default: // todo is everything else really an error? maybe message box for testing purposes + goto readabort; + } + + // check how far we've gone + if (cursec >= buffers[currentBuffer].sector) + { + char *olddest=dest; + int bytes = CopyBuffer(buffers[currentBuffer], dest, len); + if (submitHandle && bytes) + { + int res = workorder->WriteSigData(submitHandle, olddest, bytes); + switch(res) + { + case S_OK: + break; + case SG_SignatureAcquired: + default: + workorder->CloseSig(submitHandle); + submitHandle=0; + break; + } + } + speedLimiter.MoreBytesRead(bytes); + bytesCopied += bytes; + + if (!len || end == 1) + { + return bytesCopied; + } + + if ((buffers[currentBuffer].sector + lastseek) == end_sector) // are we done? + { + bytesCopied -= ((2352 - config_offset*4) % 2352); + end = 1; + return bytesCopied; + } + + buffers[currentBuffer].internal = buffers[currentBuffer].buffer; + buffers[currentBuffer].offset = 0; + if (!noAbort) + if (primo) primo->NextExtractAudioBuffer(buffers[currentBuffer].buffer, buf_size*2352, &buffers[currentBuffer].readSize, &buffers[currentBuffer].sector); + currentBuffer = (currentBuffer + 1) % nb_veritas_buf; + + } + else if (bytesCopied != 0) + return bytesCopied; + else + Sleep(13*buf_size); + } + // TODO: we can only get here if killswitch got set or if there was an error +readabort: + if (submitHandle) + { + workorder->AbortSig(submitHandle); + submitHandle=0; + } + AbortAsync(); + return -1; +} + +int VeritasPlay::threadProc2() +{ + bool noAbort=false; + while (!killswitch) + { + if (need_seek != -1) + SeekAndFlush(); + + DWORD cursec = 0, totsec = 0; + int r = primo->RunningStatus(PRIMOSDK_GETSTATUS, &cursec, &totsec); + + switch(r) + { + case PRIMOSDK_RUNNING: + break; + case PRIMOSDK_OK: + noAbort=true; + break; + default: + Abort(); + if (!killswitch) Sleep(200); + if (!killswitch) PostMessage(line.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return 0; + } + + int a = primo->UnitStatus(&unit, NULL, NULL, NULL, NULL); + switch(a) + { + case PRIMOSDK_OK: + break; + default: // todo is everything else really an error? maybe message box for testing purposes + Abort(); + if (!killswitch) Sleep(200); + if (!killswitch) PostMessage(line.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return 0; + } + + // check how far we've gone + if (cursec >= buffers[currentBuffer].sector) + { + OutputBuffer(buffers[currentBuffer]); + + if ((buffers[currentBuffer].sector+lastseek)==end_sector) // are we done? + break; + + buffers[currentBuffer].internal = buffers[currentBuffer].buffer; + if (!noAbort) + primo->NextExtractAudioBuffer(buffers[currentBuffer].buffer, buf_size*2352, &buffers[currentBuffer].readSize, &buffers[currentBuffer].sector); + + currentBuffer = (currentBuffer + 1) % nb_veritas_buf; + } + else + Sleep(13*buf_size); + } + + if (killswitch) + { + Abort(); + return 0; + } + + if (!noAbort) + AbortAsync(); + + OutputOverflow(); + //wait for output to be finished + line.outMod->Write(NULL, 0); + if (!noAbort) + WaitForAbort(10); + while (!killswitch && line.outMod->IsPlaying()) Sleep(10); + if (!killswitch) + PostMessage(line.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + + + return 0; +} + +#define VERITASPLAY_OPEN_FAIL 1 +#define VERITASPLAY_OPEN_NOPRIMO 2 + +int VeritasPlay::open(char drive, int track) //called by winampGetExtendedRead +{ + if ((ripping && !config_rip_veritas)) + return VERITASPLAY_OPEN_FAIL; + + need_seek = -1; + + driveLetter = drive; + unit = drive; + + hThread = NULL; + + if (!primo) + { + waServiceFactory *sf = line.service->service_getServiceByGuid(obj_primo::getServiceGuid()); + if (sf) primo = reinterpret_cast<obj_primo *>(sf->getInterface()); + } + + if (!primo) + return VERITASPLAY_OPEN_NOPRIMO; + + end = 0; + + speedLimiter.Reset(); + if (getRegVer() <= 0) + speedLimiter.SetLimit(5); + else + speedLimiter.NoLimit(); + + if (primo->DiscInfoEx(&unit,0, NULL, NULL, NULL, NULL, NULL, NULL) == PRIMOSDK_OK) + { + DWORD sesnum, tracktype, pregap; + if (primo->TrackInfo(track, &sesnum, &tracktype, &pregap, &start_sector, &sec_length) == PRIMOSDK_OK) + { + if (ripping) + { + if (config_offset != 0) + sec_length++; // TODO: some sort of logic for the last track + if (config_offset<0) + { + if (track != 1 || config_read_leadin) + start_sector--; + else + { + sec_length--; + padStart=true; + } + } + + } + end_sector = start_sector + sec_length; +#if 0 // TODO: add a config option to skip pregap (maybe separate config for burning and playback) + start_sector+=pregap; + sec_length-=pregap; +#endif + CreateBuffers(); + + if (openVeritasTrack(start_sector, sec_length)) + { + g_nch = 2; // TODO: maybe we should handle red book 4 channel audio? + g_playlength = (sec_length / 75) * 1000; + + decode_pos_ms = 0; + lastseek = start_sector; + need_seek = -1; + return 0; + } + } + } + Close(); + return VERITASPLAY_OPEN_FAIL; +} + +int VeritasPlay::play(char drive, int track) //called by winamp2 +{ + if (!config_use_dae2 || !config_use_veritas) + return 1; + + hThread=NULL; + { + g_playtrack = track; + } + + switch(open(drive, track)) + { + case VERITASPLAY_OPEN_FAIL: + Sleep(200); + // fall through + case VERITASPLAY_OPEN_NOPRIMO: + Close(); + return 1; + } + + DWORD thread_id; + hThread = CreateThread(NULL, NULL, &threadProc, (LPVOID)this, CREATE_SUSPENDED, &thread_id); + SetThreadPriority(hThread, AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + + int maxlat = line.outMod->Open(44100, g_nch, 16, -1, -1); + if (maxlat < 0) + { + Sleep(200); + Close(); + CloseHandle(hThread); + return 1; + } + + killswitch = 0; + + line.SetInfo(1411, 44, g_nch, 1); + line.SAVSAInit(maxlat, 44100); + line.VSASetInfo(g_nch, 44100); + line.is_seekable = 1; + ResumeThread(hThread); + + return 0; +} + +void VeritasPlay::Close() +{ + driveLetter=0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/VeritasPlay.h b/Src/Plugins/Input/in_cdda/VeritasPlay.h new file mode 100644 index 00000000..30b16f38 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/VeritasPlay.h @@ -0,0 +1,122 @@ +#ifndef NULLSOFT_VERITASPLAYH +#define NULLSOFT_VERITASPLAYH + +#include "CDPlay.h" +#include <windows.h> +#include "SpeedLimiter.h" +#include "main.h" + +using namespace Nullsoft::Utility; +class VeritasBuffer +{ +public: + VeritasBuffer() : buffer(0), readSize(0), sector(0), offset(0) + { + } + ~VeritasBuffer() + { + Destroy(); + } + void Create(int numBuffers) + { + buffer = new BYTE[numBuffers * 2352]; + Clear(); + } + void Destroy() + { + if (buffer) + delete buffer; + buffer = 0; + Clear(); + } + void Clear() + { + readSize = 0; + sector = 0; + } + BYTE *buffer; + BYTE *internal; + DWORD readSize; + DWORD sector; + int offset; +}; + +class VeritasPlay : public C_CDPlay +{ +public: + VeritasPlay(bool _ripping = false); + ~VeritasPlay(); + //void Delete() {if (primo) delete primo; primo = 0; } + void CreateBuffers(); + void DestroyBuffers(); + int open(char drive, int track); //called by winampGetExtendedRead + int play(char drive, int track); //called by winamp2 + int openVeritasTrack(DWORD start, DWORD length); + + static DWORD WINAPI threadProc(LPVOID lpParameter) + { + VeritasPlay *wp = (VeritasPlay *)lpParameter; + return wp->threadProc2(); + } + + void OutputBuffer(VeritasBuffer &buffer); + int read(char *dest, int len, int *killswitch); + void Abort(); + void Seek(); + void SeekAndFlush(); + void Output(char *buffer, int len); + void OutputOverflow(); + int CopyOverflow(char *sample_buffer, int len); + + size_t CopyBuffer(VeritasBuffer &buffer, char *&sample_buffer, int &bytes); + int threadProc2(); + void stop(); + void pause() + { + line.outMod->Pause(1); + } + void unpause() + { + line.outMod->Pause(0); + } + int getlength() + { + return g_playlength; + } + int getoutputtime() + { + return line.outMod->GetOutputTime(); + } + void setoutputtime(int time_in_ms); + void setvolume(int a_v, int a_p); + + void *submitHandle; +private: + void Close(); + void AbortAsync(); + + void WaitForAbort(int time); + int killswitch; + HANDLE hThread; + int decode_pos_ms; + int need_seek; + + DWORD unit, start_sector, end_sector, lastseek, sec_length; + int end; + int total_extract_len; + DWORD extract_start_time; + BYTE *overflowBuffer; + long overflow; + VeritasBuffer *buffers; + int buf_size, currentBuffer, nb_veritas_buf; + int g_nch; + bool ripping, opened; + SpeedLimiter speedLimiter; + bool padStart, padEnd; + + #ifndef IGNORE_PRIMO + obj_primo *primo; + #endif +}; + +#endif diff --git a/Src/Plugins/Input/in_cdda/WindacPlay.cpp b/Src/Plugins/Input/in_cdda/WindacPlay.cpp new file mode 100644 index 00000000..69c7a78b --- /dev/null +++ b/Src/Plugins/Input/in_cdda/WindacPlay.cpp @@ -0,0 +1,321 @@ +#include "WindacPlay.h" +#include "api__in_cdda.h" + +int WindacPlay::threadProc2() +{ + while (1) + { + if (need_seek != -1) + { + current_sector = start_sector; + current_sector += ((need_seek * 75) / 1000); + bytes_in_sbuf = 0; + line.outMod->Flush(need_seek); + decode_pos_ms = need_seek; + need_seek = -1; + } + if (!killswitch && bytes_in_sbuf <= 0 && current_sector.GetHSG() < end_sector.GetHSG()) + { + if (!scsi->Get_DriveStatus().CDPresent) + { + //infos->error("No CD present!"); + PostMessage(line.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return 0; + } + unsigned char *s = sbuf; + while ((bytes_in_sbuf < buf_size*2352) && (current_sector.GetHSG() < end_sector.GetHSG()) && !killswitch) + { + int n = min((int)16, (int)(end_sector.GetHSG() - current_sector.GetHSG())); + memset(s, 0, n*2352); + scsi->ReadCDDA(current_sector, n, s); + while (!scsi->WaitCDDA() && !killswitch) Sleep(66); + bytes_in_sbuf += n * 2352; + s += n * 2352; + current_sector += n; + } + } + + if (!bytes_in_sbuf && !killswitch) + { + //wait for output to be finished + line.outMod->Write(NULL, 0); + while (!killswitch && line.outMod->IsPlaying()) Sleep(10); + if (!killswitch) + PostMessage(line.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return 0; + } + + if (killswitch) return 0; + + char sample_buffer[576*4*2] = {0}; + int bytes = sizeof(sample_buffer) / 2; // enough room for dsp bullcrap + bytes = min((int)bytes_in_sbuf, (int)bytes); + memcpy(sample_buffer, sbuf, bytes); + if (bytes_in_sbuf > bytes) memcpy(sbuf, sbuf + bytes, bytes_in_sbuf - bytes); + bytes_in_sbuf -= bytes; + + int obytes = bytes; + + line.VSAAddPCMData(sample_buffer, g_nch, 16, line.outMod->GetWrittenTime() /*decode_pos_ms*/); + line.SAAddPCMData(sample_buffer, g_nch, 16, line.outMod->GetWrittenTime() /*decode_pos_ms*/); + + if (line.dsp_isactive()) + bytes = line.dsp_dosamples((short *)sample_buffer, bytes / g_nch / 2, 16, g_nch, 44100) * (g_nch * 2); + + while (line.outMod->CanWrite() < bytes && !killswitch) Sleep(66); + if (killswitch) return 0; + + line.outMod->Write(sample_buffer, bytes); + + decode_pos_ms += ((obytes / g_nch / 2) * 1000) / 44100; + } + return 0; +} + +void WindacPlay::stop() +{ + if (hThread) + { + killswitch = 1; + WaitForSingleObject(hThread, INFINITE); + } + if (needsToClose) + { + needsToClose = false; + } + line.outMod->Close(); +} + +int WindacPlay::open(wchar_t drive, int track) //called by winampGetExtendedRead +{ + g_drive = drive; + if (!inited && !LoadASPI()) + { + g_drive = 0; + return 1; + } + + inited = 1; + + int drivenum = 0; + getTrackInfos(&drivenum, (char)drive); + + m_pMapDrive = new CMapDrive(TRUE); + int nbdrives = m_pMapDrive->GetMaxDrives(); + if (!nbdrives) return 0; + + int host = -1, id = -1, lun = -1; + if (getSCSIIDFromDrive((char)drive, &host, &id, &lun)) + { + int found = 0; + for (int i = 0;i < nbdrives;i++) + { + drive_info = m_pMapDrive->GetInfo(i); + if (drive_info.HostAdapterNumber == host && drive_info.ID == id && drive_info.LUN == lun) + { + found = 1; + break; + } + } + if (!found) + { + s_last_error = "Drive not found"; + return 1; + } + } + else + { + // can't figure out the SCSI ID, oh well, try the gay method + TDriveInfo *tdi = &m_pMapDrive->GetInfo(drivenum); + if (!tdi) + { + s_last_error = "Drive not found"; + return 1; + } + + drive_info = *tdi; + } + + scsi = new CSCSICD((char)drive, drive_info); + + TDriveStatus status = scsi->Get_DriveStatus(); + + if (!status.CDPresent) + { + s_last_error = "CD not present"; + //infos->warning("No CD present!"); + g_drive=0; + return 1; + } + + TTrackList track_info = {0}; + track_info.TrackNummer = track; + scsi->ReadTrackInfo(track_info); + + if (track_info.Flags.DataTrack) + { + s_last_error = "Cannot play track"; + //infos->warning("Can't play data tracks"); + g_drive=0; + return 1; + } + + start_sector = track_info.StartSektor; + current_sector = start_sector; + end_sector = start_sector; + slength = track_info.Laenge; + end_sector += slength; + + g_playlength = (slength / 75) * 1000; + + g_nch = track_info.Flags.AudioChannels; + g_srate = 44100; + g_bps = 16; + + scsi->PrepareCDDA(); + + if (!sbuf) + sbuf = (unsigned char *)malloc(2352 * buf_size); + bytes_in_sbuf = 0; + + last_eject_scan = 0; + + return 0; +} + +int WindacPlay::play(wchar_t drive, int track) //called by winamp2's normal(old) play() interface +{ + if (open(drive, track)) return 1; + + // do this here as it helps to prevent an audio glitch on first playback and volume is set low + setvolume(a_v, a_p); + + int maxlat = line.outMod->Open(44100, g_nch, 16, -1, -1); + if (maxlat < 0) + { + g_drive=0; + return 1; + } + + line.SetInfo(1411, 44, g_nch, 1); + line.SAVSAInit(maxlat, 44100); + line.VSASetInfo(g_nch, 44100); + line.is_seekable = 1; + + killswitch = 0; + DWORD thread_id; + hThread = CreateThread(NULL, NULL, &threadProc, (LPVOID)this, NULL, &thread_id); + SetThreadPriority(hThread, AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + + //open the device thru MCI (for getfileinfo to work properly) + g_playtrack = track; + needsToClose = true; + + return 0; +} + +int WindacPlay::read(char *dest, int len, int *killswitch) //called by winampGetExtendedRead_getData +{ + int l = 0; + + while (l < len && !*killswitch) + { + if (!*killswitch && bytes_in_sbuf <= 0 && current_sector.GetHSG() < end_sector.GetHSG()) + { + //scan for ejected CD only every 2 seconds + if (last_eject_scan + 5000 < GetTickCount()) + { + int cnt = 5; + while (!scsi->Get_DriveStatus().CDPresent && cnt--) + { + Sleep(100); + } + if (cnt < 0 && !scsi->Get_DriveStatus().CDPresent) + { + //infos->error("No CD present!"); + return -1; + } + last_eject_scan = GetTickCount(); + } + + unsigned char *s = sbuf; + while ((bytes_in_sbuf < buf_size*2352) && (current_sector.GetHSG() < end_sector.GetHSG())) + { + int n = min((int)16, (int)(end_sector.GetHSG() - current_sector.GetHSG())); + memset(s, 0, n*2352); + scsi->ReadCDDA(current_sector, n, s); + while (!scsi->WaitCDDA() && !*killswitch) Sleep(66); + if (*killswitch) break; + bytes_in_sbuf += n * 2352; + s += n * 2352; + current_sector += n; + } + } + + if (!bytes_in_sbuf) break; + + int bytes = min(bytes_in_sbuf, len - l); + memcpy(dest + l, sbuf, bytes); + + if (bytes_in_sbuf > bytes) memcpy(sbuf, sbuf + bytes, bytes_in_sbuf - bytes); + bytes_in_sbuf -= bytes; + + l += bytes; + } + return l; +} + +void WindacPlay::getTrackInfos(int *drivenum, char driveletter) +{ + //finds first cdrom drive letter + char firstcd = 'D'; + { + DWORD drives = GetLogicalDrives(); + int nb = 0; + for (int drivemask = 0; (drivemask < 32) && (nb < 4); drivemask++) + { + if (drives&(1 << drivemask)) + { + wchar_t tmp[16] = {0}; + wsprintf(tmp, L"%c:\\", L'A' + drivemask); + if (GetDriveType(tmp) == DRIVE_CDROM) + { + firstcd = 'A' + drivemask; + break; + } + } + } + } + + *drivenum = driveletter - (unsigned char)firstcd; +} + +WindacPlay::WindacPlay() +{ + scsi = NULL; + sbuf = NULL; + m_pMapDrive = NULL; + buf_size = 64; //make it configitem + hThread = NULL; + decode_pos_ms = 0; + inited = 0; + need_seek = -1; + needsToClose = false; +} + +WindacPlay::~WindacPlay() +{ + if (scsi) + { + scsi->FinishCDDA(); + delete(scsi); + } + delete(m_pMapDrive); + free(sbuf); + if (inited) FreeASPI(); + + if (needsToClose) + { + needsToClose = false; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/WindacPlay.h b/Src/Plugins/Input/in_cdda/WindacPlay.h new file mode 100644 index 00000000..da06308b --- /dev/null +++ b/Src/Plugins/Input/in_cdda/WindacPlay.h @@ -0,0 +1,77 @@ +#ifndef NULLSOFT_WINDACPLAYH +#define NULLSOFT_WINDACPLAYH + +#include "Main.h" +#include "CDPlay.h" +#include "windac/Dac32.h" +#include "../nu/AutoLock.h" + +using namespace Nullsoft::Utility; +class WindacPlay : public C_CDPlay +{ +public: + WindacPlay(); + ~WindacPlay(); + int open(wchar_t drive, int track); + int play(wchar_t drive, int track); + static DWORD WINAPI threadProc(LPVOID lpParameter) + { + WindacPlay *wp = (WindacPlay *)lpParameter; + return wp->threadProc2(); + } + + int read(char *dest, int len, int *killswitch); + int threadProc2(); + void stop(); + void pause() + { + line.outMod->Pause(1); + } + void unpause() + { + line.outMod->Pause(0); + } + int getlength() + { + return g_playlength; + } + int getoutputtime() + { + return line.outMod->GetOutputTime(); + } + void setoutputtime(int time_in_ms) + { + need_seek = time_in_ms; + } + void setvolume(int _a_v, int _a_p) + { + line.outMod->SetVolume(_a_v); + line.outMod->SetPan(_a_p); + } + +private: + void getTrackInfos(int *drivenum, char driveletter); + unsigned char *sbuf; + long bytes_in_sbuf; + int buf_size; + int start, end; + int g_nch, g_srate, g_bps; + int killswitch; + HANDLE hThread; + int decode_pos_ms; + int need_seek; + + BOOL inited; + + CMapDrive *m_pMapDrive; + CSCSICD *scsi; + TDriveInfo drive_info; + CCDAdress start_sector, current_sector, end_sector; + int slength; + + DWORD last_eject_scan; + + bool needsToClose; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/api__in_cdda.h b/Src/Plugins/Input/in_cdda/api__in_cdda.h new file mode 100644 index 00000000..a2dc222c --- /dev/null +++ b/Src/Plugins/Input/in_cdda/api__in_cdda.h @@ -0,0 +1,22 @@ +#ifndef NULLSOFT_APIH +#define NULLSOFT_APIH + +#include <api/application/api_application.h> +extern api_application *applicationApi; +#define WASABI_API_APP applicationApi + +#include "../Agave/Config/api_config.h" +extern api_config *agaveConfigApi; +#define AGAVE_API_CONFIG agaveConfigApi + +#include "../Agave/Language/api_language.h" + +#ifndef IGNORE_API_GRACENOTE +#include "../gracenote/api_gracenote.h" +extern api_gracenote *gracenoteApi; +#define AGAVE_API_GRACENOTE gracenoteApi +#endif + +#include <api/service/waServiceFactory.h> + +#endif NULLSOFT_APIH
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/cddbevnt.cpp b/Src/Plugins/Input/in_cdda/cddbevnt.cpp new file mode 100644 index 00000000..4a86a2a7 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/cddbevnt.cpp @@ -0,0 +1,178 @@ +#include "./cddbevnt.h" +#include "cddbinterface.h" +#include <strsafe.h> +#include <ocidl.h> + +#include "cddbcontrolwinamp.tlh" + +CDBBEventManager::CDBBEventManager(void) : ref(1), cookie(0), user(0), + fnCmdCompleted(NULL), fnCmdProgress(NULL), + fnLogMessage(NULL), fnServerMessage(NULL) +{ +} + +CDBBEventManager::~CDBBEventManager(void) +{ +} + +HRESULT CDBBEventManager::Advise(IUnknown *pCDDBCtrl) +{ + HRESULT hr; + IConnectionPoint *pcp; + IConnectionPointContainer *pcpc; + + if (cookie) return E_FAIL; + if (!pCDDBCtrl) return E_INVALIDARG; + + hr = pCDDBCtrl->QueryInterface(IID_IConnectionPointContainer, (PVOID*)&pcpc); + if (SUCCEEDED (hr)) + { + hr = pcpc->FindConnectionPoint(DIID_DCDDBEvents, &pcp); + if (SUCCEEDED(hr)) + { + hr = pcp->Advise(static_cast<IDispatch*>(this), &cookie); + if (FAILED(hr)) cookie = 0; + pcp->Release(); + } + pcpc->Release(); + } + return hr; +} + +HRESULT CDBBEventManager::Unadvise(IUnknown *pCDDBCtrl) +{ + HRESULT hr; + IConnectionPoint *pcp = nullptr; + IConnectionPointContainer *pcpc = nullptr; + + if (!cookie) return S_OK; + if (!pCDDBCtrl) return E_INVALIDARG; + + hr = pCDDBCtrl->QueryInterface(IID_IConnectionPointContainer, (PVOID*)&pcpc); + if (SUCCEEDED (hr)) + { + hr = pcpc->FindConnectionPoint(DIID_DCDDBEvents, &pcp); + if (SUCCEEDED(hr)) + { + hr = pcp->Unadvise(cookie); + pcp->Release(); + } + pcpc->Release(); + } + return hr; +} + +HRESULT CDBBEventManager::RegisterCallback(UINT nType, void *fnCallback) +{ + switch(nType) + { + case CDDB_CB_CMDCOMPLETED: fnCmdCompleted = (CDDB_CMDCOMPLETED)fnCallback; break; + case CDDB_CB_CMDPROGRESS: fnCmdProgress = (CDDB_CMDPROGRESS)fnCallback; break; + case CDDB_CB_LOGMSG: fnLogMessage = (CDDB_LOGMSG)fnCallback; break; + case CDDB_CB_SRVMSG: fnServerMessage = (CDDB_SRVMSG)fnCallback; break; + default: return E_INVALIDARG; + } + return S_OK; +} + +ULONG_PTR CDBBEventManager::SetUserParam(ULONG_PTR userParam) +{ + ULONG_PTR tmp = user; + user = userParam; + return tmp; +} + +STDMETHODIMP CDBBEventManager::QueryInterface(REFIID riid, PVOID *ppvObject) +{ + if (!ppvObject) return E_POINTER; + + if (IsEqualIID(riid, DIID_DCDDBEvents)) + *ppvObject = dynamic_cast<CDBBEventManager*>(this); + else if (IsEqualIID(riid, IID_IDispatch)) + *ppvObject = dynamic_cast<IDispatch*>(this); + else if (IsEqualIID(riid, IID_IUnknown)) + *ppvObject = dynamic_cast<IUnknown*>(this); + else + { + *ppvObject = NULL; + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; +} + +ULONG CDBBEventManager::AddRef(void) +{ + return InterlockedIncrement(&ref); +} + +ULONG CDBBEventManager::Release(void) +{ + if (ref && 0 == InterlockedDecrement(&ref)) + { + delete this; + return 0; + } + return ref; +} + +STDMETHODIMP CDBBEventManager::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid) +{ + return DISP_E_UNKNOWNNAME; +} + +STDMETHODIMP CDBBEventManager::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo) +{ + return E_NOTIMPL; +} + +STDMETHODIMP CDBBEventManager::GetTypeInfoCount(unsigned int FAR * pctinfo) +{ + return E_NOTIMPL; +} + +STDMETHODIMP CDBBEventManager::Invoke(DISPID dispId, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pDispParams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr) +{ + switch(dispId) + { + case EVENT_COMMAND_COMPLETED: + if (3 != pDispParams->cArgs) return DISP_E_BADPARAMCOUNT; + OnCommandCompleted(pDispParams->rgvarg[2].lVal, pDispParams->rgvarg[1].lVal, pDispParams->rgvarg[0].pvarVal); + return S_OK; + case EVENT_LOG_MESSAGE: + if (1 != pDispParams->cArgs) return DISP_E_BADPARAMCOUNT; + OnLogMessage(pDispParams->rgvarg[0].bstrVal); + return S_OK; + case EVENT_SERVER_MESSAGE: + if (3 != pDispParams->cArgs) return DISP_E_BADPARAMCOUNT; + OnServerMessage(pDispParams->rgvarg[2].lVal, pDispParams->rgvarg[1].lVal, pDispParams->rgvarg[0].bstrVal); + return S_OK; + case EVENT_COMMAND_PROGRESS: + if (4 != pDispParams->cArgs) return DISP_E_BADPARAMCOUNT; + OnCommandProgress(pDispParams->rgvarg[3].lVal, pDispParams->rgvarg[2].lVal, pDispParams->rgvarg[1].lVal, pDispParams->rgvarg[0].lVal); + return S_OK; + } + return DISP_E_MEMBERNOTFOUND; +} + +void CDBBEventManager::OnCommandCompleted(LONG lCommandCode, HRESULT hCommandResult, VARIANT *pCommandData) +{ + if(fnCmdCompleted) fnCmdCompleted(lCommandCode, hCommandResult, pCommandData, user); +} + +void CDBBEventManager::OnLogMessage(BSTR bstrMessage) +{ + if(fnLogMessage) fnLogMessage(bstrMessage, user); +} + +void CDBBEventManager::OnServerMessage(LONG lMessageCode, LONG lMessageAction, BSTR bstrMessageData) +{ + if(fnServerMessage) fnServerMessage(lMessageCode, lMessageAction,bstrMessageData, user); +} + +void CDBBEventManager::OnCommandProgress(LONG lCommandCode, LONG lProgressCode, LONG lBytesDone, LONG lBytesTotal) +{ + if(fnCmdProgress) fnCmdProgress(lCommandCode, lProgressCode, lBytesDone, lBytesTotal, user); + +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/cddbevnt.h b/Src/Plugins/Input/in_cdda/cddbevnt.h new file mode 100644 index 00000000..21104cde --- /dev/null +++ b/Src/Plugins/Input/in_cdda/cddbevnt.h @@ -0,0 +1,63 @@ +#ifndef NULLSOFT_CDDB_EVENT_MANAGER_HEADER +#define NULLSOFT_CDDB_EVENT_MANAGER_HEADER + + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +#pragma once +#endif + +#include <windows.h> + +// callbacks +typedef void (CALLBACK *CDDB_CMDCOMPLETED)(LONG /*lCommandCode*/, HRESULT /*hCommandResult*/, VARIANT* /*pCommandData*/, ULONG_PTR /*user*/); +typedef void (CALLBACK *CDDB_LOGMSG)(BSTR /*bstrMessage*/, ULONG_PTR /*user*/); +typedef void (CALLBACK *CDDB_SRVMSG)(LONG /*lMessageCode*/, LONG /*lMessageAction*/, BSTR /*bstrMessageData*/, ULONG_PTR /*user*/); +typedef void (CALLBACK *CDDB_CMDPROGRESS)(LONG /*lCommandCode*/, LONG /*lProgressCode*/, LONG /*lBytesDone*/, LONG /*lBytesTotal*/, ULONG_PTR /*user*/); +// callback types +#define CDDB_CB_CMDCOMPLETED 0 +#define CDDB_CB_CMDPROGRESS 1 +#define CDDB_CB_LOGMSG 2 +#define CDDB_CB_SRVMSG 3 + +class CDBBEventManager : public IDispatch +{ +public: + CDBBEventManager(void); + virtual ~CDBBEventManager(void); + +public: + // *** IUnknown Methods *** + STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject); + STDMETHOD_(ULONG, AddRef)(void); + STDMETHOD_(ULONG, Release)(void); + + // *** IDispatch Methods *** + STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid); + STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo); + STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo); + STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr); + +protected: + void OnCommandCompleted(LONG lCommandCode, HRESULT hCommandResult, VARIANT *pCommandData); + void OnLogMessage(BSTR bstrMessage); + void OnServerMessage(LONG lMessageCode, LONG lMessageAction, BSTR bstrMessageData); + void OnCommandProgress(LONG lCommandCode, LONG lProgressCode, LONG lBytesDone, LONG lBytesTotal); + +public: + HRESULT Advise(IUnknown *pCDDBCtrl); + HRESULT Unadvise(IUnknown *pCDDBCtrl); + HRESULT RegisterCallback(UINT nType, void *fnCallback); + ULONG_PTR SetUserParam(ULONG_PTR userParam); +protected: + LONG ref; + DWORD cookie; + ULONG_PTR user; + CDDB_CMDCOMPLETED fnCmdCompleted; + CDDB_CMDPROGRESS fnCmdProgress; + CDDB_LOGMSG fnLogMessage; + CDDB_SRVMSG fnServerMessage; + +}; + +#endif //NULLSOFT_CDDB_EVENT_MANAGER_HEADER + diff --git a/Src/Plugins/Input/in_cdda/cddbui.cpp b/Src/Plugins/Input/in_cdda/cddbui.cpp new file mode 100644 index 00000000..adb51e71 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/cddbui.cpp @@ -0,0 +1,997 @@ +#include "main.h" +#include ".\cddbui.h" +#include ".\cddb.h" +#include "api__in_cdda.h" +#include ".\resource.h" +#include "..\winamp\wa_ipc.h" + +#include <api/application/api_application.h> + +#include <shobjidl.h> +#include <commctrl.h> +#include <strsafe.h> + +#include "cddbcontrolwinamp.tlh" + +#define PROP_PRGDLG L"PRGDLG" + + +#define TIMER_PROGRESS_DESTROY_ID 1978 +#define TIMER_PROGRESS_DESTROY_ELAPSE 250 +#define TIMER_PROGRESS_ANIMATE_ID 1980 +#define TIMER_PROGRESS_ANIMATE_ELAPSE 65 + +#define MODAL_EXIT 0 +#define MODAL_ACTIVE 1 +#define MODAL_DESTROY 2 + +#define ICON_OFFSET_X 12 +#define ICON_OFFSET_Y 12 + +#define DIALOG_HEIGHT_NORMAL 66 +#define DIALOG_HEIGHT_EXTENDED 160 + +#ifndef IDC_HAND +#define IDC_HAND MAKEINTRESOURCE(32649) +#endif //IDC_HAND + + + +typedef struct _PROGRESSICON +{ + HINSTANCE hInstance; + LONG resId; + RECT rc; + LONG frames; + LONG step; + HBITMAP hbmp; +} PROGRESSICON; + +typedef struct _PROGRESSDLG +{ + PROGRESSICON icon; + UINT uState; + DWORD dwAutoClose; + CDDBDLG_ONBTNCLICK OnAbort; + CDDBDLG_ONBTNCLICK OnButton1; + BSTR Btn1Data; + BSTR AbortData; + WORD Modal; + HRESULT rCode; + HANDLE user; +} PROGRESSDLG; + +static HFONT hFont = NULL; +static LONG fontRef = 0; + +static INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +static void CALLBACK ProgressDlg_OnTimer(HWND hwnd, UINT uMsg, UINT_PTR evntId, DWORD dwTime); +static void InvalidateLogo(HWND hwnd, PROGRESSICON *pIcon); +static BOOL EndProgressDialog(HWND hwnd); + +#define GET_DATA(__hwnd) ((PROGRESSDLG*)GetPropW((__hwnd), PROP_PRGDLG)) + +static wchar_t szText1[256]; +static wchar_t szText2[256]; + +#define GET_SAFE_LANGSTRING(__str, __buff, __cch) ((__str) ? (IS_INTRESOURCE(__str) ? WASABI_API_LNGSTRINGW_BUF((UINT)(UINT_PTR)(__str), (__buff), (__cch)) : (__str)) : L"") +#define GET_SAFE_LANGSTRING1(__str) GET_SAFE_LANGSTRING((__str), (szText1), (sizeof(szText1)/sizeof(wchar_t))) +#define GET_SAFE_LANGSTRING2(__str) GET_SAFE_LANGSTRING((__str), (szText2), (sizeof(szText2)/sizeof(wchar_t))) + + +typedef struct _ENUMWND_DATAPACK +{ + HWND host; + HWND *list; + INT index; + INT count; + UINT flags; + BOOL found; +} ENUMWND_DATAPACK; + +HWND CddbProgressDlg_Create(HWND hwndParent, INT nCmdShow) +{ + HWND hdlg = WASABI_API_CREATEDIALOGPARAMW(IDD_CDDB_PROGRESS, NULL, DialogProc, 0L); + if (hdlg && SW_HIDE != nCmdShow) ShowWindow(hdlg, SW_HIDE); + return hdlg; +} + +BOOL CddbProgressDlg_Initialize(HWND hwnd, LPCWSTR pszCaption, CDDBDLG_ONBTNCLICK fnOnAbort, BSTR bstrAbortUser) +{ + HWND hwndCtrl; + PROGRESSDLG *pDlg; + + if(!hwnd || !IsWindow(hwnd)) return FALSE; + + pDlg = GET_DATA(hwnd); + if (!pDlg) return FALSE; + + KillTimer(hwnd, TIMER_PROGRESS_ANIMATE_ID); + pDlg->OnAbort = (fnOnAbort) ? fnOnAbort : NULL; + if (pDlg->AbortData) SysFreeString(pDlg->AbortData); + pDlg->AbortData = (bstrAbortUser) ? SysAllocString(bstrAbortUser) : NULL; + + SetDlgItemTextW(hwnd, IDC_LBL_CAPTION, GET_SAFE_LANGSTRING1(pszCaption)); + SetDlgItemTextW(hwnd, IDC_LBL_STATUS, L""); + + if (NULL != (hwndCtrl = GetDlgItem(hwnd, IDC_LBL_REASON))) + { + ShowWindow(hwndCtrl, SW_HIDE); + SetWindowTextW(hwndCtrl, L""); + } + + if (NULL != (hwndCtrl = GetDlgItem(hwnd, IDC_PRG_STATUS))) + { + ShowWindow(hwndCtrl, SW_HIDE); + SendMessageW(hwndCtrl, PBM_SETPOS, (WPARAM)0, (LPARAM)0L); + + } + + if (NULL != (hwndCtrl = GetDlgItem(hwnd, IDCANCEL))) + { + SetWindowTextW(hwndCtrl, GET_SAFE_LANGSTRING1(MAKEINTRESOURCEW((fnOnAbort) ? IDS_ABORT : IDS_CLOSE))); + EnableWindow(hwndCtrl, (NULL != fnOnAbort)); + } + if (NULL != (hwndCtrl = GetDlgItem(hwnd, IDC_LV_EXT))) + { + SendMessageW(hwndCtrl, LVM_DELETEALLITEMS, 0, 0L); + } + + + pDlg->icon.step = 0; + InvalidateLogo(hwnd, &pDlg->icon); + SetTimer(hwnd, TIMER_PROGRESS_ANIMATE_ID, TIMER_PROGRESS_ANIMATE_ELAPSE, ProgressDlg_OnTimer); + + pDlg->uState = STATE_ACTIVE; + return TRUE; +} + +BOOL CddbProgressDlg_Completed(HWND hwnd, LPCWSTR pszResult, LPCWSTR pszReason, DWORD nAutoCloseDelay, HRESULT rCode) +{ + PROGRESSDLG *pDlg; + if(!hwnd || !IsWindow(hwnd)) return FALSE; + + pDlg = GET_DATA(hwnd); + if (!pDlg) return FALSE; + + KillTimer(hwnd, TIMER_PROGRESS_ANIMATE_ID); + + pDlg->uState = STATE_COMPLETED; + pDlg->rCode = rCode; + + if (AUTOCLOSE_NOW == nAutoCloseDelay) + { + EndProgressDialog(hwnd); + } + else + { + + HWND hwndCtrl; + SetDlgItemTextW(hwnd, IDC_LBL_STATUS, GET_SAFE_LANGSTRING1(pszResult)); + if (NULL != (hwndCtrl = GetDlgItem(hwnd, IDC_PRG_STATUS))) + { + ShowWindow(hwndCtrl, SW_HIDE); + SendMessageW(hwndCtrl, PBM_SETPOS, (WPARAM)0, (LPARAM)0L); + } + if (NULL != (hwndCtrl = GetDlgItem(hwnd, IDC_LBL_REASON))) + { + SetWindowTextW(hwndCtrl, GET_SAFE_LANGSTRING1(pszReason)); + ShowWindow(hwndCtrl, SW_SHOWNORMAL); + } + if (NULL != (hwndCtrl = GetDlgItem(hwnd, IDCANCEL))) + { + + if (AUTOCLOSE_NEVER != nAutoCloseDelay) + { + INT time = nAutoCloseDelay/1000 + (((nAutoCloseDelay%1000)>500) ? 1 : 0); + if (time) + { + wchar_t szText[128] = {0}; + StringCchPrintfW(szText, sizeof(szText)/sizeof(wchar_t), L"%s (%d)", GET_SAFE_LANGSTRING1(MAKEINTRESOURCEW(IDS_CLOSE)),time); + SetDlgItemTextW(hwnd, IDCANCEL, szText); + } + else SetWindowText(hwndCtrl, GET_SAFE_LANGSTRING1(MAKEINTRESOURCEW(IDS_CLOSE))); + } + else SetWindowText(hwndCtrl, GET_SAFE_LANGSTRING1(MAKEINTRESOURCEW(IDS_CLOSE))); + EnableWindow(hwndCtrl, TRUE); + } + + + pDlg->icon.step = pDlg->icon.frames - 1; + InvalidateLogo(hwnd, &pDlg->icon); + + if (AUTOCLOSE_NEVER != nAutoCloseDelay) + { + pDlg->dwAutoClose = GetTickCount() + nAutoCloseDelay; + return (0 != SetTimer(hwnd, TIMER_PROGRESS_DESTROY_ID, TIMER_PROGRESS_DESTROY_ELAPSE, ProgressDlg_OnTimer)); + } + } + return TRUE; +} + +BOOL CddbProgressDlg_SetStatus(HWND hwnd, LPCWSTR pszStatus, INT nPercentCompleted) +{ + BOOL br(TRUE); + HWND hwndCtrl; + if (!hwnd || !IsWindow(hwnd)) return FALSE; + if (pszStatus && !SetDlgItemTextW(hwnd, IDC_LBL_STATUS, GET_SAFE_LANGSTRING1(pszStatus))) br = FALSE; + hwndCtrl = GetDlgItem(hwnd, IDC_PRG_STATUS); + if (hwndCtrl) + { + if (nPercentCompleted > 100) nPercentCompleted = 100; + if (nPercentCompleted < 0) ShowWindow(hwndCtrl, SW_HIDE); + else + { + SendMessageW(hwndCtrl, PBM_SETPOS, (WPARAM)nPercentCompleted, (LPARAM)0L); + ShowWindow(hwndCtrl, SW_SHOWNA); + } + } + return br; +} + +UINT CddbProgressDlg_GetState(HWND hwnd) +{ + PROGRESSDLG *pDlg; + if(!hwnd || !IsWindow(hwnd)) return STATE_INACTIVE; + pDlg = GET_DATA(hwnd); + return (pDlg) ? pDlg->uState : STATE_INACTIVE; +} + +BOOL CddbProgressDlg_EnableAbortButton(HWND hwnd, BOOL bEnable) +{ + HWND hwndBtn; + if(!hwnd || !IsWindow(hwnd)) return FALSE; + if (NULL == (hwndBtn = GetDlgItem(hwnd, IDCANCEL))) return FALSE; + return EnableWindow(hwndBtn, bEnable); +} + +BOOL CddbProgressDlg_ShowButton1(HWND hwnd, LPCWSTR pszCaption, CDDBDLG_ONBTNCLICK fnOnButton1, BSTR bstrUser) +{ + PROGRESSDLG *pDlg; + HWND hwndBtn; + + if(!hwnd || !IsWindow(hwnd)) return FALSE; + pDlg = GET_DATA(hwnd); + if (!pDlg) return FALSE; + + if (NULL == (hwndBtn = GetDlgItem(hwnd, IDC_BUTTON1))) return FALSE; + + if (pDlg->Btn1Data) SysFreeString(pDlg->Btn1Data); + + if(pszCaption && fnOnButton1) + { + SetWindowTextW(hwndBtn, GET_SAFE_LANGSTRING1(pszCaption)); + ShowWindow(hwndBtn, SW_SHOWNORMAL); + pDlg->OnButton1 = fnOnButton1; + pDlg->Btn1Data = (bstrUser) ? SysAllocString(bstrUser) : NULL; + SendMessageW(hwnd, WM_NEXTDLGCTL, (WPARAM)TRUE, (LPARAM)hwndBtn); + } + else + { + ShowWindow(hwndBtn, SW_HIDE); + pDlg->OnButton1 = NULL; + pDlg->Btn1Data = NULL; + + } + return TRUE; +} + +BOOL CddbProgressDlg_ShowInTaskbar(HWND hwnd, BOOL bShow) +{ + HRESULT hr; + ITaskbarList *pTaskbar; + if(!hwnd || !IsWindow(hwnd)) return FALSE; + hr = CoCreateInstance(CLSID_TaskbarList,0, CLSCTX_INPROC_SERVER, IID_ITaskbarList, (void**)&pTaskbar); + if(SUCCEEDED(hr)) + { + hr = pTaskbar->HrInit(); + if (SUCCEEDED(hr)) + { + if (bShow) + { + hr = pTaskbar->AddTab(hwnd); + pTaskbar->ActivateTab(hwnd); + + } + else pTaskbar->DeleteTab(hwnd); + } + pTaskbar->Release(); + } + return (S_OK == hr); +} +BOOL CddbProgressDlg_SetExtendedMode(HWND hwnd, BOOL bEnable) +{ + RECT rc; + HWND hwndCtrl; + INT height; + if(!hwnd || !IsWindow(hwnd)) return FALSE; + GetWindowRect(hwnd, &rc); + + RECT rw; + GetClientRect(hwnd, &rw); + height = (rc.bottom - rc.top) - (rw.bottom - rw.top); + SetRect(&rw, 0, 0, 1, (bEnable) ? DIALOG_HEIGHT_EXTENDED : DIALOG_HEIGHT_NORMAL); + MapDialogRect(hwnd, &rw); + height += rw.bottom; + + if (height == rc.bottom - rc.top) return TRUE; + + if (NULL != (hwndCtrl = GetDlgItem(hwnd, IDC_BUTTON1))) + { + GetWindowRect(hwndCtrl, &rw); + MapWindowPoints(HWND_DESKTOP, hwnd, (POINT*)&rw, 2); + SetWindowPos(hwndCtrl, NULL, rw.left, rw.top + (height - (rc.bottom - rc.top)), 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE); + } + + if (NULL != (hwndCtrl = GetDlgItem(hwnd, IDCANCEL))) + { + GetWindowRect(hwndCtrl, &rw); + MapWindowPoints(HWND_DESKTOP, hwnd, (POINT*)&rw, 2); + SetWindowPos(hwndCtrl, NULL, rw.left, rw.top + (height - (rc.bottom - rc.top)), 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE); + } + + if (NULL != (hwndCtrl = GetDlgItem(hwnd, IDC_LV_EXT))) + { + PROGRESSDLG *pDlg; + INT listBottom; + GetWindowRect(GetDlgItem(hwnd, IDCANCEL), &rw); + MapWindowPoints(HWND_DESKTOP, hwnd, (POINT*)&rw, 1); + listBottom = rw.top - MulDiv(4, HIWORD(GetDialogBaseUnits()), 8); + + pDlg = GET_DATA(hwnd); + if (pDlg) + { + GetWindowRect(hwndCtrl, &rw); + MapWindowPoints(HWND_DESKTOP, hwnd, (POINT*)&rw, 2); + INT listTop = ICON_OFFSET_Y * 2 + (pDlg->icon.rc.bottom - pDlg->icon.rc.top); + SetWindowPos(hwndCtrl, NULL, ICON_OFFSET_X, listTop, rc.right - rc.left - ICON_OFFSET_X*2, listBottom - listTop, SWP_NOACTIVATE | SWP_NOZORDER); + ShowWindow(hwndCtrl, (bEnable) ? SW_SHOW : SW_HIDE); + } + } + + SetWindowPos(hwnd, NULL, 0, 0, rc.right - rc.left, height, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE); + + + return TRUE; +} +BOOL CddbProgressDlg_AddRecord(HWND hwnd, LPCWSTR pszArtist, LPCWSTR pszTitle, LPCWSTR pszLanguage) +{ + HWND hwndList; + LVITEMW item; + INT index; + if(!hwnd || !IsWindow(hwnd)) return FALSE; + + hwndList = GetDlgItem(hwnd, IDC_LV_EXT); + if (!hwndList) return FALSE; + + item.mask = LVIF_TEXT; + item.iItem = 0xFFFF; + item.iSubItem = 0; + item.pszText = GET_SAFE_LANGSTRING1(pszArtist); + index = (INT)SendMessageW(hwndList, LVM_INSERTITEMW, 0, (LPARAM)&item); + if (-1 == index) return FALSE; + + if (0 == index) + { + item.state = LVIS_FOCUSED | LVIS_SELECTED; + item.stateMask = item.state; + SendMessageW(hwndList, LVM_SETITEMSTATE, (WPARAM)index, (LPARAM)&item); + } + item.iItem = index; + item.mask = LVIF_TEXT; + item.iSubItem = 1; + item.pszText = GET_SAFE_LANGSTRING1(pszTitle);; + SendMessageW(hwndList, LVM_SETITEMW, 0, (LPARAM)&item); + + item.iItem = index; + item.mask = LVIF_TEXT; + item.iSubItem = 2; + item.pszText = GET_SAFE_LANGSTRING1(pszLanguage);; + SendMessageW(hwndList, LVM_SETITEMW, 0, (LPARAM)&item); + + return TRUE; +} + + +#define HOOK_MAX_DATA 12 +typedef struct _HOOKDATA +{ + HHOOK handle; + int ref; + HWND modalList[HOOK_MAX_DATA]; +} HOOKDATA; + +static HOOKDATA g_hook = { NULL, 0}; + +static LRESULT CALLBACK HookProc(int code, WPARAM wParam, LPARAM lParam) +{ + MOUSEHOOKSTRUCT *pMouse = (MOUSEHOOKSTRUCT*)lParam; + switch(wParam) + { + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONDBLCLK: + case WM_NCRBUTTONDOWN: + case WM_NCRBUTTONDBLCLK: + case WM_NCMBUTTONDOWN: + case WM_NCMBUTTONDBLCLK: + if (FALSE == IsWindowEnabled(pMouse->hwnd)) + { + + for(int i = g_hook.ref - 1; i > -1 ; i--) + { + HWND hwndOwner = GetWindow(g_hook.modalList[i], GW_OWNER); + if (hwndOwner == pMouse->hwnd || hwndOwner == GetWindow(pMouse->hwnd, GW_OWNER)) + { + DWORD style = GetWindowLongPtrW(g_hook.modalList[i], GWL_STYLE); + if (0 != (WS_VISIBLE & style) && 0 == (WS_DISABLED & style)) + { + HWND hwndTest = GetForegroundWindow(); + if (hwndTest == g_hook.modalList[i]) + { + FLASHWINFO flash; + flash.hwnd = g_hook.modalList[i]; + flash.cbSize = sizeof(FLASHWINFO); + flash.dwFlags = FLASHW_CAPTION; + flash.uCount = 2; + flash.dwTimeout = 100; + FlashWindowEx(&flash); + MessageBeep(MB_OK); + } + else SetForegroundWindow(g_hook.modalList[i]); + return CallNextHookEx(g_hook.handle, code, wParam, lParam); + } + } + } + } + break; + } + return CallNextHookEx(g_hook.handle, code, wParam, lParam); +} +static void AddModalHook(HWND hdlg) +{ + if (!hdlg) return; + if (!g_hook.handle) + { + ZeroMemory(&g_hook, sizeof(HOOKDATA)); + g_hook.handle = SetWindowsHookEx(WH_MOUSE, HookProc, line.hDllInstance, NULL); + if (!g_hook.handle) return; + } + if (HOOK_MAX_DATA == (g_hook.ref - 1)) return; + g_hook.modalList[g_hook.ref] = hdlg; + g_hook.ref++; + return; +} + +static void ReleaseModalHook(HWND hdlg) +{ + if (!hdlg) return; + for (int i = 0; i < g_hook.ref; i++) + { + if (g_hook.modalList[i] == hdlg) + { + if (i != g_hook.ref -1) MoveMemory(&g_hook.modalList[i], &g_hook.modalList[i + 1], (g_hook.ref - i -1)*sizeof(HWND)); + + g_hook.ref--; + if (!g_hook.ref) + { + UnhookWindowsHookEx(g_hook.handle); + ZeroMemory(&g_hook, sizeof(HOOKDATA)); + } + return; + } + } + return ; +} + +HRESULT CddbProgressDlg_DoModal(HWND hwnd, RECT *prc) +{ + MSG msg; + HWND hwndOwner; + + PROGRESSDLG *pDlg; + HRESULT rCode; + HWND disabledList[32] = {0}; + + if(!hwnd || !IsWindow(hwnd)) return E_INVALIDARG; + pDlg = GET_DATA(hwnd); + if (!pDlg || MODAL_ACTIVE == pDlg->Modal) return E_POINTER; + + pDlg->Modal = MODAL_ACTIVE; + hwndOwner = GetParent(hwnd); + if (hwndOwner == GetDesktopWindow()) hwndOwner = NULL; + if (hwndOwner != line.hMainWindow && + hwndOwner == (HWND)SendMessageW(line.hMainWindow, WM_WA_IPC, 0, IPC_GETDIALOGBOXPARENT)) + { + hwndOwner = line.hMainWindow; + } + + DWORD mineTID, parentTID; + mineTID = GetWindowThreadProcessId(hwnd, NULL); + parentTID = (hwndOwner) ? GetWindowThreadProcessId(hwndOwner, NULL) : mineTID; + if (hwndOwner) + { + HWND *p; + if (mineTID != parentTID) AttachThreadInput(parentTID, mineTID, TRUE); + p = disabledList; + if (IsWindowEnabled(hwndOwner)) + { + *p = hwndOwner; + p++; + } + FindAllOwnedWindows(hwndOwner, p, sizeof(disabledList)/sizeof(HWND) - (INT)(p - disabledList), FINDWND_ONLY_ENABLED); + for (p = disabledList; *p != NULL; p++) { if (hwnd != *p) EnableWindow(*p, FALSE); } + } + + AddModalHook(hwnd); + + msg.message = WM_NULL; + while(MODAL_ACTIVE == pDlg->Modal) + { + if (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) + { + if (msg.message == WM_QUIT) break; + else if (!CallMsgFilter(&msg, MSGF_DIALOGBOX) && + !IsDialogMessage(hwnd, &msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + else if (MODAL_ACTIVE == pDlg->Modal) WaitMessage(); + } + + ReleaseModalHook(hwnd); + + rCode = pDlg->rCode; + + if (msg.message == WM_QUIT) PostQuitMessage((int)msg.wParam); + if (hwndOwner) + { + if (mineTID != parentTID) AttachThreadInput(parentTID, mineTID, FALSE); + for (HWND *p = disabledList; *p != NULL; p++) { if (hwnd != *p) EnableWindow(*p, TRUE); } + SetActiveWindow(hwndOwner); + } + + if (prc && !GetWindowRect(hwnd, prc)) SetRect(prc, 0, 0, 0,0); + + if(MODAL_EXIT != pDlg->Modal) DestroyWindow(hwnd); + return rCode; +} + +BOOL CddbProgressDlg_ExitModal(HWND hwnd, HRESULT rCode, BOOL bDesroy) +{ + PROGRESSDLG *pDlg; + if(!hwnd || !IsWindow(hwnd)) return FALSE; + pDlg = GET_DATA(hwnd); + if (!pDlg) return FALSE; + if (MODAL_ACTIVE == pDlg->Modal) + { + pDlg->Modal = (bDesroy) ? MODAL_DESTROY : MODAL_EXIT; + pDlg->rCode = rCode; + PostMessageW(hwnd, WM_NULL, 0, 0); + } + return TRUE; +} + +BOOL CddbProgressDlg_IsModal(HWND hwnd) +{ + PROGRESSDLG *pDlg; + if(!hwnd || !IsWindow(hwnd)) return FALSE; + pDlg = GET_DATA(hwnd); + return (pDlg && (MODAL_ACTIVE == pDlg->Modal)); +} + +INT CddbProgressDlg_GetSelRecordIndex(HWND hwnd) +{ + HWND hwndList; + if(!hwnd || !IsWindow(hwnd)) return -1; + hwndList = GetDlgItem(hwnd, IDC_LV_EXT); + if (!hwndList) return -1; + return (INT)(INT_PTR)SendMessageW(hwndList, LVM_GETNEXTITEM, -1, (LPARAM)(LVNI_SELECTED | LVNI_FOCUSED)); +} + +BOOL CddbProgressDlg_SetUserData(HWND hwnd, HANDLE user) +{ + PROGRESSDLG *pDlg; + if(!hwnd || !IsWindow(hwnd)) return FALSE; + pDlg = GET_DATA(hwnd); + if (!pDlg) return FALSE; + pDlg->user = user; + return TRUE; +} + +HANDLE CddbProgressDlg_GetUserData(HWND hwnd) +{ + PROGRESSDLG *pDlg; + if(!hwnd || !IsWindow(hwnd)) return NULL; + pDlg = GET_DATA(hwnd); + return (pDlg) ? pDlg->user : NULL; +} + +static void InvalidateLogo(HWND hwnd, PROGRESSICON *pIcon) +{ + RECT rc; + SetRect(&rc, ICON_OFFSET_X, ICON_OFFSET_Y, + ICON_OFFSET_X + (pIcon->rc.right - pIcon->rc.left), + ICON_OFFSET_Y + (pIcon->rc.bottom - pIcon->rc.top)); + InvalidateRect(hwnd, &rc, TRUE); +} + +static BOOL EnableWindowTheme(HWND hwnd, BOOL bEnable) +{ + static HMODULE hModule = NULL; + static BOOL firstTime = TRUE; + static HRESULT (WINAPI *__setwintheme)(HWND, LPCWSTR, LPCWSTR) = NULL; + + if (!hModule) + { + if (!firstTime) return FALSE; + firstTime = FALSE; + hModule = LoadLibraryW(L"UxTheme.dll"); + if (!hModule) return FALSE; + __setwintheme = (HRESULT (WINAPI *)(HWND, LPCWSTR, LPCWSTR))GetProcAddress(hModule, "SetWindowTheme"); + if (!__setwintheme) + { + FreeLibrary(hModule); + hModule = NULL; + return FALSE; + } + } + return (S_OK == __setwintheme(hwnd, NULL, ((bEnable) ? NULL : L""))); +} + +static HRESULT InitializeProgressIcon(PROGRESSICON *pIcon) +{ + HRESULT hr; + LONG/*_PTR*/ lVal; // benski> windows 64 isn't supported by gracenote + + ICddbUIOptions *pUIOptions; + + if (!pIcon) return E_INVALIDARG; + ZeroMemory(pIcon, sizeof(PROGRESSICON)); + + hr = Cddb_GetIUIOptions((void**)&pUIOptions); + if (FAILED(hr)) return hr; + + hr = pUIOptions->GetCurrent(UI_DISP_PROGRESS); + if (SUCCEEDED(hr)) + { + if (SUCCEEDED(pUIOptions->get_ResourceHINSTANCE(&lVal))) pIcon->hInstance = (HINSTANCE)lVal; + if (SUCCEEDED(pUIOptions->get_Left(&lVal))) pIcon->rc.left = (LONG)lVal; + if (SUCCEEDED(pUIOptions->get_Top(&lVal))) pIcon->rc.top = (LONG)lVal; + if (SUCCEEDED(pUIOptions->get_Right(&lVal))) pIcon->rc.right = (LONG)lVal; + if (SUCCEEDED(pUIOptions->get_Bottom(&lVal))) pIcon->rc.bottom = (LONG)lVal; + pUIOptions->get_ProgressResourceID(&pIcon->resId); + pUIOptions->get_Frames(&pIcon->frames); + } + pUIOptions->Release(); + return hr; +} + +static BOOL AnimateProgressIcon(HDC hdc, INT x, INT y, PROGRESSICON *pIcon) +{ + INT w, h; + HDC hdcDst; + HBITMAP bmpOld; + + if (!hdc || !pIcon) return FALSE; + + if (!pIcon->hbmp) + { + if (pIcon->hInstance && pIcon->resId) + { + pIcon->hbmp = (HBITMAP)LoadImageW(pIcon->hInstance, MAKEINTRESOURCEW(pIcon->resId), + IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE); + } + if (!pIcon->hbmp) return FALSE; + } + + hdcDst = CreateCompatibleDC(hdc); + bmpOld = (HBITMAP)SelectObject(hdcDst, pIcon->hbmp); + + w = pIcon->rc.right - pIcon->rc.left; + h = pIcon->rc.bottom - pIcon->rc.top; + + BitBlt(hdc, x, y, w, h, hdcDst, pIcon->rc.left + (pIcon->step * w), pIcon->rc.top, SRCCOPY); + SelectObject(hdcDst, bmpOld); + DeleteDC(hdcDst); + return TRUE; +} + +static void CALLBACK ProgressDlg_OnTimer(HWND hwnd, UINT uMsg, UINT_PTR evntId, DWORD dwTime) +{ + PROGRESSDLG *pDlg; + pDlg = GET_DATA(hwnd); + if (!pDlg) return; + + DWORD tclose; + switch(evntId) + { + case TIMER_PROGRESS_ANIMATE_ID: + InvalidateLogo(hwnd, &pDlg->icon); + if (++pDlg->icon.step >= pDlg->icon.frames) + { + KillTimer(hwnd, evntId); + pDlg->icon.step = pDlg->icon.frames - 1; + } + break; + case TIMER_PROGRESS_DESTROY_ID: + tclose = pDlg->dwAutoClose; + if (dwTime >= tclose) + { + KillTimer(hwnd, evntId); + EndProgressDialog(hwnd); + } + else + { + wchar_t szText[128] = {0}; + tclose = (tclose - dwTime)/1000 + ((((tclose - dwTime)%1000) > 500) ? 1 : 0); + StringCchPrintfW(szText, sizeof(szText)/sizeof(wchar_t), L"%s (%d)", GET_SAFE_LANGSTRING1(MAKEINTRESOURCEW(IDS_CLOSE)), tclose); + SetDlgItemTextW(hwnd, IDCANCEL, szText); + } + break; + } +} + +static BOOL EndProgressDialog(HWND hwnd) +{ + PROGRESSDLG *pDlg; + pDlg = GET_DATA(hwnd); + if (!pDlg) return FALSE; + + if (MODAL_ACTIVE == pDlg->Modal) + { + pDlg->Modal = MODAL_DESTROY; + PostMessageW(hwnd, WM_NULL, 0, 0); + } + else + { + DestroyWindow(hwnd); + } + return TRUE; +} + +static INT_PTR ProgressDlg_OnDialogInit(HWND hwnd, HWND hwndFocus, LPARAM lParam) +{ + HWND hwndCtrl; + PROGRESSDLG *pDlg(NULL); + + pDlg = (PROGRESSDLG*)calloc(1, sizeof(PROGRESSDLG)); + if (pDlg) + { + pDlg->uState = STATE_INACTIVE; + if ((FAILED(InitializeProgressIcon(&pDlg->icon)) || !SetPropW(hwnd, PROP_PRGDLG, pDlg))) + { + free(pDlg); + pDlg = NULL; + DestroyWindow(hwnd); + return TRUE; + } + } + hwndCtrl = GetDlgItem(hwnd, IDC_PRG_STATUS); + if (hwndCtrl) + { + RECT rc; + EnableWindowTheme(hwndCtrl, FALSE); + SetWindowLongPtrW(hwndCtrl, GWL_EXSTYLE, GetWindowLongPtrW(hwndCtrl, GWL_EXSTYLE) & ~WS_EX_STATICEDGE); + GetWindowRect(hwndCtrl, &rc); + SetWindowPos(hwndCtrl, NULL, 0, 0, rc.right - rc.left, 3, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER); + SendMessageW(hwndCtrl, PBM_SETRANGE, 0, MAKELPARAM(0, 100)); + SendMessageW(hwndCtrl, PBM_SETPOS, 0, 0L); + SendMessageW(hwndCtrl, PBM_SETSTEP, 1, 0L); + SendMessageW(hwndCtrl, PBM_SETBARCOLOR, 0, (LPARAM)GetSysColor(COLOR_WINDOW)); + SendMessageW(hwndCtrl, PBM_SETBKCOLOR, 0, (LPARAM)GetSysColor(COLOR_WINDOWTEXT)); + } + hwndCtrl = GetDlgItem(hwnd, IDC_LBL_STATUS); + if(hwndCtrl) + { + if (!hFont) + { + HFONT hf; + LOGFONT lf; + + hf = (HFONT)SendMessageW(hwndCtrl, WM_GETFONT, 0, 0L); + if (hf) hf = (HFONT)GetStockObject(DEFAULT_GUI_FONT); + if (hf && GetObject(hf, sizeof(LOGFONT), &lf)) + { + HDC hdc = GetDC(hwndCtrl); + lf.lfHeight = (hdc) ? -MulDiv(7, GetDeviceCaps(hdc, LOGPIXELSY), 72) : -9; + lf.lfWeight = FW_THIN; + lf.lfQuality = PROOF_QUALITY; + StringCchCopy(lf.lfFaceName, sizeof(lf.lfFaceName)/sizeof(*lf.lfFaceName), L"Arial"); + hFont = CreateFontIndirect(&lf); + if (hdc) ReleaseDC(hwnd, hdc); + } + } + if (hFont) + { + SendMessageW(hwndCtrl, WM_SETFONT, (WPARAM)hFont, (LPARAM)TRUE); + SendDlgItemMessageW(hwnd, IDC_LBL_REASON, WM_SETFONT, (WPARAM)hFont, (LPARAM)TRUE); + fontRef++; + } + } + + if (NULL != (hwndCtrl = GetDlgItem(hwnd, IDC_LV_EXT))) + { + DWORD exstyle = LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP; + SendMessageW(hwndCtrl, LVM_SETEXTENDEDLISTVIEWSTYLE, exstyle, exstyle); + LVCOLUMNW column; + column.mask = LVCF_WIDTH | LVCF_TEXT; + column.cx = 120; + column.pszText = L"Artist"; + SendMessageW(hwndCtrl, LVM_INSERTCOLUMNW, (WPARAM)0xEFFF, (LPARAM)&column); + column.cx = 160; + column.pszText = L"Album"; + SendMessageW(hwndCtrl, LVM_INSERTCOLUMNW, (WPARAM)0xEFFF, (LPARAM)&column); + column.cx = 40; + column.pszText = L"Language"; + SendMessageW(hwndCtrl, LVM_INSERTCOLUMNW, (WPARAM)0xEFFF, (LPARAM)&column); + } + + return TRUE; +} + +static void ProgressDlg_OnDestroy(HWND hwnd) +{ + PROGRESSDLG *pDlg; + pDlg = GET_DATA(hwnd); + if (pDlg) + { + RemovePropW(hwnd, PROP_PRGDLG); + if (pDlg->icon.hbmp) DeleteObject(pDlg->icon.hbmp); + if (pDlg->Btn1Data) SysFreeString(pDlg->Btn1Data); + if (pDlg->AbortData) SysFreeString(pDlg->AbortData); + + free(pDlg); + pDlg = NULL; + } + + if (fontRef && 0 == --fontRef) + { + DeleteObject(hFont); + hFont = NULL; + } +} + +static void ProgressDlg_OnCommand(HWND hwnd, WORD ctrlId, WORD evntId, HWND hwndCtrl) +{ + PROGRESSDLG *pDlg; + + pDlg = GET_DATA(hwnd); + if (!pDlg) return; + + switch(ctrlId) + { + case IDCANCEL: + pDlg->rCode = S_FALSE; + switch(pDlg->uState) + { + case STATE_ACTIVE: + if (!pDlg->OnAbort) return; + SetWindowTextW(hwndCtrl, WASABI_API_LNGSTRINGW(IDS_ABORTING)); + EnableWindow(hwndCtrl, FALSE); + pDlg->uState = STATE_ABORTING; + pDlg->OnAbort(hwnd, pDlg->AbortData); + return; + case STATE_ABORTING: return; // don't do anything + } + ; + EndProgressDialog(hwnd); + break; + case IDC_BUTTON1: + if (BN_CLICKED == evntId) + { + if (pDlg->OnButton1) pDlg->OnButton1(hwnd, pDlg->Btn1Data); + } + break; + } +} + +static void ProgressDlg_OnErase(HWND hwnd, HDC hdc) +{ + PROGRESSDLG *pDlg; + + if (NULL != (pDlg = GET_DATA(hwnd))) + { + RECT rc; + SetRect(&rc, ICON_OFFSET_X, ICON_OFFSET_Y, + ICON_OFFSET_X + (pDlg->icon.rc.right - pDlg->icon.rc.left), + ICON_OFFSET_Y + (pDlg->icon.rc.bottom - pDlg->icon.rc.top)); + if (RectVisible(hdc, &rc)) + { + if (AnimateProgressIcon(hdc, ICON_OFFSET_X, ICON_OFFSET_Y, &pDlg->icon)) + ExcludeClipRect(hdc, rc.left, rc.top, rc.right, rc.bottom); + } + } +} + +static void ProgressDlg_OnLButtonDown(HWND hwnd, DWORD wKey, POINTS pts) +{ + PROGRESSDLG *pDlg = GET_DATA(hwnd); + if (pDlg) + { + POINT pt; + RECT rc; + POINTSTOPOINT(pt, pts); + SetRect(&rc, ICON_OFFSET_X, ICON_OFFSET_Y, + ICON_OFFSET_X + (pDlg->icon.rc.right - pDlg->icon.rc.left), + ICON_OFFSET_Y + (pDlg->icon.rc.bottom - pDlg->icon.rc.top)); + + if (PtInRect(&rc, pt)) SendMessageW(line.hMainWindow, WM_WA_IPC, (WPARAM)L"http://www.cddb.com/", IPC_OPEN_URL); + } +} + +static INT_PTR ProgressDlg_OnSetCursor(HWND hwnd, HWND hwndCursor, WORD htCode, WORD msgId) +{ + PROGRESSDLG *pDlg = GET_DATA(hwnd); + if (pDlg) + { + RECT rc; + POINT pt; + GetCursorPos(&pt); + MapWindowPoints(HWND_DESKTOP, hwnd, &pt, 1); + SetRect(&rc, ICON_OFFSET_X, ICON_OFFSET_Y, + ICON_OFFSET_X + (pDlg->icon.rc.right - pDlg->icon.rc.left), + ICON_OFFSET_Y + (pDlg->icon.rc.bottom - pDlg->icon.rc.top)); + if (PtInRect(&rc, pt)) return (NULL != SetCursor(LoadCursor(NULL, IDC_HAND))); + } + return FALSE; +} + +static INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: return ProgressDlg_OnDialogInit(hwndDlg, (HWND)wParam, lParam); + case WM_DESTROY: ProgressDlg_OnDestroy(hwndDlg); break; + case WM_COMMAND: ProgressDlg_OnCommand(hwndDlg, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); break; + case WM_ERASEBKGND: ProgressDlg_OnErase(hwndDlg, (HDC)wParam); break; + case WM_LBUTTONDOWN: ProgressDlg_OnLButtonDown(hwndDlg, (DWORD)wParam, MAKEPOINTS(lParam)); break; + case WM_SETCURSOR: return ProgressDlg_OnSetCursor(hwndDlg, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)); + } + return 0; +} + +static BOOL CALLBACK EnumWnd_OnNextWindow(HWND hwnd, LPARAM lParam) +{ + ENUMWND_DATAPACK *pData = (ENUMWND_DATAPACK*)lParam; + if (!pData) return FALSE; + + if (!pData->found) + { + if (pData->host == hwnd) pData->found = TRUE; + // return TRUE; + } + ULONG_PTR style = GetWindowLongPtrW(hwnd, GWL_STYLE); + if (0 == (WS_CHILD & style) && + (0 == (FINDWND_ONLY_VISIBLE & pData->flags) || (WS_VISIBLE & style)) && + (0 == (FINDWND_ONLY_ENABLED & pData->flags) || 0 == (WS_DISABLED & style))) + { + HWND hwndOwner = GetWindow(hwnd, GW_OWNER); + if (pData->host == hwndOwner) + { + if (pData->index == pData->count) return FALSE; /// + pData->list[pData->index] = hwnd; + pData->index++; + } + } + return TRUE; +} + +BOOL FindAllOwnedWindows(HWND hwndHost, HWND *hwndList, INT cList, UINT flags) +{ + BOOL br; + ENUMWND_DATAPACK data; + + ZeroMemory(&data, sizeof(ENUMWND_DATAPACK)); + if (!hwndHost || !hwndList) return FALSE; + + data.host = hwndHost; + data.list = hwndList; + data.count = cList; + data.flags = flags; + data.list[0] = NULL; + br = EnumWindows(EnumWnd_OnNextWindow, (LPARAM)&data); + data.list[data.index] = NULL; + return br; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/cddbui.h b/Src/Plugins/Input/in_cdda/cddbui.h new file mode 100644 index 00000000..d4e0a60d --- /dev/null +++ b/Src/Plugins/Input/in_cdda/cddbui.h @@ -0,0 +1,46 @@ +#ifndef NULLSOFT_CDDB_UI_HEADER +#define NULLSOFT_CDDB_UI_HEADER + + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +#pragma once +#endif + +#include <windows.h> + +#define AUTOCLOSE_NOW 0x00000000 +#define AUTOCLOSE_NEVER 0xFFFFFFFF + + +#define STATE_INACTIVE ((UINT)0) +#define STATE_ACTIVE ((UINT)1) +#define STATE_COMPLETED ((UINT)2) +#define STATE_ABORTING ((UINT)3) + +typedef void (CALLBACK *CDDBDLG_ONBTNCLICK)(HWND /*hwndDlg*/, BSTR /*bstrUser*/); // return TRUE to close dialog or FALSE to stay in STATE_ABORTING + +// all functions can accept String IDS and will resolve it using WASABI_API_LNGSTRINGW +HWND CddbProgressDlg_Create(HWND hwndParent, INT nCmdShow); +BOOL CddbProgressDlg_Initialize(HWND hwnd, LPCWSTR pszCaption, CDDBDLG_ONBTNCLICK fnOnAbort, BSTR bstrAbortUser); // +BOOL CddbProgressDlg_Completed(HWND hwnd, LPCWSTR pszResult, LPCWSTR pszReason, DWORD nAutoCloseDelay, HRESULT rCode); +BOOL CddbProgressDlg_SetStatus(HWND hwnd, LPCWSTR pszStatus, INT nPercentCompleted); +BOOL CddbProgressDlg_EnableAbortButton(HWND hwnd, BOOL bEnable); +BOOL CddbProgressDlg_ShowButton1(HWND hwnd, LPCWSTR pszCaption, CDDBDLG_ONBTNCLICK fnOnButton1, BSTR bstrUser); // set pszCaption = NULL and/or fnOnButton1 = NULL to hide it +UINT CddbProgressDlg_GetState(HWND hwnd); +BOOL CddbProgressDlg_SetUserData(HWND hwnd, HANDLE user); +HANDLE CddbProgressDlg_GetUserData(HWND hwnd); +BOOL CddbProgressDlg_ShowInTaskbar(HWND hwnd, BOOL bShow); +BOOL CddbProgressDlg_SetExtendedMode(HWND hwnd, BOOL bEnable); +BOOL CddbProgressDlg_AddRecord(HWND hwnd, LPCWSTR pszArtist, LPCWSTR pszTitle, LPCWSTR pszLanguage); +INT CddbProgressDlg_GetSelRecordIndex(HWND hwnd); +HRESULT CddbProgressDlg_DoModal(HWND hwnd, RECT *prc); // if prc != NULL will contain window rect before it closed +BOOL CddbProgressDlg_ExitModal(HWND hwnd, HRESULT rCode, BOOL bDestroy); /// exits modal loop without destroying window +BOOL CddbProgressDlg_IsModal(HWND hwnd); + + +#define FINDWND_ONLY_VISIBLE 0x01 +#define FINDWND_ONLY_ENABLED 0x02 + +BOOL FindAllOwnedWindows(HWND hwndHost, HWND *hwndList, INT cList, UINT flags); + +#endif //NULLSOFT_CDDB_UI_HEADER diff --git a/Src/Plugins/Input/in_cdda/discid.cpp b/Src/Plugins/Input/in_cdda/discid.cpp new file mode 100644 index 00000000..0ffbc06e --- /dev/null +++ b/Src/Plugins/Input/in_cdda/discid.cpp @@ -0,0 +1,168 @@ +#include "main.h" +#include "cddb.h" +#include <strsafe.h> + +/* +* cddb_sum +* Convert an integer to its text string representation, and +* compute its checksum. Used by cddb_discid to derive the +* disc ID. +* +* Args: +* n - The integer value. +* +* Return: +* The integer checksum. +*/ +int cddb_sum(int n) +{ + char buf[12], + *p; + int ret = 0; + + /* For backward compatibility this algorithm must not change */ + StringCchPrintfA(buf, 12, "%lu", n); + for (p = buf; *p != '\0'; p++) + ret += (*p - '0'); + + return (ret); +} + +/* +* cddb_discid +* Compute a magic disc ID based on the number of tracks, +* the length of each track, and a checksum of the string +* that represents the offset of each track. +* +* Return: +* The integer disc ID. +*/ + +unsigned long cddb_discid(unsigned char nTracks, unsigned int* pnMin, unsigned int* pnSec) +{ + int i, + t = 0, + n = 0; + + /* For backward compatibility this algorithm must not change */ + for (i = 0; i < (int) nTracks; i++) + { + n += cddb_sum((pnMin[i] * 60) + pnSec[i]); + + t += ((pnMin[i + 1] * 60) + pnSec[i + 1]) - ((pnMin[i] * 60) + pnSec[i]); + } + + return ((n % 0xff) << 24 | t << 8 | nTracks); +} + +// Functions used to generate the CDDB id + +void CDGetEndFrame(MCIDEVICEID wDeviceID, + DINFO* psDI, + unsigned int nOffset, + unsigned int* pnFrame, + unsigned int* pnMin, + unsigned int* pnSec) +{ + MCI_STATUS_PARMS sMCIStatus; + + sMCIStatus.dwItem = MCI_STATUS_LENGTH; + MCISendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, + (DWORD_PTR)(LPVOID) &sMCIStatus); +#ifdef _WIN64 + *pnFrame = (unsigned int)((double)sMCIStatus.dwReturn * (75.0 / 1000.0)); +#else + { + int nFrame; + static double tmp = 75.0 / 1000.0; + unsigned long a = sMCIStatus.dwReturn; + __asm + { + fld qword ptr tmp + fild dword ptr a + fmul + fistp dword ptr nFrame + } + *pnFrame = nFrame; + } +#endif + pnFrame[0] += 1 + nOffset; // Due to bug in MCI according to CDDB docs! + + psDI->nDiscLength = (pnFrame[0] / 75); + + *pnMin = pnFrame[0] / 75 / 60; + *pnSec = (pnFrame[0] / 75) % 60; +} + + +void CDGetAbsoluteTrackPos(MCIDEVICEID wDeviceID, + unsigned int nTrack, + unsigned int* pnFrame, + unsigned int* pnMin, + unsigned int* pnSec) +{ + MCI_STATUS_PARMS sMCIStatus; + + sMCIStatus.dwItem = MCI_STATUS_POSITION; + sMCIStatus.dwTrack = nTrack; + MCISendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK | MCI_WAIT, + (DWORD_PTR)(LPVOID) &sMCIStatus); +#ifdef _WIN64 + *pnFrame = (int)((double)sMCIStatus.dwReturn * (75.0 / 1000.0)); +#else + { + static double tmp = 75.0 / 1000.0; + unsigned long a = sMCIStatus.dwReturn; + __asm + { + fld qword ptr tmp + fild dword ptr a + fmul + fistp dword ptr a + } + *pnFrame = a; + } +#endif + + + *pnMin = *pnFrame / 75 / 60; + *pnSec = (*pnFrame / 75) % 60; +} + +int GetDiscID(MCIDEVICEID wDeviceID, DINFO* psDI) +{ + MCI_SET_PARMS sMCISet; + unsigned int nLoop; + unsigned int nMCITracks = CDGetTracks(wDeviceID); + unsigned int* pnMin = NULL; + unsigned int* pnSec = NULL; + + if (nMCITracks > 65535) return 1; + + if (nMCITracks > 128) nMCITracks = 128; + psDI->ntracks = nMCITracks; + + pnMin = (unsigned int*)GlobalAlloc(GPTR, (nMCITracks + 1) * 2 * sizeof(unsigned int)); + if (!pnMin) return 1; + + pnSec = pnMin + (nMCITracks + 1); + + sMCISet.dwTimeFormat = MCI_FORMAT_MILLISECONDS; + MCISendCommand(wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)(LPVOID) &sMCISet); + + for (nLoop = 0 ; nLoop < nMCITracks; nLoop ++) + CDGetAbsoluteTrackPos(wDeviceID, nLoop + 1, &psDI->pnFrames[nLoop], &pnMin[nLoop], &pnSec[nLoop]); + + CDGetEndFrame(wDeviceID, psDI, psDI->pnFrames[0], &psDI->pnFrames[nLoop], &pnMin[nLoop], &pnSec[nLoop]); + + psDI->CDDBID = cddb_discid((unsigned char)nMCITracks, pnMin, pnSec); + + sMCISet.dwTimeFormat = MCI_FORMAT_TMSF; + MCISendCommand(wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)(LPVOID) &sMCISet); + + if (pnMin) + { + GlobalFree(pnMin); + } + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/grabwnd.cpp b/Src/Plugins/Input/in_cdda/grabwnd.cpp new file mode 100644 index 00000000..71341fcd --- /dev/null +++ b/Src/Plugins/Input/in_cdda/grabwnd.cpp @@ -0,0 +1,74 @@ +#include ".\grabwnd.h" +#include <strsafe.h> + + +#define LCID_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT) +typedef struct _GRABDATA +{ + HHOOK hook; + HWND hwndParent; + WCHAR szClassName[256]; + WCHAR szTitle[256]; + GRABCB callback; + ULONG_PTR user; +} GRABDATA; + +static GRABDATA g_grab = {0, 0, }; + +static BOOL IsTargetClass(HWND hwnd, LPCWSTR plzClassName) +{ + wchar_t szName[256] = {0}; + return (0x00 == plzClassName[0] || + (GetClassNameW(hwnd, szName, sizeof(szName)/sizeof(wchar_t)) && + CSTR_EQUAL == CompareStringW(LCID_INVARIANT, NORM_IGNORECASE, szName, -1, plzClassName, -1))); +} + +static BOOL IsTargetTitle(HWND hwnd, LPCWSTR pszTitle) +{ + wchar_t szName[256] = {0}; + return (0x00 == pszTitle[0] || + (GetWindowTextW(hwnd, szName, sizeof(szName)/sizeof(wchar_t)) && + CSTR_EQUAL == CompareStringW(LCID_INVARIANT, NORM_IGNORECASE, szName, -1, pszTitle, -1))); +} +static LRESULT CALLBACK HookProc(int code, WPARAM wParam, LPARAM lParam) +{ + if (HCBT_CREATEWND == code && IsTargetClass((HWND)wParam, g_grab.szClassName) && + IsTargetTitle((HWND)wParam, g_grab.szTitle) && + (!g_grab.hwndParent || ((CBT_CREATEWND*)lParam)->lpcs->hwndParent == g_grab.hwndParent)) + { + LRESULT result; + if (g_grab.callback) + { + g_grab.callback((HWND)wParam, ((CBT_CREATEWND*)lParam)->lpcs, &((CBT_CREATEWND*)lParam)->hwndInsertAfter, g_grab.user); + } + result = CallNextHookEx(g_grab.hook, code, wParam, lParam); + UnhookWindowsHookEx(g_grab.hook); + + + ZeroMemory(&g_grab, sizeof(GRABDATA)); + return result; + } + return CallNextHookEx(g_grab.hook, code, wParam, lParam); +} + +BOOL BeginGrabCreateWindow(LPCWSTR pszClassName, LPCWSTR pszTitle, HWND hwndParent, GRABCB callback, ULONG_PTR user) +{ + if (g_grab.hook || !callback) return FALSE; + if (pszClassName) StringCchCopyW(g_grab.szClassName, sizeof(g_grab.szClassName)/sizeof(wchar_t), pszClassName); + else g_grab.szClassName[0] = 0x00; + if (pszTitle) StringCchCopyW(g_grab.szTitle, sizeof(g_grab.szTitle)/sizeof(wchar_t), pszTitle); + else g_grab.szTitle[0] = 0x00; + g_grab.hwndParent = hwndParent; + g_grab.callback = callback; + g_grab.user = user; + + g_grab.hook = SetWindowsHookEx(WH_CBT, HookProc, NULL, GetCurrentThreadId()); + if (!g_grab.hook) ZeroMemory(&g_grab, sizeof(GRABDATA)); + + return (NULL != g_grab.hook); +} +void EndGrabCreateWindow(void) +{ + if (g_grab.hook) UnhookWindowsHookEx(g_grab.hook); + ZeroMemory(&g_grab, sizeof(GRABDATA)); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/grabwnd.h b/Src/Plugins/Input/in_cdda/grabwnd.h new file mode 100644 index 00000000..290f0b66 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/grabwnd.h @@ -0,0 +1,18 @@ +#ifndef NULLSOFT_GRAB_WINDOW_HEADER +#define NULLSOFT_GRAB_WINDOW_HEADER + + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +#pragma once +#endif + +#include <windows.h> + +typedef void (CALLBACK *GRABCB)(HWND /*hwnd*/, CREATESTRUCT* /*lpcs*/, HWND* /*phwndInsertAfter*/, ULONG_PTR /*user*/); + +BOOL BeginGrabCreateWindow(LPCWSTR pszClassName, LPCWSTR pszTitle, HWND hwndParent, GRABCB callback, ULONG_PTR user); // you can skip fields that you don't need +void EndGrabCreateWindow(void); //always call it when you done to gurantee proper shutdown + + + +#endif //NULLSOFT_GRAB_WINDOW_HEADER
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/in_cdda.rc b/Src/Plugins/Input/in_cdda/in_cdda.rc new file mode 100644 index 00000000..9965aae9 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/in_cdda.rc @@ -0,0 +1,366 @@ +// 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 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +#if defined(APSTUDIO_INVOKED) || defined(ORIGINAL) +#if defined(APSTUDIO_INVOKED) +IDD_DIALOG1$(ORIGINAL) DIALOGEX 0, 0, 173, 146 +#else +IDD_DIALOG1 DIALOGEX 0, 0, 173, 146 +#endif +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "CD playback settings" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "Digital Audio Extraction",IDC_STATIC,4,4,164,50 + CONTROL "Enable digital audio extraction if possible",IDC_DIGITAL, + "Button",BS_AUTOCHECKBOX | BS_MULTILINE | WS_TABSTOP,11,17,153,14 + CONTROL "Use Sonic engine when possible",IDC_VERITAS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,33,142,14 + GROUPBOX "Sampling (when not in DAE mode)",IDC_STATIC,4,57,164,67 + CONTROL "&Sample input from soundcard (for vis)\n(requires 44khz 16bit stereo capability)",IDC_SAMPLE, + "Button",BS_AUTOCHECKBOX | BS_MULTILINE | WS_TABSTOP,11,67,153,24 + LTEXT "(Note: be sure to set up your soundcard sampling input to the correct input source in order to have visualizations working)",IDC_STATIC,17,92,138,28 + GROUPBOX "MusicID",IDC_STATIC,233,12,82,84,NOT WS_VISIBLE + CONTROL "Use MusicID",IDC_CDDB,"Button",BS_AUTOCHECKBOX | NOT WS_VISIBLE | WS_TABSTOP,241,23,57,10 + LTEXT "The MusicID components are not installed. Please get Winamp Full or Pro to enable MusicID support.",IDC_CDDBNOTE,241,36,65,50,NOT WS_VISIBLE + CONTROL "",IDC_CDDBICON,"Static",SS_OWNERDRAW | SS_NOTIFY | SS_REALSIZEIMAGE | NOT WS_VISIBLE,247,39,15,13 + DEFPUSHBUTTON "OK",IDOK,64,129,50,13 + PUSHBUTTON "Cancel",IDCANCEL,118,129,50,13 +END +#endif + +#if defined(APSTUDIO_INVOKED) || defined(IGNORE_API_GRACENOTE) +#if defined(APSTUDIO_INVOKED) +IDD_DIALOG2$(IGNORE_API_GRACENOTE) DIALOGEX 0, 0, 247, 68 +#else +IDD_DIALOG2 DIALOGEX 0, 0, 247, 68 +#endif +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Retrieving CD information..." +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + CONTROL "",IDC_CDDBICON,"Static",SS_OWNERDRAW | SS_NOTIFY,7,7,16,14 + LTEXT "MusicID brought to you by Gracenote",IDC_STATIC,67,8,167,9 + LTEXT "",IDC_STATUS,67,20,173,18,SS_SUNKEN + PUSHBUTTON "Abort",ID_ABORTBABY,67,41,50,14 +END +#endif + +#if defined(APSTUDIO_INVOKED) || defined(DISABLED) +#if defined(APSTUDIO_INVOKED) +IDD_PREFS_CDRIP$(DISABLED) DIALOGEX 0, 0, 260, 226 +#else +IDD_PREFS_CDRIP DIALOGEX 0, 0, 260, 226 +#endif +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + GROUPBOX "Ripping Speed",IDC_STATIC,4,3,256,70 + LTEXT "As Winamp rips your CDs to files, it must read the audio data from the CD. The unit of speed used is 1x, which means that the audio is extracted at the ",IDC_STATIC,11,14,244,16 + LTEXT "same rate as if you were listening to it. Choose what speed you would like to rip your CDs at (higher speeds require less time to rip):",IDC_STATIC,12,30,243,17 + LTEXT "Maximum speed at which Winamp should rip CDs:",IDC_STATIC,12,55,158,8 + COMBOBOX IDC_COMBO1,171,53,59,84,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Advanced Ripping Settings",IDC_STATIC,4,77,256,52 + CONTROL "Read audio data from CDs using bundled Sonic extraction engine",IDC_VERITAS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,90,221,10 + LTEXT "Disabling this option will instruct Winamp to attempt to rip via a third party ASPI driver or, if not available, using the native NT SCSI API.",IDC_STATIC,24,104,229,19 +END +#endif + +#if defined(APSTUDIO_INVOKED) || defined(IGNORE_API_GRACENOTE) +#if defined(APSTUDIO_INVOKED) +IDD_CDDB_PROGRESS$(IGNORE_API_GRACENOTE) DIALOGEX 0, 0, 250, 66 +#else +IDD_CDDB_PROGRESS DIALOGEX 0, 0, 250, 66 +#endif +STYLE DS_SETFONT | DS_MODALFRAME | DS_NOIDLEMSG | DS_FIXEDSYS | DS_NOFAILCREATE | WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_NOPARENTNOTIFY | WS_EX_CONTROLPARENT +CAPTION "Retrieving CD information..." +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + CONTROL "",IDC_LBL_CAPTION,"Static",SS_LEFTNOWORDWRAP | SS_NOPREFIX | SS_WORDELLIPSIS | WS_GROUP,70,8,172,10 + PUSHBUTTON "&Abort",IDCANCEL,182,43,60,15 + CONTROL "",IDC_LBL_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_NOPREFIX | WS_GROUP,70,17,172,8 + CONTROL "",IDC_PRG_STATUS,"msctls_progress32",PBS_SMOOTH | NOT WS_VISIBLE,70,25,111,6 + LTEXT "",IDC_LBL_REASON,70,25,111,8,NOT WS_VISIBLE + PUSHBUTTON "Button1",IDC_BUTTON1,113,43,60,15,NOT WS_VISIBLE + CONTROL "",IDC_LV_EXT,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | NOT WS_VISIBLE | WS_BORDER | WS_TABSTOP,8,58,234,7 +END +#endif + +#if defined(APSTUDIO_INVOKED) || defined(IGNORE_API_GRACENOTE) +#if defined(APSTUDIO_INVOKED) +IDD_MUSICID$(IGNORE_API_GRACENOTE) DIALOGEX 0, 0, 341, 164 +#else +IDD_MUSICID DIALOGEX 0, 0, 341, 164 +#endif +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + PUSHBUTTON "Lookup",IDC_LOOKUP,231,146,50,14 + PUSHBUTTON "Submit",IDC_SUBMIT,175,146,50,14 + PUSHBUTTON "Edit",IDC_EDIT_GRACENOTE,119,146,50,14 + PUSHBUTTON "Use",IDC_USE,287,146,50,14 + LTEXT "Title",IDC_STATIC,7,9,40,14,SS_CENTERIMAGE,WS_EX_RIGHT + LTEXT "Album Artist",IDC_STATIC,7,27,40,14,SS_CENTERIMAGE,WS_EX_RIGHT + LTEXT "Disc",IDC_STATIC,7,44,40,14,SS_CENTERIMAGE,WS_EX_RIGHT + LTEXT "Year",IDC_STATIC,7,61,40,14,SS_CENTERIMAGE,WS_EX_RIGHT + LTEXT "Label",IDC_STATIC,7,78,40,14,SS_CENTERIMAGE,WS_EX_RIGHT + LTEXT "of",IDC_STATIC,83,44,8,14,SS_CENTERIMAGE + LTEXT "Primary Genre",IDC_STATIC,172,27,48,14,SS_CENTERIMAGE,WS_EX_RIGHT + GROUPBOX "Notes",IDC_STATIC,6,94,177,50 + GROUPBOX "Track List",IDC_STATIC,186,42,152,102 + LISTBOX IDC_TRACKLIST,191,52,142,87,LBS_NOINTEGRALHEIGHT | LBS_NOSEL | WS_VSCROLL | WS_TABSTOP + EDITTEXT IDC_TITLE,55,9,281,14,ES_AUTOHSCROLL | ES_READONLY + EDITTEXT IDC_ARTIST,55,27,110,14,ES_AUTOHSCROLL | ES_READONLY + EDITTEXT IDC_DISC,55,44,24,14,ES_AUTOHSCROLL | ES_READONLY + EDITTEXT IDC_DISCS,96,44,24,14,ES_AUTOHSCROLL | ES_READONLY + EDITTEXT IDC_YEAR,55,61,65,14,ES_AUTOHSCROLL | ES_READONLY + EDITTEXT IDC_LABEL,55,78,105,14,ES_AUTOHSCROLL | ES_READONLY + EDITTEXT IDC_GENRE,227,27,109,14,ES_AUTOHSCROLL | ES_READONLY + EDITTEXT IDC_NOTES,13,103,164,36,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + GROUPBOX "MusicID",IDC_STATIC,0,0,341,164 +END +#endif + +IDD_CDTEXT DIALOGEX 0, 0, 341, 164 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "CD Text",IDC_STATIC,0,0,341,164 + LTEXT "Artist:",IDC_STATIC,3,9,21,8 + EDITTEXT IDC_ARTIST,3,19,109,14,ES_AUTOHSCROLL + LTEXT "Album:",IDC_STATIC,116,9,23,8 + EDITTEXT IDC_ALBUM,116,19,109,14,ES_AUTOHSCROLL + LTEXT "Composer:",IDC_STATIC,228,9,36,8 + EDITTEXT IDC_COMPOSER,228,19,109,14,ES_AUTOHSCROLL + CONTROL "",IDC_TRACKS,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,4,36,333,106 + PUSHBUTTON "Lookup",IDC_LOOKUP,233,146,50,14 + PUSHBUTTON "Use",IDC_USE,287,146,50,14 +END + +#if defined(APSTUDIO_INVOKED) || defined(IGNORE_API_GRACENOTE) +#if defined(APSTUDIO_INVOKED) +IDD_DIALOG1$(IGNORE_API_GRACENOTE) DIALOGEX 0, 0, 173, 216 +#else +IDD_DIALOG1 DIALOGEX 0, 0, 173, 216 +#endif +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "CD playback settings" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "Digital Audio Extraction",IDC_STATIC,4,4,164,50 + CONTROL "Enable digital audio extraction if possible",IDC_DIGITAL, + "Button",BS_AUTOCHECKBOX | BS_MULTILINE | WS_TABSTOP,11,17,153,14 + CONTROL "Use Sonic engine when possible",IDC_VERITAS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,33,142,14 + GROUPBOX "Sampling (when not in DAE mode)",IDC_STATIC,4,57,164,67 + CONTROL "&Sample input from soundcard (for vis)\n(requires 44khz 16bit stereo capability)",IDC_SAMPLE, + "Button",BS_AUTOCHECKBOX | BS_MULTILINE | WS_TABSTOP,11,67,153,24 + LTEXT "(Note: be sure to set up your soundcard sampling input to the correct input source in order to have visualizations working)",IDC_STATIC,17,92,138,28 + GROUPBOX "MusicID",IDC_STATIC,4,127,82,84 + CONTROL "Use MusicID",IDC_CDDB,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,138,57,10 + LTEXT "The MusicID components are not installed. Please get Winamp Full or Pro to enable MusicID support.",IDC_CDDBNOTE,12,151,65,50,NOT WS_VISIBLE + CONTROL "",IDC_CDDBICON,"Static",SS_OWNERDRAW | SS_NOTIFY | SS_REALSIZEIMAGE,17,153,15,13 + DEFPUSHBUTTON "OK",IDOK,117,178,50,13 + PUSHBUTTON "Cancel",IDCANCEL,117,197,50,13 +END +#endif + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + "IDD_DIALOG1$(ORIGINAL)", DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 159 + TOPMARGIN, 4 + BOTTOMMARGIN, 124 + END + + "IDD_DIALOG2$(IGNORE_API_GRACENOTE)", DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 240 + TOPMARGIN, 7 + BOTTOMMARGIN, 61 + END + + "IDD_CDDB_PROGRESS$(IGNORE_API_GRACENOTE)", DIALOG + BEGIN + LEFTMARGIN, 8 + RIGHTMARGIN, 242 + VERTGUIDE, 70 + VERTGUIDE, 181 + TOPMARGIN, 8 + BOTTOMMARGIN, 58 + END + + "IDD_DIALOG1$(IGNORE_API_GRACENOTE)", DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 159 + TOPMARGIN, 4 + BOTTOMMARGIN, 194 + END +END +#endif // APSTUDIO_INVOKED + + +#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""\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_CD_PLUGIN "Nullsoft CD Plug-in v%s" + 65535 "{87DCEEC2-1EC3-4c59-BED4-E8F42232C7D8}" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_CD_PLUGIN_OLD "Nullsoft CD Plug-in" + IDS_QUERYING "Accessing Gracenote CDDB®..." + IDS_NOT_FOUND "No matches found" + IDS_PROCESSING "Processing..." + IDS_CDDB_NOT_INSTALLED "MusicID not installed" + IDS_SUCCESS "Success" + IDS_CLOSE "&Close" + IDS_INITIALIZING "initializing..." + IDS_ABORTING "Aborting..." + IDS_UNLIMITED "Unlimited" + IDS_PURCHASE_WINAMP_PRO_PROMPT + "In order to use more than 8x extraction, you must purchase Winamp Pro.\nWould you like information on purchasing Winamp Pro?" + IDS_WINAMP_PRO_FEATURE "Winamp Pro Feature" + IDS_ARTIST "Artist" + IDS_TITLE "Title" + IDS_ALBUM_ARTIST "Album Artist" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_CD_PLUGIN2 "Nullsoft CD Plug-in" + IDS_CD_CURRENTLY_IN_USE "CD Drive currently in use" + IDS_DRIVE_IN_USE "Drive in use" + IDS_DRIVE_NOT_FOUND "Drive not found" + IDS_CD_NOT_PRESENT "CD not present" + IDS_CANNOT_PLAY_TRACK "Cannot play track" + IDS_CDDA_AUDIO_TRACKS "CDDA Audio Tracks (*.CDA)" + IDS_RIPPING "Ripping" + IDS_TRACK_X "Track %i" + IDS_UNKNOWN "unknown" + IDS_UNKNOWN_ARTIST "Unknown Artist" + IDS_FAMILY_STRING "CD Audio Track shortcut" + IDS_ABOUT_TEXT "%s\nCopyright © 1997-2023 Winamp SA\n\nBuild date: %s\n\nHow to use:\n For CD audio, open cda://<cd drive letter>\n (or use the command in winamp)\n\n You can also play individual CD tracks using\n cda://<letter>,<number>\n\n" +END + +STRINGTABLE +BEGIN + IDS_ABORT "&Abort" + IDS_CDDB_PROGRESS_CONNECTING "connecting..." + IDS_CDDB_PROGRESS_SENDING "sending..." +END + +STRINGTABLE +BEGIN + IDS_CDDB_PROGRESS_RECEIVING "receiving..." + IDS_CDDB_PROGRESS_WAITING "waiting..." + IDS_CDDB_PROGRESS_CANCELLED "cancelled" + IDS_CDDB_PROGRESS_COMPLETED "completed" + IDS_CDDB_E_BUSY "Service busy" + IDS_CDDB_E_FAIL "Unknown error" + IDS_CDDB_E_BADTOC "Bad disc data" + IDS_REASON "Reason: " + IDS_FOUND_MULTIPLE "Multiple matches were found online for this CD." + IDS_RESOLVING "resolving" + IDS_SUBMITTINGDISC "Submitting disc info..." + IDS_FOUND_EXACT "Exact match found" + IDS_SUBMITNEW "Submit &New" + IDS_SUBMITDISC_TITLE "Submitting Disc Information" + IDS_SUBMITTING "Submitting Disc Information to Gracenote CDDB®..." + IDS_OPENING "opening dialog..." +END + +STRINGTABLE +BEGIN + IDS_FUZZYDISC_TITLE "Multiple Match Results" + IDS_ACCEPT "&Accept" + IDS_LOOKUPRESULT_TITLE "CD Lookup Results" + IDS_SUBMIT_OFFER "Do you want to submit new?" + IDS_CDDB_E_ABORT "Aborted by user" + IDS_CDTEXT "CD Text" + IDS_TRACK "Track" + IDS_COMPOSER "Composer" +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_cdda/in_cdda.sln b/Src/Plugins/Input/in_cdda/in_cdda.sln new file mode 100644 index 00000000..9b43b7b0 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/in_cdda.sln @@ -0,0 +1,86 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29613.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_cdda", "in_cdda.vcxproj", "{F514A693-F5AA-4A7F-B091-81A0901C2EF6}" + ProjectSection(ProjectDependencies) = postProject + {57C90706-B25D-4ACA-9B33-95CDB2427C27} = {57C90706-B25D-4ACA-9B33-95CDB2427C27} + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} + {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D} + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nde", "..\nde\nde.vcxproj", "{4D25C321-7F8B-424E-9899-D80A364BAF1A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\replicant\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\replicant\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\replicant\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\replicant\zlib\zlib.vcxproj", "{0F9730E4-45DA-4BD2-A50A-403A4BC9751A}" +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 + {F514A693-F5AA-4A7F-B091-81A0901C2EF6}.Debug|Win32.ActiveCfg = Debug|Win32 + {F514A693-F5AA-4A7F-B091-81A0901C2EF6}.Debug|Win32.Build.0 = Debug|Win32 + {F514A693-F5AA-4A7F-B091-81A0901C2EF6}.Debug|x64.ActiveCfg = Debug|x64 + {F514A693-F5AA-4A7F-B091-81A0901C2EF6}.Debug|x64.Build.0 = Debug|x64 + {F514A693-F5AA-4A7F-B091-81A0901C2EF6}.Release|Win32.ActiveCfg = Release|Win32 + {F514A693-F5AA-4A7F-B091-81A0901C2EF6}.Release|Win32.Build.0 = Release|Win32 + {F514A693-F5AA-4A7F-B091-81A0901C2EF6}.Release|x64.ActiveCfg = Release|x64 + {F514A693-F5AA-4A7F-B091-81A0901C2EF6}.Release|x64.Build.0 = Release|x64 + {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.ActiveCfg = Debug|Win32 + {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.Build.0 = Debug|Win32 + {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.ActiveCfg = Debug|x64 + {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.Build.0 = Debug|x64 + {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.ActiveCfg = Release|Win32 + {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.Build.0 = Release|Win32 + {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.ActiveCfg = Release|x64 + {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.Build.0 = Release|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.ActiveCfg = Debug|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.Build.0 = Debug|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.ActiveCfg = Debug|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.Build.0 = Debug|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.ActiveCfg = Release|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.Build.0 = Release|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.ActiveCfg = Release|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {41AF61AB-53FC-4AC8-A9FB-234866A1F3AE} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_cdda/in_cdda.vcxproj b/Src/Plugins/Input/in_cdda/in_cdda.vcxproj new file mode 100644 index 00000000..88e44deb --- /dev/null +++ b/Src/Plugins/Input/in_cdda/in_cdda.vcxproj @@ -0,0 +1,393 @@ +<?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>{F514A693-F5AA-4A7F-B091-81A0901C2EF6}</ProjectGuid> + <RootNamespace>in_cdda</RootNamespace> + <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" 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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|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> + <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"> + <VcpkgEnableManifest>false</VcpkgEnableManifest> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;..;..\..\..\;..\..\..\external_dependencies\libdiscid-0.6.2\include;..\..\..\gracenote;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;IN_CDDA2_EXPORTS;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x601;IGNORE_API_GRACENOTE;IGNORE_PRIMO;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4005;4065;4099;4133;4273;4700;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>odbc32.lib;odbccp32.lib;winmm.lib;comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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;..\..\..\replicant;..;..\..\..\external_dependencies\libdiscid-0.6.2\include;..\..\..\gracenote;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;_DEBUG;_WINDOWS;_USRDLL;IN_CDDA2_EXPORTS;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x601;IGNORE_API_GRACENOTE;IGNORE_PRIMO;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4005;4065;4099;4267;4244;4273;4302;4311;4700;4995;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>odbc32.lib;odbccp32.lib;winmm.lib;comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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> + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;..;..\..\..\;..\..\..\libdiscid\include;..\..\..\gracenote;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;IN_CDDA2_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x601;_CRT_SECURE_NO_WARNINGS;IGNORE_API_GRACENOTE;IGNORE_PRIMO;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4005;4065;4099;4700;4995;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>odbc32.lib;odbccp32.lib;winmm.lib;comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>winmm.dll;nde.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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> + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;..;..\..\..\;..\..\..\libdiscid\include;..\..\..\gracenote;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;IN_CDDA2_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x601;_CRT_SECURE_NO_WARNINGS;IGNORE_API_GRACENOTE;IGNORE_PRIMO;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4005;4065;4099;4267;4244;4273;4302;4311;4700;4995;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>odbc32.lib;odbccp32.lib;winmm.lib;comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>winmm.dll;nde.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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> + <ProjectReference Include="..\..\..\nde\nde.vcxproj"> + <Project>{4d25c321-7f8b-424e-9899-d80a364baf1a}</Project> + <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies> + <ReferenceOutputAssembly>true</ReferenceOutputAssembly> + </ProjectReference> + <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj"> + <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <CustomBuild Include="..\..\..\libdiscid\include\discid\discid.h"> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> + </CustomBuild> + <CustomBuild Include="..\..\..\libdiscid\include\discid\discid_private.h"> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> + </CustomBuild> + <CustomBuild Include="..\..\..\libdiscid\src\base64.h"> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> + </CustomBuild> + <CustomBuild Include="..\..\..\libdiscid\src\ntddcdrm.h"> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> + </CustomBuild> + <CustomBuild Include="..\..\..\libdiscid\src\sha1.h"> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> + </CustomBuild> + <ClInclude Include="..\..\..\Winamp\strutil.h" /> + <ClInclude Include="api__in_cdda.h" /> + <ClInclude Include="AUDIO.H" /> + <ClInclude Include="CDDB.H" /> + <CustomBuild Include="cddbevnt.h"> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> + </CustomBuild> + <ClInclude Include="CDDBInterface.h" /> + <CustomBuild Include="cddbui.h"> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> + </CustomBuild> + <ClInclude Include="CDPlay.h" /> + <ClInclude Include="DAEPlay.h" /> + <ClInclude Include="FuncTypedefs.h" /> + <ClInclude Include="grabwnd.h" /> + <ClInclude Include="MAIN.H" /> + <ClInclude Include="MCIPlay.h" /> + <ClInclude Include="PlayStatus.h" /> + <ClInclude Include="resource.h" /> + <CustomBuild Include="WindacPlay.h" /> + <CustomBuild Include="windac\Aspifunc.h" /> + <CustomBuild Include="windac\Dac32.h" /> + <CustomBuild Include="workorder.h"> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> + </CustomBuild> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\..\..\external_dependencies\libdiscid-0.6.2\src\base64.c"> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> + </ClCompile> + <ClCompile Include="..\..\..\external_dependencies\libdiscid-0.6.2\src\disc.c"> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> + </ClCompile> + <ClCompile Include="..\..\..\external_dependencies\libdiscid-0.6.2\src\disc_win32.c"> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> + </ClCompile> + <ClCompile Include="..\..\..\external_dependencies\libdiscid-0.6.2\src\sha1.c"> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> + </ClCompile> + <ClCompile Include="..\..\..\external_dependencies\libdiscid-0.6.2\src\toc.c"> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> + </ClCompile> + <ClCompile Include="..\..\..\nu\listview.cpp" /> + <ClCompile Include="..\..\..\Winamp\strutil.cpp" /> + <ClCompile Include="AUDIO.Cpp" /> + <ClCompile Include="CDDB.Cpp" /> + <ClCompile Include="cddbevnt.cpp" /> + <ClCompile Include="cddbui.cpp" /> + <ClCompile Include="CDText.cpp" /> + <ClCompile Include="CONFIG.Cpp" /> + <ClCompile Include="DAEPlay.cpp" /> + <ClCompile Include="DB.Cpp" /> + <ClCompile Include="discid.cpp" /> + <ClCompile Include="EditCDInfo.cpp" /> + <ClCompile Include="ExtendedFileInfo.cpp" /> + <ClCompile Include="ExtendedRead.cpp" /> + <ClCompile Include="grabwnd.cpp" /> + <ClCompile Include="Main.cpp" /> + <ClCompile Include="MCI.Cpp" /> + <ClCompile Include="nde_cd.cpp" /> + <ClCompile Include="PlayStatus.cpp" /> + <ClCompile Include="scsi_id.cpp" /> + <ClCompile Include="util.cpp" /> + <ClCompile Include="WindacPlay.cpp" /> + <ClCompile Include="windac\Aspifunc.cpp" /> + <ClCompile Include="windac\Dac32.cpp" /> + <ClCompile Include="windac\NTScsi.cpp" /> + <ClCompile Include="workorder.cpp"> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild> + <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_cdda.rc" /> + </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_cdda/in_cdda.vcxproj.filters b/Src/Plugins/Input/in_cdda/in_cdda.vcxproj.filters new file mode 100644 index 00000000..fcfa3b74 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/in_cdda.vcxproj.filters @@ -0,0 +1,190 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="AUDIO.Cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="windac\Aspifunc.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="CDDB.Cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="cddbevnt.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="cddbui.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="CDText.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="CONFIG.Cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="windac\Dac32.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="DAEPlay.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="DB.Cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="discid.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="EditCDInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedFileInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedRead.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="grabwnd.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="MCI.Cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="nde_cd.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="windac\NTScsi.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="PlayStatus.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="scsi_id.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="util.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="WindacPlay.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="workorder.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\external_dependencies\libdiscid-0.6.2\src\base64.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\external_dependencies\libdiscid-0.6.2\src\disc.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\external_dependencies\libdiscid-0.6.2\src\disc_win32.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\listview.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\external_dependencies\libdiscid-0.6.2\src\sha1.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\Winamp\strutil.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\external_dependencies\libdiscid-0.6.2\src\toc.c"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="api__in_cdda.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="AUDIO.H"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="CDDB.H"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="CDDBInterface.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="CDPlay.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="DAEPlay.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FuncTypedefs.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="grabwnd.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="MAIN.H"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="MCIPlay.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="PlayStatus.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Winamp\strutil.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <CustomBuild Include="windac\Aspifunc.h"> + <Filter>Header Files</Filter> + </CustomBuild> + <CustomBuild Include="cddbevnt.h"> + <Filter>Header Files</Filter> + </CustomBuild> + <CustomBuild Include="cddbui.h"> + <Filter>Header Files</Filter> + </CustomBuild> + <CustomBuild Include="windac\Dac32.h"> + <Filter>Header Files</Filter> + </CustomBuild> + <CustomBuild Include="WindacPlay.h"> + <Filter>Header Files</Filter> + </CustomBuild> + <CustomBuild Include="workorder.h"> + <Filter>Header Files</Filter> + </CustomBuild> + <CustomBuild Include="..\..\..\libdiscid\src\base64.h"> + <Filter>Header Files</Filter> + </CustomBuild> + <CustomBuild Include="..\..\..\libdiscid\include\discid\discid.h"> + <Filter>Header Files</Filter> + </CustomBuild> + <CustomBuild Include="..\..\..\libdiscid\include\discid\discid_private.h"> + <Filter>Header Files</Filter> + </CustomBuild> + <CustomBuild Include="..\..\..\libdiscid\src\ntddcdrm.h"> + <Filter>Header Files</Filter> + </CustomBuild> + <CustomBuild Include="..\..\..\libdiscid\src\sha1.h"> + <Filter>Header Files</Filter> + </CustomBuild> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{8d59d863-b543-4d13-b6a7-55382eadffe0}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{ab3794f1-f2e8-465d-abc5-6fcf5bef0413}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{388bf86e-3bd7-4945-8833-ebecc85f04f8}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_cdda.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/inst.nsi b/Src/Plugins/Input/in_cdda/inst.nsi new file mode 100644 index 00000000..4354a247 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/inst.nsi @@ -0,0 +1,78 @@ +Name "CDDB2 test, beta 1" + +; The file to write +OutFile "cddb2.exe" + +InstallDir $PROGRAMFILES\Winamp +InstallDirRegKey HKLM \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\Winamp" \ + "UninstallString" + +; The text to prompt the user to enter a directory +DirText "Please select your Winamp path below (you will be able to proceed when Winamp is detected):" +DirShow hide + +; automatically close the installer when done. +AutoCloseWindow true +; hide the "show details" box +ShowInstDetails nevershow + +BGGradient 000000 308030 FFFFFF +InstallColors FF8080 000000 +InstProgressFlags smooth colored + +Function .onInit + MessageBox MB_YESNO|MB_ICONQUESTION "Install CDDB2 update test?" IDYES update + MessageBox MB_OK|MB_ICONINFORMATION "Install aborted." + Abort + update: +FunctionEnd + +Function .onVerifyInstDir + IfFileExists $INSTDIR\Winamp.exe Good + Abort + Good: +FunctionEnd + +Function CloseWinamp + Push $0 + loop: + FindWindow $0 "Winamp v1.x" + IntCmp $0 0 done + SendMessage $0 16 0 0 + StrCpy $9 "yes" + Sleep 100 + Goto loop + done: + Pop $0 +FunctionEnd + + +Section "ThisNameIsIgnoredSoWhyBother?" + StrCpy $9 "no" + Call CloseWinamp + SetOutPath $INSTDIR + File "C:\program files\winamp\winamp.exe" + SetOutPath $INSTDIR\Plugins + + UnRegDll $OUTDIR\cddbcontrolwinamp.dll + UnRegDll $OUTDIR\cddbuiwinamp.dll + File "C:\program files\winamp\plugins\in_cdda.dll" + File "C:\program files\winamp\plugins\in_mp3.dll" + File "cddbcontrolwinamp.dll" + File "cddbuiwinamp.dll" + RegDll $OUTDIR\cddbcontrolwinamp.dll + RegDll $OUTDIR\cddbuiwinamp.dll + + DetailPrint Completed. +SectionEnd + + +Function .onInstSuccess + MessageBox MB_OK|MB_ICONINFORMATION "Update installed." + StrCmp $9 "no" nope + Exec '"$INSTDIR\Winamp.exe"' + nope: +FunctionEnd + +; eof diff --git a/Src/Plugins/Input/in_cdda/nde_cd.cpp b/Src/Plugins/Input/in_cdda/nde_cd.cpp new file mode 100644 index 00000000..2b20e36f --- /dev/null +++ b/Src/Plugins/Input/in_cdda/nde_cd.cpp @@ -0,0 +1,720 @@ +#include "../nde/nde_c.h" +#include "main.h" +#include <shlwapi.h> +#include "../nu/AutoLock.h" +#include "../nu/AutoWide.h" +#include "cddbinterface.h" +#include "cddb.h" + +#include "api__in_cdda.h" +#include <api/service/waservicefactory.h> +#include <atlbase.h> +#include <strsafe.h> + +using namespace Nullsoft::Utility; +static nde_database_t discDB=0; +static nde_table_t discTable=0, trackTable=0; +static Nullsoft::Utility::LockGuard dbcs; +static int g_dirty; + +enum +{ + NDE_CD_SUCCESS=0, + NDE_CD_FAILURE=1, +}; + +enum +{ + DISCTABLE_ID_DISCID = 0, + DISCTABLE_ID_ALBUM=1, + DISCTABLE_ID_ARTIST=2, + DISCTABLE_ID_TUID=3, + DISCTABLE_ID_YEAR=4, + DISCTABLE_ID_GENRE=5, + DISCTABLE_ID_COMMENT=6, + DISCTABLE_ID_DISC=7, + DISCTABLE_ID_COMPOSER=8, + DISCTABLE_ID_PUBLISHER=9, + DISCTABLE_ID_CONDUCTOR=10, + DISCTABLE_ID_REMIXING=10, +}; + +enum +{ + TRACKTABLE_ID_DISCID = 0, + TRACKTABLE_ID_TRACK = 1, + TRACKTABLE_ID_ARTIST=2, + TRACKTABLE_ID_TITLE=3, + TRACKTABLE_ID_TAGID=4, + TRACKTABLE_ID_COMPOSER=5, + TRACKTABLE_ID_CONDUCTOR=6, + TRACKTABLE_ID_EXTENDED_DATA=7, + TRACKTABLE_ID_REMIXING=8, + TRACKTABLE_ID_ISRC=9, +}; + +static void CreateDiscFields(nde_table_t table) +{ + // create defaults + NDE_Table_NewColumnW(table, DISCTABLE_ID_DISCID, L"discid", FIELD_INTEGER); + NDE_Table_NewColumnW(table, DISCTABLE_ID_ALBUM, L"title", FIELD_STRING); + NDE_Table_NewColumnW(table, DISCTABLE_ID_ARTIST, L"artist", FIELD_STRING); + NDE_Table_NewColumnW(table, DISCTABLE_ID_TUID, L"tuid", FIELD_STRING); + NDE_Table_NewColumnW(table, DISCTABLE_ID_YEAR, L"year", FIELD_STRING); + NDE_Table_NewColumnW(table, DISCTABLE_ID_GENRE, L"genre", FIELD_STRING); + NDE_Table_NewColumnW(table, DISCTABLE_ID_COMMENT, L"comment", FIELD_STRING); + NDE_Table_NewColumnW(table, DISCTABLE_ID_DISC, L"disc", FIELD_STRING); + NDE_Table_NewColumnW(table, DISCTABLE_ID_COMPOSER, L"composer", FIELD_STRING); + NDE_Table_NewColumnW(table, DISCTABLE_ID_PUBLISHER, L"publisher", FIELD_STRING); + NDE_Table_NewColumnW(table, DISCTABLE_ID_CONDUCTOR, L"conductor", FIELD_STRING); + NDE_Table_NewColumnW(table, DISCTABLE_ID_REMIXING, L"remixing", FIELD_STRING); + + NDE_Table_PostColumns(table); + NDE_Table_AddIndexByIDW(table, DISCTABLE_ID_DISCID, L"discid"); +} + +static void CreateTrackFields(nde_table_t table) +{ + // create defaults + NDE_Table_NewColumnW(table, TRACKTABLE_ID_DISCID, L"discid", FIELD_INTEGER); + NDE_Table_NewColumnW(table, TRACKTABLE_ID_TRACK, L"track", FIELD_INTEGER); + NDE_Table_NewColumnW(table, TRACKTABLE_ID_ARTIST, L"artist", FIELD_STRING); + NDE_Table_NewColumnW(table, TRACKTABLE_ID_TITLE, L"title", FIELD_STRING); + NDE_Table_NewColumnW(table, TRACKTABLE_ID_TAGID, L"tagid", FIELD_STRING); + NDE_Table_NewColumnW(table, TRACKTABLE_ID_COMPOSER, L"composer", FIELD_STRING); + NDE_Table_NewColumnW(table, TRACKTABLE_ID_CONDUCTOR, L"conductor", FIELD_STRING); + NDE_Table_NewColumnW(table, TRACKTABLE_ID_EXTENDED_DATA, L"extendeddata", FIELD_STRING); + NDE_Table_NewColumnW(table, TRACKTABLE_ID_REMIXING, L"remixing", FIELD_STRING); + NDE_Table_NewColumnW(table, TRACKTABLE_ID_ISRC, L"ISRC", FIELD_STRING); + + NDE_Table_PostColumns(table); + NDE_Table_AddIndexByIDW(table, TRACKTABLE_ID_DISCID, L"discid"); + NDE_Table_AddIndexByIDW(table, TRACKTABLE_ID_TRACK, L"track"); +} + +static int OpenDiscDatabase() +{ + AutoLock lock(dbcs); + if (!discDB) + { + discDB = NDE_CreateDatabase(); + } + return NDE_CD_SUCCESS; +} + +static int OpenDiscTable() +{ + AutoLock lock(dbcs); + int ret = OpenDiscDatabase(); + if (ret != NDE_CD_SUCCESS) + { + return ret; + } + + if (!discTable) + { + const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath(); + wchar_t discTablePath[MAX_PATH] = {0}, discIndexPath[MAX_PATH] = {0}; + PathCombineW(discTablePath, inidir, L"plugins"); + PathAppendW(discTablePath, L"cddiscs.dat"); + PathCombineW(discIndexPath, inidir, L"plugins"); + PathAppendW(discIndexPath, L"cddiscs.idx"); + discTable = NDE_Database_OpenTable(discDB, discTablePath, discIndexPath, NDE_OPEN_ALWAYS, NDE_CACHE); + if (discTable) + { + CreateDiscFields(discTable); + } + } + return (discTable ? NDE_CD_SUCCESS : NDE_CD_FAILURE); +} + +static int OpenTrackTable() +{ + AutoLock lock(dbcs); + int ret = OpenDiscDatabase(); + if (ret != NDE_CD_SUCCESS) + { + return ret; + } + + if (!trackTable) + { + const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath(); + wchar_t trackTablePath[MAX_PATH] = {0}, trackIndexPath[MAX_PATH] = {0}; + PathCombineW(trackTablePath, inidir, L"plugins"); + PathAppendW(trackTablePath, L"cdtracks.dat"); + PathCombineW(trackIndexPath, inidir, L"plugins"); + PathAppendW(trackIndexPath, L"cdtracks.idx"); + trackTable = NDE_Database_OpenTable(discDB, trackTablePath, trackIndexPath, NDE_OPEN_ALWAYS, NDE_CACHE); + if (trackTable) + { + CreateTrackFields(trackTable); + } + } + return (trackTable ? NDE_CD_SUCCESS : NDE_CD_FAILURE); +} + +void CloseTables() +{ + if (discTable) + { + if (g_dirty & 1) NDE_Table_Sync(discTable); + NDE_Database_CloseTable(discDB, discTable); + discTable = 0; + } + + if (trackTable) + { + if (g_dirty & 2) NDE_Table_Sync(trackTable); + NDE_Database_CloseTable(discDB, trackTable); + trackTable = 0; + } + + if (discDB) + { + NDE_DestroyDatabase(discDB); + discDB = 0; + } + g_dirty = 0; +} + +static void db_setFieldInt(nde_scanner_t s, unsigned char id, int data) +{ + nde_field_t f = NDE_Scanner_GetFieldByID(s, id); + if (!f) f = NDE_Scanner_NewFieldByID(s, id); + NDE_IntegerField_SetValue(f, data); +} + +static void db_setFieldString(nde_scanner_t s, unsigned char id, const wchar_t *data) +{ + nde_field_t f = NDE_Scanner_GetFieldByID(s, id); + if (!f) f = NDE_Scanner_NewFieldByID(s, id); + NDE_StringField_SetString(f, data); +} + +static void db_removeField(nde_scanner_t s, unsigned char id) +{ + nde_field_t f = NDE_Scanner_GetFieldByID(s, id); + if (f) + { + NDE_Scanner_DeleteField(s, f); + } +} + +static int db_getFieldInt(nde_scanner_t s, unsigned char id, int defaultVal) +{ + nde_field_t f = NDE_Scanner_GetFieldByID(s, id); + if (f) + return NDE_IntegerField_GetValue(f); + else + return defaultVal; +} + +static wchar_t *db_getFieldString(nde_scanner_t s, unsigned char id) +{ + nde_field_t f = NDE_Scanner_GetFieldByID(s, id); + if (f) + return NDE_StringField_GetString(f); + else + return 0; +} + +static void SeekToDisc(nde_scanner_t s, unsigned int cddb_id) +{ + if (!NDE_Scanner_LocateInteger(s, DISCTABLE_ID_DISCID, FIRST_RECORD, cddb_id)) + { + NDE_Scanner_New(s); + db_setFieldInt(s,DISCTABLE_ID_DISCID,cddb_id); + } +} + +static void SeekToTrack(nde_scanner_t s, unsigned int cddb_id, int track) +{ + nde_field_t f_id = NDE_IntegerField_Create(cddb_id); + NDE_Scanner_AddFilterByID(s, TRACKTABLE_ID_DISCID, f_id, FILTER_EQUALS); + if (!NDE_Scanner_LocateInteger(s, TRACKTABLE_ID_TRACK, FIRST_RECORD, track)) + { + NDE_Scanner_New(s); + db_setFieldInt(s,TRACKTABLE_ID_DISCID,cddb_id); + db_setFieldInt(s,TRACKTABLE_ID_TRACK,track); + } + NDE_Scanner_RemoveFilters(s); +} + +#ifndef IGNORE_API_GRACENOTE +void StoreDisc(unsigned int cddb_id, ICddbDiscPtr pDisc) +{ + AutoLock lock(dbcs); + CComBSTR str, disc_artist, disc_composer, disc_conductor, disc_remixing; + BSTR composerRole=L"3", conductorRole=L"12", remixingRole=L"147"; + + /* + for (int i=100;i<300;i++) + { + wchar_t id[256] = {0}; + _itow(i, id, 10); + ICddbRolePtr role; + pCDDBControl->GetRoleInfo(id, &role); + if (role) + { + BSTR name, description; + role->get_Name(&name); + role->get_Description(&description); + wchar_t str[4096] = {0}; + wsprintf(str, L"ID: %s\r\nName: %s\r\nDescription: %s\r\n", id, name, description); + MessageBoxW(NULL, str, L"CDDB Role", MB_OK); + } + } + */ + OpenDiscTable(); + nde_scanner_t s = NDE_Table_CreateScanner(discTable); + SeekToDisc(s, cddb_id); + + ICddbDisc2Ptr pDisc2; + pDisc->QueryInterface(&pDisc2); + + ICddbDisc2_5Ptr pDisc2_5; + pDisc->QueryInterface(&pDisc2_5); + + if (GetRole(pDisc, conductorRole, &disc_conductor) && disc_conductor && disc_conductor.m_str[0]) + db_setFieldString(s, DISCTABLE_ID_CONDUCTOR, disc_conductor); + else + db_removeField(s, DISCTABLE_ID_CONDUCTOR); + + if (GetRole(pDisc, composerRole, &disc_composer) && disc_composer && disc_composer.m_str[0]) + db_setFieldString(s, DISCTABLE_ID_COMPOSER, disc_composer); + else + db_removeField(s, DISCTABLE_ID_COMPOSER); + + if (GetRole(pDisc, remixingRole, &disc_remixing) && disc_remixing && disc_remixing.m_str[0]) + db_setFieldString(s, DISCTABLE_ID_REMIXING, disc_remixing); + else + db_removeField(s, DISCTABLE_ID_REMIXING); + + if (SUCCEEDED(pDisc->get_Artist(&disc_artist)) && disc_artist && disc_artist.m_str[0]) + db_setFieldString(s, DISCTABLE_ID_ARTIST, disc_artist); + else + db_removeField(s, DISCTABLE_ID_ARTIST); + + if (SUCCEEDED(pDisc->get_Year(&str)) && str && str.m_str[0]) + db_setFieldString(s, DISCTABLE_ID_YEAR, str); + else + db_removeField(s, DISCTABLE_ID_YEAR); + + if (pDisc2_5 == NULL + || (FAILED(pDisc2_5->get_V2GenreStringPrimaryByLevel(3, &str)) + && FAILED(pDisc2_5->get_V2GenreStringPrimaryByLevel(2, &str)) + && FAILED(pDisc2_5->get_V2GenreStringPrimaryByLevel(1, &str)) + && FAILED(pDisc2_5->get_V2GenreStringPrimary(&str))) + ) + { + pDisc->get_GenreId(&str); + ICddbGenre *poop = 0; + if (SUCCEEDED(pCDDBControl->GetGenreInfo(str, &poop)) && poop) + { + poop->get_Name(&str); + poop->Release(); + } + else + str.Empty(); + } + + if (str && str.m_str[0]) + db_setFieldString(s, DISCTABLE_ID_GENRE, str); + else + db_removeField(s, DISCTABLE_ID_GENRE); + + if (SUCCEEDED(pDisc->get_Title(&str)) && str && str.m_str[0]) + db_setFieldString(s, DISCTABLE_ID_ALBUM, str); + else + db_removeField(s, DISCTABLE_ID_ALBUM); + + if (SUCCEEDED(pDisc->get_TitleUId(&str)) && str && str.m_str[0]) + db_setFieldString(s, DISCTABLE_ID_TUID, str); + else + db_removeField(s, DISCTABLE_ID_TUID); + + if (SUCCEEDED(pDisc->get_Label(&str)) && str && str.m_str[0]) + db_setFieldString(s, DISCTABLE_ID_PUBLISHER, str); + else + db_removeField(s, DISCTABLE_ID_PUBLISHER); + + if (SUCCEEDED(pDisc->get_Notes(&str)) && str && str.m_str[0]) + db_setFieldString(s, DISCTABLE_ID_COMMENT, str); + else + db_removeField(s, DISCTABLE_ID_COMMENT); + + /* + long val; + pDisc->get_Compilation(&val); + ps->compilation = !!val; + */ + int numdiscs = 0; + if (SUCCEEDED(pDisc->get_TotalInSet(&str)) && str && str.m_str[0]) + numdiscs = _wtoi(str.m_str); + int discnum =0; + if (SUCCEEDED(pDisc->get_NumberInSet(&str)) && str && str.m_str[0]) + discnum = _wtoi(str.m_str); + + if (discnum) + { + wchar_t disc_temp[64] = {0}; + if (numdiscs) + StringCchPrintfW(disc_temp, 64, L"%d/%d", discnum, numdiscs); + else + StringCchPrintfW(disc_temp, 64, L"%d", discnum); + db_setFieldString(s, DISCTABLE_ID_DISC, disc_temp); + } + else + db_removeField(s, DISCTABLE_ID_DISC); + + long tracks=0; + if (FAILED(pDisc->get_NumTracks(&tracks))) + tracks=0; + + NDE_Scanner_Post(s); + NDE_Table_DestroyScanner(discTable, s); + OpenTrackTable(); + s = NDE_Table_CreateScanner(trackTable); + for (int x = 0; x < tracks; x ++) + { + ICddbTrack *t=0; + ICddbTrack2_5Ptr track2_5; + if (FAILED(pDisc->GetTrack(x + 1, &t)) || !t) + break; + + SeekToTrack(s, cddb_id, x+1); + + // don't store if it's the same as the disc artist + if (SUCCEEDED(t->get_Artist(&str)) && str && str.m_str[0] && (!disc_artist || !disc_artist.m_str[0] || wcscmp(str.m_str, disc_artist.m_str))) + db_setFieldString(s, TRACKTABLE_ID_ARTIST, str); + else + db_removeField(s, TRACKTABLE_ID_ARTIST); + + if (SUCCEEDED(t->get_Title(&str)) && str && str.m_str[0]) + db_setFieldString(s, TRACKTABLE_ID_TITLE, str); + else + db_removeField(s, TRACKTABLE_ID_TITLE); + + if (SUCCEEDED(t->get_ISRC(&str)) && str && str.m_str[0]) + db_setFieldString(s, TRACKTABLE_ID_ISRC, str); + else + db_removeField(s, TRACKTABLE_ID_ISRC); + + if (SUCCEEDED(pCDDBControl->GetDiscTagId(pDisc, x + 1, &str)) && str && str.m_str[0]) + db_setFieldString(s, TRACKTABLE_ID_TAGID, str); + else + db_removeField(s, TRACKTABLE_ID_TAGID); + + // don't store if it's the same as the disc conductor + if (GetRole(t, conductorRole, &str) && str && str.m_str[0] && (!disc_conductor || !disc_conductor.m_str[0] || wcscmp(str.m_str, disc_conductor.m_str))) + db_setFieldString(s, TRACKTABLE_ID_CONDUCTOR, str); + else + db_removeField(s, TRACKTABLE_ID_CONDUCTOR); + + // don't store if it's the same as the disc composer + if (GetRole(t, composerRole, &str) && str && str.m_str[0] && (!disc_composer || !disc_composer.m_str[0] || wcscmp(str.m_str, disc_composer.m_str))) + db_setFieldString(s, TRACKTABLE_ID_COMPOSER, str); + else + db_removeField(s, TRACKTABLE_ID_COMPOSER); + + // don't store if it's the same as the disc remixer + if (GetRole(t, remixingRole, &str) && str && str.m_str[0] && (!disc_remixing || !disc_remixing.m_str[0] || wcscmp(str.m_str, disc_remixing.m_str))) + db_setFieldString(s, TRACKTABLE_ID_REMIXING, str); + else + db_removeField(s, TRACKTABLE_ID_REMIXING); + + t->QueryInterface(&track2_5); + + if (track2_5 != NULL && (SUCCEEDED(track2_5->get_ExtDataSerialized(&str)) && str && str.m_str[0]) // try track first + || (pDisc2_5 != NULL && SUCCEEDED(pDisc2_5->get_ExtDataSerialized(&str)) && str && str.m_str[0])) // then disc + db_setFieldString(s, TRACKTABLE_ID_EXTENDED_DATA, str); + else + db_removeField(s, TRACKTABLE_ID_EXTENDED_DATA); + + NDE_Scanner_Post(s); + t->Release(); + } + + NDE_Table_DestroyScanner(trackTable, s); + NDE_Table_Sync(trackTable); + NDE_Table_Sync(discTable); +} +#endif + +static void CDText_Process(unsigned int cddb_id, const char *title, const char *performers, const char *composers, int codepage) +{ + AutoLock lock(dbcs); + OpenDiscTable(); + nde_scanner_t s = NDE_Table_CreateScanner(discTable); + SeekToDisc(s, cddb_id); + + char thisTitle[1024] = {0}; + + const char *titles = title; + // first, get disc title + thisTitle[0] = 0; + titles = ReadLine(titles, thisTitle, 1024, codepage); + + db_setFieldString(s, DISCTABLE_ID_ALBUM, AutoWide(thisTitle, codepage)); + + // now get track titles + OpenTrackTable(); + nde_scanner_t tableScanner = NDE_Table_CreateScanner(trackTable); + int trackNum = 1; + while (titles && *titles) + { + SeekToTrack(tableScanner, cddb_id, trackNum++); + thisTitle[0] = 0; + titles = ReadLine(titles, thisTitle, 1024, codepage); + db_setFieldString(tableScanner, TRACKTABLE_ID_TITLE, AutoWide(thisTitle, codepage)); + NDE_Scanner_Post(tableScanner); + } + + titles = performers; + // now get disc artist + thisTitle[0] = 0; + titles = ReadLine(titles, thisTitle, 1024, codepage); + db_setFieldString(s, DISCTABLE_ID_ARTIST, AutoWide(thisTitle, codepage)); + + // now get track artists + trackNum = 1; + while (titles && *titles) + { + SeekToTrack(tableScanner, cddb_id, trackNum++); + + thisTitle[0] = 0; + titles = ReadLine(titles, thisTitle, 1024, codepage); + db_setFieldString(tableScanner, TRACKTABLE_ID_ARTIST, AutoWide(thisTitle, codepage)); + NDE_Scanner_Post(tableScanner); + } + + titles = composers; + // now get disc composer + thisTitle[0] = 0; + titles = ReadLine(titles, thisTitle, 1024, codepage); + db_setFieldString(s, DISCTABLE_ID_COMPOSER, AutoWide(thisTitle, codepage)); + + // now get track composers + trackNum = 1; + while (titles && *titles) + { + SeekToTrack(tableScanner, cddb_id, trackNum++); + + thisTitle[0] = 0; + titles = ReadLine(titles, thisTitle, 1024, codepage); + db_setFieldString(tableScanner, TRACKTABLE_ID_COMPOSER, AutoWide(thisTitle, codepage)); + NDE_Scanner_Post(tableScanner); + } + + NDE_Table_DestroyScanner(trackTable, tableScanner); + NDE_Scanner_Post(s); + NDE_Table_DestroyScanner(discTable, s); +} + +/* returns true if there was CD text */ +bool StoreCDText(unsigned int cddb_id, wchar_t device) +{ + if (!device) + return false; + + #ifndef IGNORE_API_GRACENOTE + if (config_use_veritas) + { + obj_primo *primo=0; + waServiceFactory *sf = line.service->service_getServiceByGuid(obj_primo::getServiceGuid()); + if (sf) primo = reinterpret_cast<obj_primo *>(sf->getInterface()); + if (primo) + { + DWORD unit = device; + if (primo->DiscInfoEx(&unit, 0, NULL, NULL, NULL, NULL, NULL, NULL) != PRIMOSDK_OK) // CDTextInfoEJ suggest that this needs to be called first + { + sf->releaseInterface(primo); + return false; + } + char titleE[8192] = "", performerE[8192] = "", composerE[8192] = "", titleJ[2000] = "", performerJ[2000] = "", composerJ[2000] = ""; + if (primo->CDTextInfoEJ(&unit, (PBYTE)titleE, (PBYTE)performerE, (PBYTE)composerE, (PBYTE)titleJ, (PBYTE)performerJ, (PBYTE)composerJ) == PRIMOSDK_OK) + { + sf->releaseInterface(primo); + // read titles + if (titleE[0]) + { + CDText_Process(cddb_id, titleE, performerE, composerE, 28591 /* Latin1 */); + return true; + } + else if (titleJ[0]) + { + CDText_Process(cddb_id, titleJ, performerJ, composerJ, 932 /* SHIFT-JIS */); + return true; + } + } + } + } + #endif + + return false; +} + +void StoreCDNoInfo(unsigned int cddb_id) +{ + AutoLock lock(dbcs); + OpenDiscTable(); + nde_scanner_t s = NDE_Table_CreateScanner(discTable); + SeekToDisc(s, cddb_id); + NDE_Scanner_Post(s); + NDE_Table_DestroyScanner(discTable, s); + g_dirty |= 1; +} + +// sets part and parts to 0 on fail/missing +void ParseIntSlashInt(const wchar_t *string, int *part, int *parts) +{ + *part = 0; + *parts = 0; + + if (string && string[0]) + { + *part = _wtoi(string); + while (string && *string && *string != '/') + { + string++; + } + if (string && *string == '/') + { + string++; + *parts = _wtoi(string); + } + } +} + +bool QueryDINFO(unsigned int cddb_id, DINFO *info) +{ + info->Reset(); + AutoLock lock(dbcs); + if (OpenDiscTable() != NDE_CD_SUCCESS) + return false; + + nde_scanner_t s = NDE_Table_CreateScanner(discTable); + if (NDE_Scanner_LocateInteger(s, DISCTABLE_ID_DISCID, FIRST_RECORD, cddb_id)) + { + ndestring_retain(info->title = db_getFieldString(s, DISCTABLE_ID_ALBUM)); + ndestring_retain(info->artist = db_getFieldString(s, DISCTABLE_ID_ARTIST)); + ndestring_retain(info->tuid = db_getFieldString(s, DISCTABLE_ID_TUID)); + ndestring_retain(info->year = db_getFieldString(s, DISCTABLE_ID_YEAR)); + ndestring_retain(info->genre = db_getFieldString(s, DISCTABLE_ID_GENRE)); + ndestring_retain(info->notes = db_getFieldString(s, DISCTABLE_ID_COMMENT)); + const wchar_t *disc_str = db_getFieldString(s, DISCTABLE_ID_DISC); + ParseIntSlashInt(disc_str , &info->discnum, &info->numdiscs); + ndestring_retain(info->composer = db_getFieldString(s, DISCTABLE_ID_COMPOSER)); + ndestring_retain(info->label = db_getFieldString(s, DISCTABLE_ID_PUBLISHER)); + ndestring_retain(info->conductor = db_getFieldString(s, DISCTABLE_ID_CONDUCTOR)); + + /* Read tracks */ + OpenTrackTable(); + nde_scanner_t trackScanner = NDE_Table_CreateScanner(trackTable); + + nde_field_t f_id = NDE_IntegerField_Create(cddb_id); + NDE_Scanner_AddFilterByID(trackScanner, TRACKTABLE_ID_DISCID, f_id, FILTER_EQUALS); + + int trackNum=1; + while (trackNum < 100) + { + TRACKINFO &trackInfo = info->tracks[trackNum-1]; + if (!NDE_Scanner_LocateInteger(trackScanner, TRACKTABLE_ID_TRACK, FIRST_RECORD, trackNum)) + break; + trackNum++; + ndestring_retain(trackInfo.artist = db_getFieldString(trackScanner, TRACKTABLE_ID_ARTIST)); + ndestring_retain(trackInfo.title = db_getFieldString(trackScanner, TRACKTABLE_ID_TITLE)); + ndestring_retain(trackInfo.tagID = db_getFieldString(trackScanner, TRACKTABLE_ID_TAGID)); + ndestring_retain(trackInfo.composer = db_getFieldString(trackScanner, TRACKTABLE_ID_COMPOSER)); + ndestring_retain(trackInfo.conductor = db_getFieldString(trackScanner, TRACKTABLE_ID_CONDUCTOR)); + ndestring_retain(trackInfo.extData = db_getFieldString(trackScanner, TRACKTABLE_ID_EXTENDED_DATA)); + } + NDE_Table_DestroyScanner(trackTable, trackScanner); + NDE_Table_DestroyScanner(discTable, s); + info->populated = true; + return true; + } + NDE_Table_DestroyScanner(discTable, s); + return false; +} + +static void db_add_or_set(nde_scanner_t s, unsigned char id, wchar_t *data) +{ + nde_field_t f = NDE_Scanner_GetFieldByID(s, id); + if (data) + { + if (!f) f = NDE_Scanner_NewFieldByID(s, id); + NDE_StringField_SetNDEString(f, data); + } + else if (f) + { + NDE_Scanner_DeleteField(s, f); + } +} + +static void db_compare_add_or_set(nde_scanner_t s, unsigned char id, const wchar_t *disc_data, wchar_t *data) +{ + if (disc_data && data && !wcscmp(disc_data, data)) + db_removeField(s, id); + else + db_add_or_set(s, id, data); +} + +//#ifndef IGNORE_API_GRACENOTE +bool StoreDINFO(unsigned cddb_id, DINFO *info) +{ + AutoLock lock(dbcs); + if (OpenDiscTable() != NDE_CD_SUCCESS) + return false; + + nde_scanner_t s = NDE_Table_CreateScanner(discTable); + SeekToDisc(s, cddb_id); + + db_add_or_set(s, DISCTABLE_ID_ALBUM, info->title); + db_add_or_set(s, DISCTABLE_ID_ARTIST, info->artist); + db_add_or_set(s, DISCTABLE_ID_TUID, info->tuid); + db_add_or_set(s, DISCTABLE_ID_YEAR, info->year); + db_add_or_set(s, DISCTABLE_ID_GENRE, info->genre); + db_add_or_set(s, DISCTABLE_ID_PUBLISHER, info->label); + db_add_or_set(s, DISCTABLE_ID_COMMENT, info->notes); + + if (info->discnum) + { + wchar_t disc_temp[64] = {0}; + if (info->numdiscs) + StringCchPrintfW(disc_temp, 64, L"%d/%d", info->discnum, info->numdiscs); + else + StringCchPrintfW(disc_temp, 64, L"%d", info->discnum); + db_setFieldString(s, DISCTABLE_ID_DISC, disc_temp); + } + else + db_removeField(s, DISCTABLE_ID_DISC); + + db_add_or_set(s, DISCTABLE_ID_CONDUCTOR, info->conductor); + db_add_or_set(s, DISCTABLE_ID_COMPOSER, info->composer); + + NDE_Scanner_Post(s); + NDE_Table_DestroyScanner(discTable, s); + + OpenTrackTable(); + s = NDE_Table_CreateScanner(trackTable); + for (int x=0;x<info->ntracks;x++) + { + SeekToTrack(s, cddb_id, x+1); + TRACKINFO &trackInfo = info->tracks[x]; + db_compare_add_or_set(s, TRACKTABLE_ID_ARTIST, info->artist, trackInfo.artist); + db_add_or_set(s, TRACKTABLE_ID_TITLE, trackInfo.title); + db_add_or_set(s, TRACKTABLE_ID_TAGID, trackInfo.tagID); + db_compare_add_or_set(s, TRACKTABLE_ID_COMPOSER, info->composer, trackInfo.composer); + db_compare_add_or_set(s, TRACKTABLE_ID_CONDUCTOR, info->conductor, trackInfo.conductor); + db_add_or_set(s, TRACKTABLE_ID_EXTENDED_DATA, trackInfo.extData); + NDE_Scanner_Post(s); + } + NDE_Table_DestroyScanner(trackTable, s); + NDE_Table_Sync(trackTable); + NDE_Table_Sync(discTable); + g_dirty = 0; + return true; +} +//#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/ntddcdrm.h b/Src/Plugins/Input/in_cdda/ntddcdrm.h new file mode 100644 index 00000000..f9904978 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/ntddcdrm.h @@ -0,0 +1,549 @@ +/*++ BUILD Version: 0001 // Increment this if a change has global effects + +Copyright (c) Microsoft Corporation. All rights reserved. + +Module Name: + + ntddcdrm.h + +Abstract: + + This module contains structures and definitions + associated with CDROM IOCTls. + +Author: + + Mike Glass + +Revision History: + +--*/ + +// begin_winioctl + +#ifndef _NTDDCDRM_ +#define _NTDDCDRM_ + +#if _MSC_VER >= 1200 +#pragma warning(push) +#endif + +#if _MSC_VER > 1000 +#pragma once +#endif + +// +// remove some level 4 warnings for this header file: +#pragma warning(disable:4200) // array[0] +#pragma warning(disable:4201) // nameless struct/unions +#pragma warning(disable:4214) // bit fields other than int + +#ifdef __cplusplus +extern "C" { +#endif + +// +// NtDeviceIoControlFile IoControlCode values for this device. +// +// Warning: Remember that the low two bits of the code specify how the +// buffers are passed to the driver! +// + +#define IOCTL_CDROM_BASE FILE_DEVICE_CD_ROM + +#define IOCTL_CDROM_UNLOAD_DRIVER CTL_CODE(IOCTL_CDROM_BASE, 0x0402, METHOD_BUFFERED, FILE_READ_ACCESS) + +// +// CDROM Audio Device Control Functions +// + +#define IOCTL_CDROM_READ_TOC CTL_CODE(IOCTL_CDROM_BASE, 0x0000, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_SEEK_AUDIO_MSF CTL_CODE(IOCTL_CDROM_BASE, 0x0001, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_STOP_AUDIO CTL_CODE(IOCTL_CDROM_BASE, 0x0002, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_PAUSE_AUDIO CTL_CODE(IOCTL_CDROM_BASE, 0x0003, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_RESUME_AUDIO CTL_CODE(IOCTL_CDROM_BASE, 0x0004, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_GET_VOLUME CTL_CODE(IOCTL_CDROM_BASE, 0x0005, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_PLAY_AUDIO_MSF CTL_CODE(IOCTL_CDROM_BASE, 0x0006, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_SET_VOLUME CTL_CODE(IOCTL_CDROM_BASE, 0x000A, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_READ_Q_CHANNEL CTL_CODE(IOCTL_CDROM_BASE, 0x000B, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_GET_CONTROL CTL_CODE(IOCTL_CDROM_BASE, 0x000D, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_GET_LAST_SESSION CTL_CODE(IOCTL_CDROM_BASE, 0x000E, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_RAW_READ CTL_CODE(IOCTL_CDROM_BASE, 0x000F, METHOD_OUT_DIRECT, FILE_READ_ACCESS) +#define IOCTL_CDROM_DISK_TYPE CTL_CODE(IOCTL_CDROM_BASE, 0x0010, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_CDROM_GET_DRIVE_GEOMETRY CTL_CODE(IOCTL_CDROM_BASE, 0x0013, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_GET_DRIVE_GEOMETRY_EX CTL_CODE(IOCTL_CDROM_BASE, 0x0014, METHOD_BUFFERED, FILE_READ_ACCESS) + +#define IOCTL_CDROM_READ_TOC_EX CTL_CODE(IOCTL_CDROM_BASE, 0x0015, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_GET_CONFIGURATION CTL_CODE(IOCTL_CDROM_BASE, 0x0016, METHOD_BUFFERED, FILE_READ_ACCESS) + +// end_winioctl + +// +// The following device control codes are common for all class drivers. The +// functions codes defined here must match all of the other class drivers. +// +// Warning: these codes will be replaced in the future with the IOCTL_STORAGE +// codes included below +// + +#define IOCTL_CDROM_CHECK_VERIFY CTL_CODE(IOCTL_CDROM_BASE, 0x0200, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_MEDIA_REMOVAL CTL_CODE(IOCTL_CDROM_BASE, 0x0201, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_EJECT_MEDIA CTL_CODE(IOCTL_CDROM_BASE, 0x0202, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_LOAD_MEDIA CTL_CODE(IOCTL_CDROM_BASE, 0x0203, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_RESERVE CTL_CODE(IOCTL_CDROM_BASE, 0x0204, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_RELEASE CTL_CODE(IOCTL_CDROM_BASE, 0x0205, METHOD_BUFFERED, FILE_READ_ACCESS) +#define IOCTL_CDROM_FIND_NEW_DEVICES CTL_CODE(IOCTL_CDROM_BASE, 0x0206, METHOD_BUFFERED, FILE_READ_ACCESS) + +// +// The following file contains the IOCTL_STORAGE class ioctl definitions +// + +//#include "ntddstor.h" + +// begin_winioctl + +// +// The following device control code is for the SIMBAD simulated bad +// sector facility. See SIMBAD.H in this directory for related structures. +// + +#define IOCTL_CDROM_SIMBAD CTL_CODE(IOCTL_CDROM_BASE, 0x1003, METHOD_BUFFERED, FILE_READ_ACCESS) + +// +// Maximum CD Rom size +// + +#define MAXIMUM_NUMBER_TRACKS 100 +#define MAXIMUM_CDROM_SIZE 804 +#define MINIMUM_CDROM_READ_TOC_EX_SIZE 2 // two bytes min transferred + +// +// READ_TOC_EX structure +// +typedef struct _CDROM_READ_TOC_EX { + UCHAR Format : 4; + UCHAR Reserved1 : 3; // future expansion + UCHAR Msf : 1; + UCHAR SessionTrack; + UCHAR Reserved2; // future expansion + UCHAR Reserved3; // future expansion +} CDROM_READ_TOC_EX, *PCDROM_READ_TOC_EX; + +#define CDROM_READ_TOC_EX_FORMAT_TOC 0x00 +#define CDROM_READ_TOC_EX_FORMAT_SESSION 0x01 +#define CDROM_READ_TOC_EX_FORMAT_FULL_TOC 0x02 +#define CDROM_READ_TOC_EX_FORMAT_PMA 0x03 +#define CDROM_READ_TOC_EX_FORMAT_ATIP 0x04 +#define CDROM_READ_TOC_EX_FORMAT_CDTEXT 0x05 + +// +// CD ROM Table OF Contents (TOC) +// Format 0 - Get table of contents +// + +typedef struct _TRACK_DATA { + UCHAR Reserved; + UCHAR Control : 4; + UCHAR Adr : 4; + UCHAR TrackNumber; + UCHAR Reserved1; + UCHAR Address[4]; +} TRACK_DATA, *PTRACK_DATA; + +typedef struct _CDROM_TOC { + + // + // Header + // + + UCHAR Length[2]; // add two bytes for this field + UCHAR FirstTrack; + UCHAR LastTrack; + + // + // Track data + // + + TRACK_DATA TrackData[MAXIMUM_NUMBER_TRACKS]; +} CDROM_TOC, *PCDROM_TOC; + +#define CDROM_TOC_SIZE sizeof(CDROM_TOC) + +// +// CD ROM Table OF Contents +// Format 1 - Session Information +// + +typedef struct _CDROM_TOC_SESSION_DATA { + + // + // Header + // + + UCHAR Length[2]; // add two bytes for this field + UCHAR FirstCompleteSession; + UCHAR LastCompleteSession; + + // + // One track, representing the first track + // of the last finished session + // + + TRACK_DATA TrackData[1]; + +} CDROM_TOC_SESSION_DATA, *PCDROM_TOC_SESSION_DATA; + + +// +// CD ROM Table OF Contents +// Format 2 - Full TOC +// + +typedef struct _CDROM_TOC_FULL_TOC_DATA_BLOCK { + UCHAR SessionNumber; + UCHAR Control : 4; + UCHAR Adr : 4; + UCHAR Reserved1; + UCHAR Point; + UCHAR MsfExtra[3]; + UCHAR Zero; + UCHAR Msf[3]; +} CDROM_TOC_FULL_TOC_DATA_BLOCK, *PCDROM_TOC_FULL_TOC_DATA_BLOCK; + +typedef struct _CDROM_TOC_FULL_TOC_DATA { + + // + // Header + // + + UCHAR Length[2]; // add two bytes for this field + UCHAR FirstCompleteSession; + UCHAR LastCompleteSession; + + // + // one to N descriptors included + // + + CDROM_TOC_FULL_TOC_DATA_BLOCK Descriptors[0]; + +} CDROM_TOC_FULL_TOC_DATA, *PCDROM_TOC_FULL_TOC_DATA; + +// +// CD ROM Table OF Contents +// Format 3 - Program Memory Area +// +typedef struct _CDROM_TOC_PMA_DATA { + + // + // Header + // + + UCHAR Length[2]; // add two bytes for this field + UCHAR Reserved1; + UCHAR Reserved2; + + // + // one to N descriptors included + // + + CDROM_TOC_FULL_TOC_DATA_BLOCK Descriptors[0]; + +} CDROM_TOC_PMA_DATA, *PCDROM_TOC_PMA_DATA; + +// +// CD ROM Table OF Contents +// Format 4 - Absolute Time In Pregroove +// + +typedef struct _CDROM_TOC_ATIP_DATA_BLOCK { + + UCHAR CdrwReferenceSpeed : 3; + UCHAR Reserved3 : 1; + UCHAR WritePower : 3; + UCHAR True1 : 1; + UCHAR Reserved4 : 6; + UCHAR UnrestrictedUse : 1; + UCHAR Reserved5 : 1; + UCHAR A3Valid : 1; + UCHAR A2Valid : 1; + UCHAR A1Valid : 1; + UCHAR DiscSubType : 3; + UCHAR IsCdrw : 1; + UCHAR True2 : 1; + UCHAR Reserved7; + + UCHAR LeadInMsf[3]; + UCHAR Reserved8; + + UCHAR LeadOutMsf[3]; + UCHAR Reserved9; + + UCHAR A1Values[3]; + UCHAR Reserved10; + + UCHAR A2Values[3]; + UCHAR Reserved11; + + UCHAR A3Values[3]; + UCHAR Reserved12; + +} CDROM_TOC_ATIP_DATA_BLOCK, *PCDROM_TOC_ATIP_DATA_BLOCK; + +typedef struct _CDROM_TOC_ATIP_DATA { + + // + // Header + // + + UCHAR Length[2]; // add two bytes for this field + UCHAR Reserved1; + UCHAR Reserved2; + + // + // zero? to N descriptors included. + // + + CDROM_TOC_ATIP_DATA_BLOCK Descriptors[0]; + +} CDROM_TOC_ATIP_DATA, *PCDROM_TOC_ATIP_DATA; + +// +// CD ROM Table OF Contents +// Format 5 - CD Text Info +// +#define CD_TEXT_FIELD_LENGTH 12 +typedef struct _CDROM_TOC_CD_TEXT_DATA_BLOCK { + UCHAR PackType; + UCHAR TrackNumber : 7; + UCHAR ExtensionFlag : 1; // should be zero! + UCHAR SequenceNumber; + UCHAR CharacterPosition : 4; + UCHAR BlockNumber : 3; + UCHAR Unicode : 1; + union { + UCHAR Text[12]; + WCHAR WText[6]; + }; + UCHAR CRC[2]; +} CDROM_TOC_CD_TEXT_DATA_BLOCK, *PCDROM_TOC_CD_TEXT_DATA_BLOCK; + +typedef struct _CDROM_TOC_CD_TEXT_DATA { + + // + // Header + // + + UCHAR Length[2]; // add two bytes for this field + UCHAR Reserved1; + UCHAR Reserved2; + + // + // the text info comes in discrete blocks of + // a heavily-overloaded structure + // + + CDROM_TOC_CD_TEXT_DATA_BLOCK Descriptors[0]; + +} CDROM_TOC_CD_TEXT_DATA, *PCDROM_TOC_CD_TEXT_DATA; + +// +// These are the types used for PackType field in CDROM_TOC_CD_TEXT_DATA_BLOCK +// and also for requesting specific info from IOCTL_CDROM_READ_CD_TEXT +// +#define CDROM_CD_TEXT_PACK_ALBUM_NAME 0x80 +#define CDROM_CD_TEXT_PACK_PERFORMER 0x81 +#define CDROM_CD_TEXT_PACK_SONGWRITER 0x82 +#define CDROM_CD_TEXT_PACK_COMPOSER 0x83 +#define CDROM_CD_TEXT_PACK_ARRANGER 0x84 +#define CDROM_CD_TEXT_PACK_MESSAGES 0x85 +#define CDROM_CD_TEXT_PACK_DISC_ID 0x86 +#define CDROM_CD_TEXT_PACK_GENRE 0x87 +#define CDROM_CD_TEXT_PACK_TOC_INFO 0x88 +#define CDROM_CD_TEXT_PACK_TOC_INFO2 0x89 +// 0x8a - 0x8d are reserved.... +#define CDROM_CD_TEXT_PACK_UPC_EAN 0x8e +#define CDROM_CD_TEXT_PACK_SIZE_INFO 0x8f + +// +// Play audio starting at MSF and ending at MSF +// + +typedef struct _CDROM_PLAY_AUDIO_MSF { + UCHAR StartingM; + UCHAR StartingS; + UCHAR StartingF; + UCHAR EndingM; + UCHAR EndingS; + UCHAR EndingF; +} CDROM_PLAY_AUDIO_MSF, *PCDROM_PLAY_AUDIO_MSF; + +// +// Seek to MSF +// + +typedef struct _CDROM_SEEK_AUDIO_MSF { + UCHAR M; + UCHAR S; + UCHAR F; +} CDROM_SEEK_AUDIO_MSF, *PCDROM_SEEK_AUDIO_MSF; + + +// +// Flags for the disk type +// + +typedef struct _CDROM_DISK_DATA { + + ULONG DiskData; + +} CDROM_DISK_DATA, *PCDROM_DISK_DATA; + +#define CDROM_DISK_AUDIO_TRACK (0x00000001) +#define CDROM_DISK_DATA_TRACK (0x00000002) + +// +// CD ROM Data Mode Codes, used with IOCTL_CDROM_READ_Q_CHANNEL +// + +#define IOCTL_CDROM_SUB_Q_CHANNEL 0x00 +#define IOCTL_CDROM_CURRENT_POSITION 0x01 +#define IOCTL_CDROM_MEDIA_CATALOG 0x02 +#define IOCTL_CDROM_TRACK_ISRC 0x03 + +typedef struct _CDROM_SUB_Q_DATA_FORMAT { + UCHAR Format; + UCHAR Track; +} CDROM_SUB_Q_DATA_FORMAT, *PCDROM_SUB_Q_DATA_FORMAT; + + +// +// CD ROM Sub-Q Channel Data Format +// + +typedef struct _SUB_Q_HEADER { + UCHAR Reserved; + UCHAR AudioStatus; + UCHAR DataLength[2]; +} SUB_Q_HEADER, *PSUB_Q_HEADER; + +typedef struct _SUB_Q_CURRENT_POSITION { + SUB_Q_HEADER Header; + UCHAR FormatCode; + UCHAR Control : 4; + UCHAR ADR : 4; + UCHAR TrackNumber; + UCHAR IndexNumber; + UCHAR AbsoluteAddress[4]; + UCHAR TrackRelativeAddress[4]; +} SUB_Q_CURRENT_POSITION, *PSUB_Q_CURRENT_POSITION; + +typedef struct _SUB_Q_MEDIA_CATALOG_NUMBER { + SUB_Q_HEADER Header; + UCHAR FormatCode; + UCHAR Reserved[3]; + UCHAR Reserved1 : 7; + UCHAR Mcval : 1; + UCHAR MediaCatalog[15]; +} SUB_Q_MEDIA_CATALOG_NUMBER, *PSUB_Q_MEDIA_CATALOG_NUMBER; + +typedef struct _SUB_Q_TRACK_ISRC { + SUB_Q_HEADER Header; + UCHAR FormatCode; + UCHAR Reserved0; + UCHAR Track; + UCHAR Reserved1; + UCHAR Reserved2 : 7; + UCHAR Tcval : 1; + UCHAR TrackIsrc[15]; +} SUB_Q_TRACK_ISRC, *PSUB_Q_TRACK_ISRC; + +typedef union _SUB_Q_CHANNEL_DATA { + SUB_Q_CURRENT_POSITION CurrentPosition; + SUB_Q_MEDIA_CATALOG_NUMBER MediaCatalog; + SUB_Q_TRACK_ISRC TrackIsrc; +} SUB_Q_CHANNEL_DATA, *PSUB_Q_CHANNEL_DATA; + +// +// Audio Status Codes +// + +#define AUDIO_STATUS_NOT_SUPPORTED 0x00 +#define AUDIO_STATUS_IN_PROGRESS 0x11 +#define AUDIO_STATUS_PAUSED 0x12 +#define AUDIO_STATUS_PLAY_COMPLETE 0x13 +#define AUDIO_STATUS_PLAY_ERROR 0x14 +#define AUDIO_STATUS_NO_STATUS 0x15 + +// +// ADR Sub-channel Q Field +// + +#define ADR_NO_MODE_INFORMATION 0x0 +#define ADR_ENCODES_CURRENT_POSITION 0x1 +#define ADR_ENCODES_MEDIA_CATALOG 0x2 +#define ADR_ENCODES_ISRC 0x3 + +// +// Sub-channel Q Control Bits +// + +#define AUDIO_WITH_PREEMPHASIS 0x1 +#define DIGITAL_COPY_PERMITTED 0x2 +#define AUDIO_DATA_TRACK 0x4 +#define TWO_FOUR_CHANNEL_AUDIO 0x8 + +// +// Get Audio control parameters +// + +typedef struct _CDROM_AUDIO_CONTROL { + UCHAR LbaFormat; + USHORT LogicalBlocksPerSecond; +} CDROM_AUDIO_CONTROL, *PCDROM_AUDIO_CONTROL; + +// +// Volume control - Volume takes a value between 1 and 0xFF. +// SCSI-II CDROM audio suppports up to 4 audio ports with +// Independent volume control. +// + +typedef struct _VOLUME_CONTROL { + UCHAR PortVolume[4]; +} VOLUME_CONTROL, *PVOLUME_CONTROL; + +typedef enum _TRACK_MODE_TYPE { + YellowMode2, + XAForm2, + CDDA +} TRACK_MODE_TYPE, *PTRACK_MODE_TYPE; + +// +// Passed to cdrom to describe the raw read, ie. Mode 2, Form 2, CDDA... +// + +typedef struct __RAW_READ_INFO { + LARGE_INTEGER DiskOffset; + ULONG SectorCount; + TRACK_MODE_TYPE TrackMode; +} RAW_READ_INFO, *PRAW_READ_INFO; + +#ifdef __cplusplus +} +#endif + + +#if _MSC_VER >= 1200 +#pragma warning(pop) // un-sets any local warning changes +#else +#pragma warning(default:4200) // array[0] is not a warning for this file +#pragma warning(default:4201) // nameless struct/unions +#pragma warning(default:4214) // bit fields other than int +#endif + + +#endif // _NTDDCDRM_ + +// end_winioctl + + diff --git a/Src/Plugins/Input/in_cdda/resource.h b/Src/Plugins/Input/in_cdda/resource.h new file mode 100644 index 00000000..3d60abbb --- /dev/null +++ b/Src/Plugins/Input/in_cdda/resource.h @@ -0,0 +1,136 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by in_cdda.rc +// +#define IDS_NULLSOFT_CD_PLUGIN_OLD 1 +#define IDS_QUERYING 2 +#define IDC_REMOVE 3 +#define IDS_NOT_FOUND 3 +#define IDC_GETCDDB 4 +#define IDS_PROCESSING 4 +#define IDS_CDDB_NOT_INSTALLED 5 +#define IDS_SUCCESS 6 +#define IDS_CLOSE 7 +#define IDS_INITIALIZING 8 +#define IDS_ABORTING 9 +#define IDS_UNLIMITED 10 +#define IDS_PURCHASE_WINAMP_PRO_PROMPT 11 +#define IDS_WINAMP_PRO_FEATURE 12 +#define IDS_ARTIST 13 +#define IDS_TITLE 14 +#define IDS_ALBUM_ARTIST 15 +#define IDS_NULLSOFT_CD_PLUGIN2 16 +#define IDS_ABOUT 17 +#define IDS_CD_CURRENTLY_IN_USE 18 +#define IDS_DRIVE_IN_USE 19 +#define IDS_DRIVE_NOT_FOUND 20 +#define IDS_CD_NOT_PRESENT 21 +#define IDS_CANNOT_PLAY_TRACK 22 +#define IDS_STRING0 23 +#define IDS_CDDA_AUDIO_TRACKS 23 +#define IDS_RIPPNG 24 +#define IDS_RIPPING 24 +#define IDS_TRACK_X 25 +#define IDS_UNKNOWN 26 +#define IDS_UNKNOWN_ARTIST 27 +#define IDS_FAMILY_STRING 28 +#define IDS_ABOUT_TEXT 29 +#define IDD_DIALOG1 101 +#define IDD_DIALOG2 102 +#define IDD_MULSEL 103 +#define IDD_SERVSEL 104 +#define IDS_ABORT 109 +#define IDS_CDDB_PROGRESS_CONNECTING 110 +#define IDS_CDDB_PROGRESS_SENDING 111 +#define IDS_CDDB_PROGRESS_RECEIVING 112 +#define IDS_CDDB_PROGRESS_WAITING 113 +#define IDS_CDDB_PROGRESS_CANCELLED 114 +#define IDS_CDDB_PROGRESS_COMPLETED 115 +#define IDS_CDDB_E_BUSY 116 +#define IDS_CDDB_E_FAIL 117 +#define IDS_CDDB_E_BADTOC 118 +#define IDS_REASON 119 +#define IDS_FOUND_MULTIPLE 120 +#define IDS_RESOLVING 121 +#define IDS_SUBMITTINGDISC 122 +#define IDS_FOUND_EXACT 123 +#define IDS_SUBMITNEW 124 +#define IDS_SUBMITDISC_TITLE 125 +#define IDS_SUBMITTING 126 +#define IDS_OPENING 127 +#define IDS_FUZZYDISC_TITLE 128 +#define IDS_ACCEPT 129 +#define IDS_LOOKUPRESULT_TITLE 130 +#define IDS_SUBMIT_OFFER 131 +#define IDS_CDDB_E_ABORT 132 +#define IDS_CDTEXT 133 +#define IDD_DIALOG3 134 +#define IDD_MUSICID 134 +#define IDS_TRACK 135 +#define IDS_COMPOSER 136 +#define IDD_PREFS_CDRIP 232 +#define IDD_CDDB_PROGRESS 233 +#define IDD_CDTEXT 234 +#define IDC_SAMPLE 1000 +#define IDC_STATUS 1001 +#define ID_ABORTBABY 1002 +#define IDC_SERV 1003 +#define IDC_EMAIL 1004 +#define IDC_CDDB 1005 +#define IDC_EMAIL2 1006 +#define IDC_LIST1 1006 +#define IDC_RAS 1006 +#define IDC_TRACKLIST 1006 +#define IDC_LV_EXT 1006 +#define IDC_BUTTON1 1007 +#define IDC_CDTEXT 1007 +#define IDC_CDDBICON 1008 +#define IDC_CDDBICON2 1009 +#define IDC_TITLE 1010 +#define IDC_ARTIST 1011 +#define IDC_TRACKS 1012 +#define IDC_EDITTEXT 1013 +#define IDC_CDDBNOTE 1014 +#define IDC_PUBLISHER 1014 +#define IDC_DIGITAL 1015 +#define IDC_GENRE 1015 +#define IDC_YEAR 1016 +#define IDC_VERITAS 1016 +#define IDC_COMBO1 1019 +#define IDC_PROGRESS 1020 +#define IDC_STATUS_TEXT 1021 +#define IDC_EDITARTIST 1022 +#define IDC_CHECK1 1024 +#define IDC_COMPILATION 1024 +#define IDC_BUTTON2 1025 +#define IDC_EDIT 1025 +#define IDC_SUBMIT 1025 +#define IDC_CLOSE 1027 +#define IDC_DISC 1030 +#define IDC_DISCS 1031 +#define IDC_NOTES 1032 +#define IDC_LABEL 1033 +#define IDC_EDIT1 1034 +#define IDC_LBL_CAPTION 1035 +#define IDC_EDIT2 1035 +#define IDC_COMPOSER 1035 +#define IDC_ALBUM 1036 +#define IDC_LBL_STATUS 1039 +#define IDC_PRG_STATUS 1040 +#define IDC_LBL_REASON 1041 +#define IDC_EDIT_GRACENOTE 1042 +#define IDC_LOOKUP 1043 +#define IDC_BUTTON4 1044 +#define IDC_USE 1044 +#define IDS_NULLSOFT_CD_PLUGIN 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 136 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1045 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_cdda/scsi_id.cpp b/Src/Plugins/Input/in_cdda/scsi_id.cpp new file mode 100644 index 00000000..78410457 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/scsi_id.cpp @@ -0,0 +1,124 @@ +#include <windows.h> +#include <winioctl.h> + +//functions to get the SCSI ID from a drive letter +//thanks to Microsoft for making this a nightmare... + +// begin ntddscsi.h definitions + +#define IOCTL_SCSI_BASE \ + FILE_DEVICE_CONTROLLER + +#define IOCTL_SCSI_GET_ADDRESS CTL_CODE \ + ( \ + IOCTL_SCSI_BASE, \ + 0x0406, \ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS \ + ) + +#define IOCTL_SCSI_GET_INQUIRY_DATA \ + CTL_CODE( \ + IOCTL_SCSI_BASE, \ + 0x0403, \ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS \ + ) + +typedef struct _SCSI_ADDRESS { + ULONG Length; + UCHAR PortNumber; + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; +}SCSI_ADDRESS, *PSCSI_ADDRESS; + +// +// Define SCSI information. +// Used with the IOCTL_SCSI_GET_INQUIRY_DATA IOCTL. +// + +typedef struct _SCSI_BUS_DATA { + UCHAR NumberOfLogicalUnits; + UCHAR InitiatorBusId; + ULONG InquiryDataOffset; +}SCSI_BUS_DATA, *PSCSI_BUS_DATA; + +// +// Define SCSI adapter bus information structure.. +// Used with the IOCTL_SCSI_GET_INQUIRY_DATA IOCTL. +// + +typedef struct _SCSI_ADAPTER_BUS_INFO { + UCHAR NumberOfBuses; + SCSI_BUS_DATA BusData[1]; +} SCSI_ADAPTER_BUS_INFO, *PSCSI_ADAPTER_BUS_INFO; + +// +// Define SCSI adapter bus information. +// Used with the IOCTL_SCSI_GET_INQUIRY_DATA IOCTL. +// + +typedef struct _SCSI_INQUIRY_DATA { + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; + BOOLEAN DeviceClaimed; + ULONG InquiryDataLength; + ULONG NextInquiryDataOffset; + UCHAR InquiryData[1]; +}SCSI_INQUIRY_DATA, *PSCSI_INQUIRY_DATA; + +// end ntddscsi.h definitions + +int getSCSIIDFromDrive(char driveletter, int *host, int *id, int *lun) +{ + //FUCKO: only works on NT :( + + SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX); + wchar_t tmp[128] = {0}; + wsprintf(tmp,L"\\\\.\\%c:",driveletter); + HANDLE device = CreateFile( + tmp, + 0, // no particular access necessary + // for the IO control we're using + FILE_SHARE_READ + | FILE_SHARE_WRITE + | FILE_SHARE_DELETE, + 0, // no security attrs - ignored anyway + OPEN_EXISTING, + 0, // no attributes or flags + 0 // no template + ); + + if( device == INVALID_HANDLE_VALUE ) return 0; + + SCSI_ADDRESS sa; + ULONG bytes; + + BOOL status = DeviceIoControl( + device, + IOCTL_SCSI_GET_ADDRESS, + 0, 0, // no input buffer + &sa, sizeof(sa), // output args + &bytes, // bytes returned + 0 // ignored + ); + + if (!status && 50 == GetLastError()) + { + *host = ((driveletter > 'a') ? (driveletter - 'a') : (driveletter - 'A')); + *id = 0; + *lun = 0; + CloseHandle(device); + return 1; + } + CloseHandle(device); + + if( !status ) return 0; + + *host=sa.PortNumber; + *id=sa.TargetId; + *lun=sa.Lun; + return 1; +} diff --git a/Src/Plugins/Input/in_cdda/util.cpp b/Src/Plugins/Input/in_cdda/util.cpp new file mode 100644 index 00000000..8fb2d243 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/util.cpp @@ -0,0 +1,23 @@ +#include "Main.h" + +void WaitForEvent(HANDLE hEvent, DWORD msMaxWaitTime) +{ + // DWORD i; + MSG msg; + const unsigned long eachWait = 10; + unsigned long totalWait = 0; + + while (WaitForSingleObject(hEvent, eachWait) == WAIT_TIMEOUT) + { + while (PeekMessage(&msg, (HWND) NULL, 0, 0, PM_REMOVE)) + { + //TranslateMessage(&msg); + DispatchMessage(&msg); + } + + totalWait += eachWait; + if (totalWait >= msMaxWaitTime) + break; + + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/version.rc2 b/Src/Plugins/Input/in_cdda/version.rc2 new file mode 100644 index 00000000..56aab11f --- /dev/null +++ b/Src/Plugins/Input/in_cdda/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 4,7,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", "4,7,0,0" + VALUE "InternalName", "Nullsoft CD Plug-in" + VALUE "LegalCopyright", "Copyright © 1997-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_cdda.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_cdda/windac/Aspi.h b/Src/Plugins/Input/in_cdda/windac/Aspi.h new file mode 100644 index 00000000..cc55c36e --- /dev/null +++ b/Src/Plugins/Input/in_cdda/windac/Aspi.h @@ -0,0 +1,410 @@ +#ifndef ASPI_INCLUDED +#define ASPI_INCLUDED + +#include <Windows.h> +#include <Stdio.h> +#include <Stdlib.h> + +// Handle to the ASPI libaray +extern HINSTANCE hAspiLib; +void GetAspiError(int nErrorCode,LPSTR lpszError); + +// 1 byte alignment or SCSI structures +#pragma pack(push,1) + +#define MAX_SCSIDEVICES 16 + + +#define TIMEOUT 10000 + + +typedef void (*POSTPROCFUNC)(); + +typedef struct TOC_TAG +{ + BYTE _reserved1; + BYTE bFlags; + BYTE bTrack; + BYTE _reserved2; + DWORD dwStartSector; +} TOC; + + +// The SRB_Flags are defined below. These flags may be OR'd together to form +// the final value for SRB_Flags in the SRB. Note that SRB_POSTING and +// SRB_EVENT_NOTIFY are mutually exclusive, as are SRB_DIR_IN and SRB_DIR_OUT. In +// addition, the directioin bits (SRB_DIR_IN and SRB_DIR_OUT) MUST be set +// correctly on commands which transfer data. Using SRB_DIR_SCSI is no longer +// an option as in ASPI for DOS and ASPI for Win16. + +#define SRB_POSTING 0x01 // Enable ASPI command completion posting. See section on posting below. +#define SRB_ENABLE_RESIDUAL_COUNT 0x04 // Enables reporting of residual byte count.This flag is only significant if the host adapter reports support for residual byte count in the SC_HA_INQUIRY command. When data underrun occurs, the SRB_BufLen field is updated to reflect the remaining bytes to transfer. +#define SRB_DIR_IN 0x08 // Data transfer from SCSI target to host. +#define SRB_DIR_OUT 0x10 // Data transfer from host to SCSI target. +#define SRB_EVENT_NOTIFY 0x40 // Enable ASPI command event notification. See section on event notification below. + + +// Inquiry DeviceTypeCodes +#define DTC_DISK 0x00 // Direct-access device +#define DTC_TAPE 0x01 // Sequential-access device +#define DTC_PRINTER 0x02 // Printer device +#define DTC_PROCESSOR 0x03 // Processor device +#define DTC_WORM 0x04 // Write-once device +#define DTC_CDROM 0x05 // CD-ROM device +#define DTC_SCANNER 0x06 // Scanner device +#define DTC_OPTICAL 0x07 // Optical memory device +#define DTC_JUKEBOX 0x08 // Medium changer device +#define DTC_COMM 0x09 // Communications device +#define DTC_PREPRESS1 0x0A // Pre-press device 1 +#define DTC_PREPRESS2 0x0B // Pre-press device 2 +#define DTC_UNKNOWN 0x1F // Unknown or no device type + + +/*************************************************************************** + ** SRB Status + ***************************************************************************/ +#define SS_PENDING 0x00 /* SRB being processed */ +#define SS_COMP 0x01 /* SRB completed without error */ +#define SS_ABORTED 0x02 /* SRB aborted */ +#define SS_ABORT_FAIL 0x03 /* Unable to abort SRB */ +#define SS_ERR 0x04 /* SRB completed with error */ +#define SS_INVALID_CMD 0x80 /* Invalid ASPI command */ +#define SS_INVALID_HA 0x81 /* Invalid host adapter number */ +#define SS_NO_DEVICE 0x82 /* SCSI device not installed */ +#define SS_INVALID_SRB 0xE0 /* Invalid parameter set in SRB */ +#define SS_OLD_MANAGER 0xE1 /* ASPI manager doesn't support */ + /* windows */ +#define SS_BUFFER_ALIGN 0xE1 /* Buffer not aligned (replaces */ + /* SS_OLD_MANAGER in Win32) */ +#define SS_ILLEGAL_MODE 0xE2 /* Unsupported Windows mode */ +#define SS_NO_ASPI 0xE3 /* No ASPI managers */ +#define SS_FAILED_INIT 0xE4 /* ASPI for windows failed init */ +#define SS_ASPI_IS_BUSY 0xE5 /* No resources available to */ + /* execute command */ +#define SS_BUFFER_TO_BIG 0xE6 /* Buffer size too big to handle */ +#define SS_BUFFER_TOO_BIG 0xE6 /* Correct spelling of 'too' */ +#define SS_MISMATCHED_COMPONENTS 0xE7 /* The DLLs/EXEs of ASPI don't */ + /* version check */ +#define SS_NO_ADAPTERS 0xE8 /* No host adapters to manager */ +#define SS_INSUFFICIENT_RESOURCES 0xE9 /* Couldn't allocate resources */ + /* needed to init */ +#define SS_ASPI_IS_SHUTDOWN 0xEA /* Call came to ASPI after */ + /* PROCESS_DETACH */ +#define SS_BAD_INSTALL 0xEB /* The DLL or other components */ + /* are installed wrong */ + + +// SRB defines +#define SC_HA_INQUIRY 0x00 // Get information about installed host adapters,including the number of installed adapters. +#define SC_GET_DEV_TYPE 0x01 // Get information about installed SCSI devices. +#define SC_EXEC_SCSI_CMD 0x02 // Execute SCSI I/O. +#define SC_ABORT_SRB 0x03 // Abort an outstanding I/O request. +#define SC_RESET_DEV 0x04 // Reset an individual SCSI target. +#define SC_GET_DISK_INFO 0x06 // Get information on disk type SCSI devices (not available under Windows NT). +#define SC_GETSET_TIMEOUTS 0x08 + +// MISC defines +#define CDSAMPLEFREQ 44100 +#define TRACKSPERSEC 75 + +#define CB_CDDASECTOR 2352 +#define CB_QSUBCHANNEL 0 +#define CB_CDROMSECTOR 2048 +#define CB_AUDIO (CB_CDDASECTOR-CB_QSUBCHANNEL) + + + +typedef struct +{ + BYTE SRB_Cmd; // ASPI command code + BYTE SRB_Status; // ASPI command status byte + BYTE SRB_HaId; // ASPI host adapter number + BYTE SRB_Flags; // ASPI request flags + DWORD SRB_Hdr_Rsvd; // Reserved, MUST = 0 +} SRB_HEADER, *LPSRB; + + +typedef struct +{ + BYTE SRB_Cmd; // ASPI command code = SC_HA_INQUIRY + BYTE SRB_Status; // ASPI command status byte + BYTE SRB_HaId; // ASPI host adapter number + BYTE SRB_Flags; // ASPI request flags + DWORD SRB_Hdr_Rsvd; // Reserved, MUST = 0 + BYTE HA_Count; // Number of host adapters present + BYTE HA_SCSI_ID; // SCSI ID of host adapter + BYTE HA_ManagerId[16]; // String describing the manager + BYTE HA_Identifier[16]; // String describing the host adapter + BYTE HA_Unique[16]; // Host Adapter Unique parameters + WORD HA_Rsvd1; +} SRB_HAINQUIRY, *LPSRB_HAINQUIRY; + + +typedef struct +{ + BYTE SRB_Cmd; // ASPI command code = SC_GET_DEV_TYPE + BYTE SRB_Status; // ASPI command status byte + BYTE SRB_HaId; // ASPI host adapter number + BYTE SRB_Flags; // Reserved + DWORD SRB_Hdr_Rsvd; // Reserved + BYTE SRB_Target; // Target's SCSI ID + BYTE SRB_Lun; // Target's LUN number + BYTE SRB_DeviceType; // Target's peripheral device type + BYTE SRB_Rsvd1; // Reserved for alignment +} SRB_GDEVBLOCK, *LPSRB_GDEVBLOCK; + + +#define SENSE_LEN 14 // Maximum sense length + + +typedef struct +{ + BYTE SRB_Cmd; // ASPI command code = SC_EXEC_SCSI_CMD + BYTE SRB_Status; // ASPI command status byte + BYTE SRB_HaId; // ASPI host adapter number + BYTE SRB_Flags; // ASPI request flags + DWORD SRB_Hdr_Rsvd; // Reserved + BYTE SRB_Target; // Target's SCSI ID + BYTE SRB_Lun; // Target's LUN number + WORD SRB_Rsvd1; // Reserved for Alignment + DWORD SRB_BufLen; // Data Allocation Length + BYTE *SRB_BufPointer; // Data Buffer Point + BYTE SRB_SenseLen; // Sense Allocation Length + BYTE SRB_CDBLen; // CDB Length + BYTE SRB_HaStat; // Host Adapter Status + BYTE SRB_TargStat; // Target Status + void (*SRB_PostProc)(); // Post routine + void *SRB_Rsvd2; // Reserved + BYTE SRB_Rsvd3[16]; // Reserved for expansion + BYTE CDBByte[16]; // SCSI CDB + BYTE SenseArea[SENSE_LEN+2]; // Request Sense buffer +} SRB_EXECSCSICMD, *LPSRB_EXECSCSICMD; + + +typedef struct +{ + BYTE SRB_Cmd; // ASPI command code = SC_EXEC_SCSI_CMD + BYTE SRB_Status; // ASPI command status byte + BYTE SRB_HaId; // ASPI host adapter number + BYTE SRB_Flags; // Reserved + DWORD SRB_Hdr_Rsvd; // Reserved + BYTE SRB_Target; // Target's SCSI ID + BYTE SRB_Lun; // Target's LUN number + BYTE SRB_DriveFlags; // Driver flags + BYTE SRB_Int13HDriveInfo;// Host Adapter Status + BYTE SRB_Heads; // Preferred number of heads translation + BYTE SRB_Sectors; // Preferred number of sectors translation + BYTE SRB_Rsvd1[10]; // Reserved +} SRB_GETDISKINFO, *LPSRB_GETDISKINFO; + + + +typedef struct +{ + BYTE SRB_Cmd; // ASPI command code = SC_RESET_DEV + BYTE SRB_Status; // ASPI command status byte + BYTE SRB_HaId; // ASPI host adapter number + BYTE SRB_Flags; // Reserved + DWORD SRB_Hdr_Rsvd; // Reserved + BYTE SRB_Target; // Target's SCSI ID + BYTE SRB_Lun; // Target's LUN number + BYTE SRB_Rsvd1[12]; // Reserved for Alignment + BYTE SRB_HaStat; // Host Adapter Status + BYTE SRB_TargStat; // Target Status + void *SRB_PostProc; // Post routine + void *SRB_Rsvd2; // Reserved + BYTE SRB_Rsvd3[32]; // Reserved +} SRB_BUSDEVICERESET, *LPSRB_BUSDEVICERESET; + + +typedef struct +{ + BYTE SRB_Cmd; /* 00/000 ASPI cmd code == SC_ABORT_SRB */ + BYTE SRB_Status; /* 01/001 ASPI command status byte */ + BYTE SRB_HaID; /* 02/002 ASPI host adapter number */ + BYTE SRB_Flags; /* 03/003 Reserved, must = 0 */ + DWORD SRB_Hdr_Rsvd; /* 04/004 Reserved, must = 0 */ + void *SRB_ToAbort; /* 08/008 Pointer to SRB to abort */ +} SRB_Abort, *PSRB_Abort, FAR *LPSRB_Abort; + + +typedef unsigned char Ucbit; +typedef unsigned char u_char; + +#define MP_P_CODE \ + Ucbit p_code : 6; \ + Ucbit p_res : 1; \ + Ucbit parsave : 1 + + +// CD Cap / mech status +typedef struct SCSICDMODEPAGE2A_TAG +{ + MP_P_CODE; // parsave & pagecode (0) + u_char p_len; // 0x14 = 20 Bytes (1) + + Ucbit cd_r_read : 1; // Reads CD-R media (2) + Ucbit cd_rw_read : 1; // Reads CD-RW media + Ucbit method2 : 1; // Reads fixed packet method2 media + Ucbit dvd_rom_read: 1; // Reads DVD ROM media + Ucbit dvd_r_read : 1; // Reads DVD-R media + Ucbit dvd_ram_read: 1; // Reads DVD-RAM media + Ucbit res_2_67 : 2; // Reserved + + Ucbit cd_r_write : 1; // Supports writing CD-R media (3) + Ucbit cd_rw_write : 1; // Supports writing CD-RW media + Ucbit test_write : 1; // Supports emulation write + Ucbit res_3_3 : 1; // Reserved + Ucbit dvd_r_write : 1; // Supports writing DVD-R media + Ucbit dvd_ram_write: 1; // Supports writing DVD-RAM media + Ucbit res_3_67 : 2; // Reserved + + Ucbit audio_play : 1; // Supports Audio play operation (4) + Ucbit composite : 1; // Deliveres composite A/V stream + Ucbit digital_port_2: 1; // Supports digital output on port 2 + Ucbit digital_port_1: 1; // Supports digital output on port 1 + Ucbit mode_2_form_1: 1; // Reads Mode-2 form 1 media (XA) + Ucbit mode_2_form_2: 1; // Reads Mode-2 form 2 media + Ucbit multi_session: 1; // Reads multi-session media + Ucbit res_4 : 1; // Reserved + + Ucbit cd_da_supported: 1; // Reads audio data with READ CD cmd + Ucbit cd_da_accurate: 1; // READ CD data stream is accurate + Ucbit rw_supported: 1; // Reads R-W sub channel information + Ucbit rw_deint_cor: 1; // Reads de-interleved R-W sub chan + Ucbit c2_pointers : 1; // Supports C2 error pointers + Ucbit ISRC : 1; // Reads ISRC information + Ucbit UPC : 1; // Reads media catalog number (UPC) + Ucbit read_bar_code: 1; // Supports reading bar codes + + Ucbit lock : 1; // PREVENT/ALLOW may lock media (5) + Ucbit lock_state : 1; // Lock state 0=unlocked 1=locked + Ucbit prevent_jumper: 1; // State of prev/allow jumper 0=pres + Ucbit eject : 1; // Ejects disc/cartr with STOP LoEj + Ucbit res_6_4 : 1; // Reserved + Ucbit loading_type: 3; // Loading mechanism type + + Ucbit sep_chan_vol: 1; // Vol controls each channel separat (6) + Ucbit sep_chan_mute: 1; // Mute controls each channel separat + Ucbit disk_present_rep:1; // Changer supports disk present rep + Ucbit sw_slot_sel:1; // Load empty slot in changer + Ucbit res_7 : 4; // Reserved + + BYTE ReadSpeedH; // Max. read speed in KB/s (7) + BYTE ReadSpeedL; // Max. read speed in KB/s (7) + + u_char num_vol_levels[2]; // # of supported volume levels (9) + + u_char buffer_size[2]; // Buffer size for the data in KB (11) + u_char cur_read_speed[2]; // Current read speed in KB/s (13) + u_char res_16; // Reserved (14) + + Ucbit res_17_0: 1; // Reserved (15) + Ucbit BCK : 1; // Data valid on falling edge of BCK + Ucbit RCK : 1; // Set: HIGH high LRCK=left channel + Ucbit LSBF : 1; // Set: LSB first Clear: MSB first + Ucbit length : 2; // 0=32BCKs 1=16BCKs 2=24BCKs 3=24I2c + Ucbit res_17 : 2; // Reserved + + u_char max_write_speed[2]; // Max. write speed supported in KB/s (17) + + u_char cur_write_speed[2]; // Current write speed in KB/s (19) + +} SCSICDMODEPAGE2A; + +char *fillbytes(void *tov, int cnt, char val); + + +typedef struct SCISMODEHEADER_TAG { + Ucbit sense_data_len : 8; + u_char medium_type; + Ucbit res2 : 4; + Ucbit cache : 1; + Ucbit res : 2; + Ucbit write_prot : 1; + BYTE nBlockLen; +} SCISMODEHEADER; + +typedef struct SCSIMODEHDR_6_TAG { + BYTE btModeDataLen; // 0 + BYTE btMediumType; // 1 + BYTE btDevSpecificParam; // 2 + BYTE btBlkDescrLen; // 3 +} SCSIMODEHDR_6; + + +typedef struct SCSIMODEHDR_10_TAG { + BYTE btModeDataLenH; // 0 + BYTE btModeDataLenL; // 1 + BYTE btMediumType; // 2 + BYTE btDevSpecificParam; // 3 + BYTE btReserved1; // 4 + BYTE btReserved2; // 5 + BYTE btBlkDescrLenH; // 6 + BYTE btBlkDescrLenL; // 7 +} SCSIMODEHDR_10; + +typedef struct SCSIBLOCKDESCRIPTOR_TAG { + BYTE btDensity; // 0 + BYTE btNumberOfBlocksH; // 1 + BYTE btNumberOfBlocksM; // 2 + BYTE btNumberOfBlocksL; // 3 + BYTE btReserved; // 4 + BYTE btBlockLenH; // 5 + BYTE btBlockLenM; // 6 + BYTE btBlockLenL; // 7 +} SCSIBLOCKDESCRIPTOR; + + + + +// Error recovery Parameters +typedef struct SCSICDMODEPAGE1A_TAG{ + MP_P_CODE; // 0 parsave & pagecode + u_char p_len; // 1 0x0A = 12 Bytes + Ucbit disa_correction : 1; // 2 Byte 2 + Ucbit term_on_rec_err : 1; + Ucbit report_rec_err : 1; + Ucbit en_early_corr : 1; + Ucbit read_continuous : 1; + Ucbit tranfer_block : 1; + Ucbit en_auto_reall_r : 1; + Ucbit en_auto_reall_w : 1; + u_char rd_retry_count; // 3 Byte 3 + u_char correction_span; // 4 + char head_offset_count; // 5 + char data_strobe_offset; // 6 + u_char res; // 7 + u_char wr_retry_count; // 8 + u_char res_tape[2]; // 9 + u_char recov_timelim[2]; // 11 +} SCSICDMODEPAGE1A; + + + + +typedef struct +{ + BYTE SRB_Cmd; // ASPI command code = SC_GETSET_TIMEOUTS + BYTE SRB_Status; // ASPI command status byte + BYTE SRB_HaId; // ASPI host adapter number + BYTE SRB_Flags; // ASPI request flags + DWORD SRB_Hdr_Rsvd; // Reserved + BYTE SRB_Target; // Target's SCSI ID + BYTE SRB_Lun; // Target's LUN number + DWORD SRB_Timeout; // Timeout in half seconds +} +SRB_GetSetTimeouts, *PSRB_GetSetTimeouts; + +typedef struct +{ + LPBYTE AB_BufPointer; // Pointer to the ASPI allocated buffer + DWORD AB_BufLen; // Length in bytes of the buffer + DWORD AB_ZeroFill; // Flag set to 1 if buffer should be zeroed + DWORD AB_Reserved; // Reserved, MUST = 0 +} +ASPI32BUFF, *PASPI32BUFF; + +#pragma pack(pop) + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/windac/Aspifunc.cpp b/Src/Plugins/Input/in_cdda/windac/Aspifunc.cpp new file mode 100644 index 00000000..ee8f3544 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/windac/Aspifunc.cpp @@ -0,0 +1,378 @@ +// ---------------------------------------------- +// - ASPIFUNC implementation file - +// - Written 1996-1998 by Christoph Schmelnik - +// ---------------------------------------------- + +// Version 1.40 : 24.02.1998 +// Changes: +// Set correct direction flags, to work with NT device IO interface and ATAPI drives +// Added Immediate paremeter for WaitSCSIRequest to allow detection of buffer underruns + +#include <stddef.h> +#include "aspifunc.h" + +HMODULE hDLL=0; +VOIDPROC GetASPI32SupportInfo; +SRBPROC SendASPI32Command; +int ASPIInstalled; +int RunningNT; +int NumberOfHostAdapters; + +//Implementation of base ASPI Functions +BOOL HAInquiry(int HostAdapterNumber,char *ManagerID, char *HAID,THAUnique &HAUnique) +{ + SRB_HAInquiry MySRB; + DWORD ASPI_Status; + memset(&MySRB,0,sizeof(SRB_HAInquiry)); + MySRB.SRB_Cmd = SC_HA_INQUIRY; + MySRB.SRB_HaId = HostAdapterNumber; + MySRB.SRB_Flags = 0; + MySRB.SRB_Hdr_Rsvd = 0; + ASPI_Status = SendASPI32Command ( (LPSRB) &MySRB ); + if (ASPI_Status!=SS_COMP) + return FALSE; + HAUnique=MySRB.HA_Unique; + for (int i=0; i<16; i++) + { + ManagerID[i]=MySRB.HA_ManagerId[i]; + HAID[i]=MySRB.HA_Identifier[i]; + } + ManagerID[16]=0; + HAID[16]=0; + return TRUE; +} + +int GetDeviceType(int HostAdapterNumber,int TargetId,int LUN) +{ + SRB_GDEVBlock MySRB; + DWORD ASPI_Status; + memset(&MySRB,0,sizeof(SRB_GDEVBlock)); + MySRB.SRB_Cmd = SC_GET_DEV_TYPE; + MySRB.SRB_HaId = HostAdapterNumber; + MySRB.SRB_Flags = 0; + MySRB.SRB_Hdr_Rsvd = 0; + MySRB.SRB_Target = TargetId; + MySRB.SRB_Lun = LUN; + ASPI_Status = SendASPI32Command ((LPSRB)&MySRB); + /***************************************************/ + /* If ASPI_Status == SS_COMP, MySRB.SRB_DeviceType */ + /* will contain the peripheral device type. */ + /***************************************************/ + if (ASPI_Status==SS_COMP) + return MySRB.SRB_DeviceType; + return DTYPE_UNKNOWN; +} + +BOOL ExecuteSCSIRequest(int HostAdapterNumber,int TargetId,int LUN,int RequestFlags, + TOpcode OpC, BYTE OpCLen,void *DataPtr, int DataLen, HANDLE hDriveEvent) +{ + if ((HostAdapterNumber>=NumberOfHostAdapters) && + RunningNT) + { + DWORD il, ol; + SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sb; + ZeroMemory(&sb, sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER)); + sb.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + sb.sptd.CdbLength = OpCLen; + sb.sptd.DataIn = ((SRB_DIR_IN & RequestFlags) ? 1/*SCSI_IOCTL_DATA_IN*/ : 0/*SCSI_IOCTL_DATA_OUT*/); + sb.sptd.SenseInfoLength = 32; + sb.sptd.DataTransferLength = DataLen; + sb.sptd.TimeOutValue = 2; + sb.sptd.DataBuffer = (unsigned char*) DataPtr; + sb.sptd.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, ucSenseBuf); + for (int i=0; i<OpCLen; i++) sb.sptd.Cdb[i]=OpC[i]; + il = sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER); + if (DeviceIoControl(hDriveEvent, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sb, il, &sb, il, &ol, FALSE)) + { + if (sb.sptd.ScsiStatus==0) + return TRUE; + } + return FALSE; + } + + + SRB_ExecSCSICmd MySRB; + DWORD ASPI_Status; + DWORD ASPIEventStatus; + memset(&MySRB,0,sizeof(SRB_ExecSCSICmd)); + MySRB.SRB_Cmd = SC_EXEC_SCSI_CMD; + MySRB.SRB_HaId = HostAdapterNumber; + MySRB.SRB_Flags = RequestFlags|SRB_EVENT_NOTIFY; + MySRB.SRB_Hdr_Rsvd = 0; + MySRB.SRB_Target = TargetId; + MySRB.SRB_Lun = LUN; + MySRB.SRB_BufPointer = (unsigned char*) DataPtr; + MySRB.SRB_BufLen = DataLen; + MySRB.SRB_CDBLen = OpCLen; + MySRB.SRB_SenseLen = SENSE_LEN; + MySRB.SRB_PostProc = (void(__cdecl *)(void))hDriveEvent; + for (int i=0; i<OpCLen; i++) MySRB.CDBByte[i]=OpC[i]; + + ResetEvent(hDriveEvent); + ASPI_Status = SendASPI32Command ((LPSRB)&MySRB); + + /**************************************************/ + /* Block on event till signaled */ + /**************************************************/ + + if ( MySRB.SRB_Status == SS_PENDING ) + { + ASPIEventStatus = WaitForSingleObject(hDriveEvent, TIMEOUT); + + /**************************************************/ + /* Reset event to non-signaled state. */ + /**************************************************/ + if (ASPIEventStatus == WAIT_OBJECT_0) + ResetEvent(hDriveEvent); + else + { + OutputDebugString(L"Execute Timed out\n"); + AbortSCSIRequest(MySRB); + } + } + + if (MySRB.SRB_Status==SS_COMP) + return TRUE; + + return FALSE; +} + +void FillSCSIRequest(int HostAdapterNumber,int TargetId,int LUN,int RequestFlags, + TOpcode OpC, BYTE OpCLen,void *DataPtr, int DataLen,SRB_ExecSCSICmd &MySRB, HANDLE hDriveEvent) +{ + memset(&MySRB,0,sizeof(SRB_ExecSCSICmd)); + MySRB.SRB_Cmd = SC_EXEC_SCSI_CMD; + MySRB.SRB_HaId = HostAdapterNumber; + if ((HostAdapterNumber>=NumberOfHostAdapters) && + RunningNT) + { + MySRB.SRB_Flags = RequestFlags; + } + else + { + MySRB.SRB_Flags = RequestFlags|SRB_EVENT_NOTIFY; + MySRB.SRB_Hdr_Rsvd = 0; + MySRB.SRB_PostProc = (void(__cdecl *)(void))hDriveEvent; + } + MySRB.SRB_Target = TargetId; + MySRB.SRB_Lun = LUN; + MySRB.SRB_BufPointer = (unsigned char*) DataPtr; + MySRB.SRB_BufLen = DataLen; + MySRB.SRB_CDBLen = OpCLen; + MySRB.SRB_SenseLen = SENSE_LEN; + for (int i=0; i<OpCLen; i++) MySRB.CDBByte[i]=OpC[i]; +} + +void ExecuteSCSIRequest(SRB_ExecSCSICmd &MySRB,HANDLE hDriveEvent) +{ + if ((MySRB.SRB_HaId>=NumberOfHostAdapters) && + RunningNT) + { + DWORD il, ol; + SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sb; + ZeroMemory(&sb, sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER)); + sb.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + sb.sptd.PathId = 0; + sb.sptd.TargetId = 1; + sb.sptd.Lun = 0; + sb.sptd.CdbLength = MySRB.SRB_CDBLen; + sb.sptd.DataIn = MySRB.SRB_Flags; + sb.sptd.SenseInfoLength = 32; + sb.sptd.DataTransferLength = MySRB.SRB_BufLen; + sb.sptd.TimeOutValue = TIMEOUT; + sb.sptd.DataBuffer = MySRB.SRB_BufPointer; + sb.sptd.SenseInfoOffset = + offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, ucSenseBuf); + for (int i=0; i<MySRB.SRB_CDBLen; i++) sb.sptd.Cdb[i]=MySRB.CDBByte[i]; + il = sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER); + if (DeviceIoControl(hDriveEvent, IOCTL_SCSI_PASS_THROUGH_DIRECT, + &sb, il, &sb, il, &ol, NULL)) + if (sb.sptd.ScsiStatus==0) + { + MySRB.SRB_Status=SS_COMP; + return; + } + MySRB.SRB_Status=SS_ERR; + return; + } + DWORD ASPI_Status; + ResetEvent(hDriveEvent); + ASPI_Status = SendASPI32Command ((LPSRB)&MySRB); +} + +BYTE WaitSCSIRequest(SRB_ExecSCSICmd &MySRB,HANDLE hDriveEvent,BOOL bImmediate) +{ + if ((MySRB.SRB_HaId>=NumberOfHostAdapters) && + RunningNT) + return MySRB.SRB_Status; + if ((MySRB.SRB_Status == SS_PENDING) && + !bImmediate) + { + DWORD ASPIEventStatus = WaitForSingleObject(hDriveEvent, 100); + + if (ASPIEventStatus == WAIT_OBJECT_0) + { + ResetEvent(hDriveEvent); + } + } + + return MySRB.SRB_Status; +} + +BOOL AbortSCSIRequest(SRB_ExecSCSICmd &StuckSRB) +{ + SRB_Abort AbortSRB; + DWORD ASPIStatus; + AbortSRB.SRB_Cmd = SC_ABORT_SRB; + AbortSRB.SRB_HaId = StuckSRB.SRB_HaId; + AbortSRB.SRB_Flags = 0; + AbortSRB.SRB_Hdr_Rsvd = 0; + AbortSRB.SRB_ToAbort = (LPSRB)&StuckSRB; + ASPIStatus = SendASPI32Command ( (LPSRB)&AbortSRB ); + return (ASPIStatus==SS_COMP); +} + + +int GetDeviceInfo(int HostAdapterNumber,int TargetId,int LUN,BYTE &SCSIType,char *VendorID, + char *ProductID,char *ProductRevision,HANDLE hDriveEvent) +{ + struct InquireFormat + { + BYTE ConfigPara[8]; + char VendorID[8]; + char ProductID[16]; + char ProductRevision[4]; + } DeviceInfo; + TOpcode OpC; + OpC[0]=0x12; + OpC[1]=0; + OpC[2]=0; + OpC[3]=0; + OpC[4]=sizeof(DeviceInfo); + OpC[5]=0; + memset(&DeviceInfo,0,sizeof(DeviceInfo)); + BOOL r=ExecuteSCSIRequest(HostAdapterNumber,TargetId,LUN,SRB_DIR_IN,OpC,6,(void*)&DeviceInfo,sizeof(DeviceInfo),hDriveEvent); + if (r) + { + for (int i=0; i<16; i++) + { + if (i<8) VendorID[i]=DeviceInfo.VendorID[i]; + ProductID[i]=DeviceInfo.ProductID[i]; + if (i<4) ProductRevision[i]=DeviceInfo.ProductRevision[i]; + } + VendorID[8]=0; + ProductID[16]=0; + ProductRevision[4]=0; + SCSIType=DeviceInfo.ConfigPara[2] & 0x0f; + return DeviceInfo.ConfigPara[0]; + } + return DTYPE_UNKNOWN; +} + +BOOL TestUnitReady(int HostAdapterNumber,int TargetId,int LUN,HANDLE hDriveEvent) +{ + TOpcode OpC; + OpC[0]=0; + OpC[1]=0; + OpC[2]=0; + OpC[3]=0; + OpC[4]=0; + OpC[5]=0; + return ExecuteSCSIRequest(HostAdapterNumber,TargetId,LUN,SRB_DIR_IN,OpC,6,NULL,0,hDriveEvent); +} + +BOOL ModeSense(int HostAdapterNumber,int TargetId,int LUN,TDriveMode &ModeData,int Size,int PageCode,HANDLE hDriveEvent) +{ +// while (!TestUnitReady(HostAdapterNumber,TargetId,LUN)); + TOpcode OpC; + OpC[0]=0x1a; + OpC[1]=0x00; + OpC[2]=PageCode; + OpC[3]=0x00; + OpC[4]=Size; + OpC[5]=0x00; + return ExecuteSCSIRequest(HostAdapterNumber,TargetId,LUN,SRB_DIR_IN,OpC,6,(void *)&ModeData,Size,hDriveEvent); +} + +BOOL ATAPIModeSense(int HostAdapterNumber,int TargetId,int LUN,TDriveMode &ModeData,int Size,int PageCode,HANDLE hDriveEvent) +{ +// while (!TestUnitReady(HostAdapterNumber,TargetId,LUN)); + TOpcode OpC; + OpC[0]=0x5a; + OpC[1]=0x00; + OpC[2]=PageCode; + OpC[3]=0x00; + OpC[4]=0x00; + OpC[5]=0x00; + OpC[6]=0x00; + OpC[7]=0x00; + OpC[8]=Size; + OpC[9]=0x00; + OpC[10]=0x00; + OpC[11]=0x00; + return ExecuteSCSIRequest(HostAdapterNumber,TargetId,LUN,SRB_DIR_IN,OpC,12,(void *)&ModeData,Size,hDriveEvent); +} + +BOOL addModeSense(int HostAdapterNumber,int TargetId,int LUN,TDriveMode &ModeData,int Size,HANDLE hDriveEvent) +{ +// while (!TestUnitReady(HostAdapterNumber,TargetId,LUN)); + TOpcode OpC; + OpC[0]=0xca; + OpC[1]=0x08; + OpC[2]=0x0f; + OpC[3]=0x00; + OpC[4]=0x00; + OpC[5]=0x00; + OpC[6]=0x00; + OpC[7]=0x00; + OpC[8]=Size; + OpC[9]=0x00; + return ExecuteSCSIRequest(HostAdapterNumber,TargetId,LUN,SRB_DIR_IN,OpC,10,(void *)&ModeData,Size,hDriveEvent); +} + +BOOL ModeSelect(int HostAdapterNumber,int TargetId,int LUN,TDriveMode &ModeData,int Size,HANDLE hDriveEvent) +{ +// while (!TestUnitReady(HostAdapterNumber,TargetId,LUN)); + TOpcode OpC; + OpC[0]=0x15; + if (Size==12) + OpC[1]=0x00; + else + OpC[1]=0x10; + OpC[2]=0x00; + OpC[3]=0x00; + OpC[4]=Size; + OpC[5]=0x00; + return ExecuteSCSIRequest(HostAdapterNumber,TargetId,LUN,SRB_DIR_OUT,OpC,6,(void *)&ModeData,Size,hDriveEvent); +} + +BOOL addModeSelect(int HostAdapterNumber,int TargetId,int LUN,TDriveMode &ModeData,int Size,HANDLE hDriveEvent) +{ +// while (!TestUnitReady(HostAdapterNumber,TargetId,LUN)); + TOpcode OpC; + OpC[0]=0xc5; + OpC[1]=0x10; + OpC[2]=0x00; + OpC[3]=0x00; + OpC[4]=0x00; + OpC[5]=0x00; + OpC[6]=0x00; + OpC[7]=0x00; + OpC[8]=Size; + OpC[9]=0x00; + return ExecuteSCSIRequest(HostAdapterNumber,TargetId,LUN,SRB_DIR_OUT,OpC,10,(void *)&ModeData,Size,hDriveEvent); +} + +BOOL SCSIMaxBlocks(HANDLE fh, int *mb) +{ + DWORD ol; + IO_SCSI_CAPABILITIES ca; + + if (DeviceIoControl(fh,IOCTL_SCSI_GET_CAPABILITIES,NULL,0, + &ca,sizeof(IO_SCSI_CAPABILITIES),&ol,NULL)) + { + *mb=(int)ca.MaximumTransferLength; + return TRUE; + } + return FALSE; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/windac/Aspifunc.h b/Src/Plugins/Input/in_cdda/windac/Aspifunc.h new file mode 100644 index 00000000..c63c2f81 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/windac/Aspifunc.h @@ -0,0 +1,101 @@ +// ---------------------------------------------- +// - ASPIFUNC header file - +// - Written 1996-1998 by Christoph Schmelnik - +// ---------------------------------------------- + +// Version 1.40 : 24.02.1998 +// Changes: +// function prototype for WaitSCSIRequest extended by immediate parameter + +#ifndef _ASPIFUNC_H +#define _ASPIFUNC_H + +#ifndef STRICT +#define STRICT // Enable strict tape checking +#define WIN32_LEAN_AND_MEAN // Include only needed header files +#endif +#include <windows.h> +#include <stdio.h> +#include <winioctl.h> + +#include "winaspi.h" +#include "scsidefs.h" + +/*#ifdef DLL +#define DACDLL __declspec(dllexport) +#else +#define DACDLL __declspec(dllimport) +#endif*/ +#define DACDLL + +typedef DWORD (__cdecl *VOIDPROC)(); +typedef DWORD (__cdecl *SRBPROC)(LPSRB); +typedef BYTE TOpcode[30]; + +// NT DeviceIO Structures +#define IOCTL_SCSI_BASE FILE_DEVICE_CONTROLLER +#define IOCTL_SCSI_GET_CAPABILITIES CTL_CODE(IOCTL_SCSI_BASE, 0x0404, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SCSI_PASS_THROUGH_DIRECT CTL_CODE(IOCTL_SCSI_BASE, 0x0405, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) + +typedef struct _IO_SCSI_CAPABILITIES { + ULONG Length; + ULONG MaximumTransferLength; + ULONG MaximumPhysicalPages; + ULONG SupportedAsynchronousEvents; + ULONG AlignmentMask; + BOOLEAN TaggedQueuing; + BOOLEAN AdapterScansDown; + BOOLEAN AdapterUsesPio; +} IO_SCSI_CAPABILITIES, *PIO_SCSI_CAPABILITIES; + +typedef struct _SCSI_PASS_THROUGH_DIRECT { + USHORT Length; + UCHAR ScsiStatus; + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; + UCHAR CdbLength; + UCHAR SenseInfoLength; + UCHAR DataIn; + ULONG DataTransferLength; + ULONG TimeOutValue; + PVOID DataBuffer; + ULONG SenseInfoOffset; + UCHAR Cdb[16]; +} SCSI_PASS_THROUGH_DIRECT, *PSCSI_PASS_THROUGH_DIRECT; + +typedef struct _SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER { + SCSI_PASS_THROUGH_DIRECT sptd; + ULONG Filler; // realign buffer to double word boundary + UCHAR ucSenseBuf[32]; +} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, *PSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER; + + +extern HMODULE hDLL; +extern VOIDPROC GetASPI32SupportInfo; +extern SRBPROC SendASPI32Command; +extern int ASPIInstalled; +extern int RunningNT; +extern int NumberOfHostAdapters; + +// base ASPI functions +int DACDLL GetDeviceType(int HostAdapterNumber,int TargetId,int LUN); +BOOL ExecuteSCSIRequest(int HostAdapterNumber,int TargetId,int LUN,int RequestFlags, + TOpcode OpC, BYTE OpCLen,void *DataPtr, int DataLen,HANDLE hDriveEvent); +void ExecuteSCSIRequest(SRB_ExecSCSICmd &MySRB,HANDLE hDriveEvent); +void FillSCSIRequest(int HostAdapterNumber,int TargetId,int LUN,int RequestFlags, + TOpcode OpC, BYTE OpCLen,void *DataPtr, int DataLen,SRB_ExecSCSICmd &MySRB,HANDLE hDriveEvent); +BYTE WaitSCSIRequest(SRB_ExecSCSICmd &MySRB,HANDLE hDriveEvent,BOOL bImmediate=FALSE); +BOOL AbortSCSIRequest(SRB_ExecSCSICmd &StuckSRB); +int GetDeviceInfo(int HostAdapterNumber,int TargetId,int LUN,BYTE &SCSIType,char *VendorID, + char *ProductID,char *ProductRevision,HANDLE hDriveEvent); +BOOL HAInquiry(int HostAdapterNumber,char *ManagerID, char *HAID,THAUnique &HAUnique); +BOOL TestUnitReady(int HostAdapterNumber,int TargetId,int LUN,HANDLE hDriveEvent); +BOOL ModeSense(int HostAdapterNumber,int TargetId,int LUN,TDriveMode &ModeData,int Size,int PageCode,HANDLE hDriveEvent); +BOOL ATAPIModeSense(int HostAdapterNumber,int TargetId,int LUN,TDriveMode &ModeData,int Size,int PageCode,HANDLE hDriveEvent); +BOOL addModeSense(int HostAdapterNumber,int TargetId,int LUN,TDriveMode &ModeData,int Size,HANDLE hDriveEvent); +BOOL ModeSelect(int HostAdapterNumber,int TargetId,int LUN,TDriveMode &ModeData,int Size,HANDLE hDriveEvent); +BOOL addModeSelect(int HostAdapterNumber,int TargetId,int LUN,TDriveMode &ModeData,int Size,HANDLE hDriveEvent); +BOOL SCSIMaxBlocks(HANDLE fh, int *mb); + +#endif //_ASPIFUNC_H
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/windac/Dac32.cpp b/Src/Plugins/Input/in_cdda/windac/Dac32.cpp new file mode 100644 index 00000000..9172a1dd --- /dev/null +++ b/Src/Plugins/Input/in_cdda/windac/Dac32.cpp @@ -0,0 +1,2667 @@ +// ---------------------------------------------- +// - DAC32.DLL Implementations Datei - +// - Written 1996-1998 by Christoph Schmelnik - +// ---------------------------------------------- + +// Version 1.33 : 18.01.1998 +// Changes: +// Added speed selection support for all current Plextor drives +// +// Version 1.40 : 24.02.1998 +// Changes: +// Set correct direction flags, to work with NT device IO interface and ATAPI drives +// Changed main CD detection to TestUnitReady +// Removed CD detection from Audio Status Info +// Added hopefully correct read command for Matsushita/Panasonic drives +// Added Parameters to CDAC class to allow the disabling of the audio test and to spin up the drive for a specified time +// in seconds to avoid spin up problems on some drives. Both parameters have default values so it behaves like the old version +// without the additional parameters +// Added Parameter to the constructor of CWaveSave to disable writing any Headers. Also this parameter has a default to work like +// before. +// Added virtual function in CDAC to report buffer underruns in Burst Copy Mode +// For the last feature an immediate parameter in WaitCDDA is added +// GetLastSense function added to return the sense information for the last read audio command +// Configuration in CMapDrive extended by new features +// Added function to CD Class to read Media Cataloge Number +// +// Version 1.41 : 02.05.1998 +// Changes: +// New GetInfoEx() function in CMapDrive, to allow a better result checking. +// Bugfixed error handling in CWaveSave and CDAC regarding write errors +// +// Version 1.42 : 02.08.1998 +// Changes: +// Added GetLastReadableAddress function to get the last readable Sektor of a session. +// Added a flag in the drive properties for the function. +// Added this function to the CDAC object. +// +// Version 1.43 : 23.12.1998 +// Changes: +// Added Wave and DAC classes are now available in a MT version, the old versions will not longer be updated. +// +// Version 1.44 : 10.03.1999 +// Changes: +// Added Support for current Plextor CDROM drives and CD-Writers. +// Added Support for Jukeboxes +// Changed Handling of the Ringbuffer +// +// Version 1.45 : 15.08.1999 +// Changes: +// Added Enhanced error detection for Plextor drives. +// Several Bugfixes (initialising under NT and Ringbuffer specific) +// +// Version 1.45-Build 11 : 11.11.1999 +// Changes: +// Added a check for the MaxSektor parameter in CBaseWaveMT to avoid Program failures even if the applications provides an invalid value. +// Changed source to comile with Borland compiler +// Added MMC-2 Type which will be default for Sony CD-Writers 140 and 928 +// Skip Virtual CD devices in Bus scan +// Fixed Array out of bound bug in drive detection. + +//limit the read to 64k, because of a bug in the Adaptec drivers +#define Bug64 + +//limit the number of retries at error to 5 +#define MAXTRIES 5 +#include "dac32.h" +#include <stdlib.h> +#include <assert.h> +#include <process.h> + +#ifdef __BORLANDC__ // pgo (to make it compatible with Bormand compiler) +#define _stricmp _stricmp +#define _strlwr strlwr +#endif + +// ---------------------------------------------------------------------------------------- +// - Implementation of the private copy operators - +// - - +// - Author: Christoph Schmelnik - +// - Purpose: Check the syntax of program - +// ---------------------------------------------------------------------------------------- + +CBaseCD& CBaseCD::operator = (const CBaseCD &other) +{ + assert(!&other); + return (*this); +} + +CSCSICD& CSCSICD::operator = (const CSCSICD &other) +{ + assert(!&other); + return (*this); +} + +// ---------------------------------------------------------------------------------------- +// - Implementation of the class members of CCDAdress - +// - - +// - Author: Christoph Schmelnik - +// - Purpose: Eliminate the errors at compile time - +// ---------------------------------------------------------------------------------------- +void CCDAdress::SetRedbook(long Value) +{ + Adresse=Value >> 24; + Adresse+=((Value >> 16) & 255)*75; + Adresse+=((Value >> 8) & 255)*4500; +}; + +// ---------------------------------------------------------------------------------------- +// - Implementation of the class members of CMapInfo - +// - - +// - Author: Christoph Schmelnik - +// - Purpose: Implement the information of the TypeMappings - +// ---------------------------------------------------------------------------------------- +CMapInfo::CMapInfo() +{ + strncpy(TypNamen[0],"TOSHIBA ", 9); + strncpy(TypNamen[1],"SONY ", 9); + strncpy(TypNamen[2],"NEC ", 9); + strncpy(TypNamen[3],"HITACHI ", 9); + strncpy(TypNamen[4],"YAMAHA ", 9); + strncpy(TypNamen[5],"PIONEER ", 9); + strncpy(TypNamen[6],"IBM ", 9); + strncpy(TypNamen[7],"PLEXTOR ", 9); + strncpy(TypNamen[8],"PHILIPS ", 9); + strncpy(TypNamen[9],"GRUNDIG ", 9); + strncpy(TypNamen[10],"HP ", 9); + strncpy(TypNamen[11],"IMS ", 9); + strncpy(TypNamen[12],"MITSUMI ", 9); + strncpy(TypNamen[13],"ATAPI ", 9); + strncpy(TypNamen[14],"TOSHNEW ", 9); + strncpy(TypNamen[15],"RICOH ", 9); + strncpy(TypNamen[16],"MATSHITA", 9); + strncpy(TypNamen[17],"PLASMON ", 9); + strncpy(TypNamen[18],"KODAK ", 9); + strncpy(TypNamen[19],"TEAC ", 9); + strncpy(TypNamen[20],"CyberDrv", 9); + strncpy(TypNamen[21],"MMC-2 ", 9); // pgo + int const t[]={CDTYPE_TOSHIBA,CDTYPE_SONY,CDTYPE_NEC,CDTYPE_SONY,CDTYPE_SONY,CDTYPE_SONY, + CDTYPE_SONY,CDTYPE_PLEXTOR,CDTYPE_PHILIPS,CDTYPE_PHILIPS,CDTYPE_PHILIPS,CDTYPE_PHILIPS, + CDTYPE_PHILIPS,CDTYPE_ATAPI,CDTYPE_TOSHNEW,CDTYPE_RICOH,CDTYPE_MATSHITA,CDTYPE_PHILIPS, + CDTYPE_PHILIPS,CDTYPE_SONY,CDTYPE_CYBERDRV, + CDTYPE_CYBERDRV}; // pgo + for (int i=0; i<MaxMappings; i++) + TypMapping[i]=t[i]; +} + +char *CMapInfo::GetTypName(int Index) +{ + return TypNamen[Index]; +}; + +int CMapInfo::GetTypMapping(int Index) +{ + return TypMapping[Index]; +}; + +int CMapInfo::GetTypMappingRev(int CDType) +{ + int Index=0; + while ((Index<MaxMappings) && + (TypMapping[Index]!=CDType)) + Index++; + return Index; +}; + +// ---------------------------------------------------------------------------------------- +// - Implementation of the class members of CMapDrive - +// - - +// - Author: Christoph Schmelnik - +// - Purpose: Administration of the drive configuration - +// ---------------------------------------------------------------------------------------- +CMapDrive::CMapDrive(BOOL bDoReset) +{ + First=0; + + m_hDriveEvent=INVALID_HANDLE_VALUE; + wchar_t szEventName[32] = {0}; + wsprintf(szEventName,L"%X",this); + m_hDriveEvent=CreateEvent(NULL,TRUE,FALSE,szEventName); + if (bDoReset) + Reset(); +}; + +CMapDrive::~CMapDrive() +{ + DeleteAll(); + if (m_hDriveEvent!=INVALID_HANDLE_VALUE) + CloseHandle(m_hDriveEvent); +}; + +void CMapDrive::DeleteAll() +{ + TDriveInfoMem *Akt; + Akt=First; + while (First) + { + Akt=First; + First=Akt->Next; + delete Akt; + } +}; + +void CMapDrive::Reset() +{ + TDriveInfoMem *Akt,*Last; + DeleteAll(); + BYTE SCSIType; + TDriveInfo Info = {0}; + int DType = DTYPE_UNKNOWN; + char Revision[5] = {0}; + char ManagerID[17] = {0}; + char HAID[17] = {0}; + + THAUnique HAUnique; + MEMORYSTATUS MemStat; + MemStat.dwLength=sizeof(MEMORYSTATUS); + GlobalMemoryStatus((LPMEMORYSTATUS)&MemStat); + CMapInfo MapInfo; + int HostNum; + for (HostNum=0; HostNum<NumberOfHostAdapters; HostNum++) + { + memset(Revision,0,sizeof(Revision)); + memset(ManagerID,0,sizeof(ManagerID)); + memset(HAID,0,sizeof(HAID)); + memset(&HAUnique,0,sizeof(HAUnique)); + if (HAInquiry(HostNum,ManagerID,HAID,HAUnique)) + { + if (_stricmp(HAID, "fastcdmp") == 0) //pgo: Skip Virtual CD Adapter in Bus Scan + continue; + HostAdapterMemory[HostNum]=HAUnique.MaximumTransferLen; + for (int IDNum=0; IDNum<8; IDNum++) + for (int LUNNum=0; LUNNum<=MAXLUN; LUNNum++) + { + DType=GetDeviceInfo(HostNum,IDNum,LUNNum,SCSIType,Info.VendorID,Info.ProductID,Revision,m_hDriveEvent); + if ((DType==DTYPE_CROM)|| + (DType==DTYPE_WORM)) + { + if (SCSIType) + { + int i; + for (i=0; (i<MaxMappings) && _stricmp(Info.VendorID,MapInfo.GetTypName(i)); i++); + if (i>=MaxMappings) i=1; //pgo: avoid array out of bound + Info.Type=i; + } + else + Info.Type=MapInfo.GetTypMappingRev(CDTYPE_ATAPI); + + if (MapInfo.GetTypMapping(Info.Type)==CDTYPE_TOSHIBA) + { + char szNumbers[17] = {0}; + for (size_t i=0; i<strlen(Info.ProductID); i++) + if (isdigit(Info.ProductID[i])) + strncat(szNumbers,&Info.ProductID[i],1); + int nProductID=atoi(szNumbers); + if (((nProductID>3800) && + (nProductID<4000)) || + ((nProductID>5700) && + (nProductID<10000))) + Info.Type=MapInfo.GetTypMappingRev(CDTYPE_TOSHNEW); + } + // pgo + else if (MapInfo.GetTypMapping(Info.Type)==CDTYPE_SONY) + { + if (SCSIType) + { + char szNumbers[17] = {0}; + for (size_t i=0; i<strlen(Info.ProductID); i++) + if (isdigit(Info.ProductID[i])) + strncat(szNumbers,&Info.ProductID[i],1); + int nProductID=atoi(szNumbers); + if ((nProductID == 140) || (nProductID == 928)) + Info.Type=MapInfo.GetTypMappingRev(CDTYPE_CYBERDRV); + } + } + Info.ID=IDNum; + Info.LUN=LUNNum; + Info.HostAdapterNumber=HostNum; + Info.Mode=ModeBurst; + Info.MaxSektors=MemStat.dwTotalPhys/16/2352; + if (Info.MaxSektors>(HostAdapterMemory[HostNum]/2352)) + Info.MaxSektors=HostAdapterMemory[HostNum]/2352; +#ifdef Bug64 + if (Info.MaxSektors>27) Info.MaxSektors=27; +#else + if (Info.MaxSektors>446) Info.MaxSektors=446; +#endif + if (!Info.MaxSektors) Info.MaxSektors=26; + + Info.SynchSektors=3; + Info.Speed=0; + Info.PerformDATest=DATEST_FIRSTTRACK; + Info.SpinUpMode=SPINUP_NEVER; + Info.dwSpinUpTime=5; + Info.bUseLastReadableAddress=FALSE; + Info.bUseC2ErrorInfo=FALSE; + Info.bSpinDown=FALSE; + Akt=new TDriveInfoMem; + Akt->Info=Info; + Akt->Next=0; + if (!First) First=Akt; + else Last->Next=Akt; + //Last must be set to Akt + Last=Akt; + } + } + } + } + if (RunningNT) + { + //check all drives for direct access + int Index, Length; + wchar_t DriveList[128] = {0}, *pDrives = 0; + + Length=GetLogicalDriveStrings(128,DriveList); + if (Length) + { + pDrives=DriveList; + Index=0; + while (pDrives && *pDrives && (Index<Length)) + { + if (GetDriveType(pDrives)==DRIVE_CDROM) + { + wchar_t CDDevice[10]=L"\\\\.\\x:"; + HANDLE hDrive; + CharLower(pDrives); + //_strlwr(pDrives); + CDDevice[4]=pDrives[0]; + int IDNum=pDrives[0]-'a'; + + //For Windows NT 5 use other file flags + OSVERSIONINFO osver; + memset( &osver, 0x00, sizeof(osver) ); + osver.dwOSVersionInfoSize = sizeof(osver); + GetVersionEx( &osver ); + + DWORD dwOpenFlags = GENERIC_READ; + if ( (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) && (osver.dwMajorVersion > 4) ) dwOpenFlags |= GENERIC_WRITE; + + hDrive=CreateFile(CDDevice,dwOpenFlags,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL); + if (hDrive!=INVALID_HANDLE_VALUE) + { + //CT> added correct host/id/lun guessing + extern int getSCSIIDFromDrive(char driveletter, int *host, int *id, int *lun); + int m_lun; + getSCSIIDFromDrive((char)pDrives[0],&HostNum,&IDNum,&m_lun); + + DType=GetDeviceInfo(HostNum,IDNum,m_lun,SCSIType,Info.VendorID,Info.ProductID,Revision,hDrive); + if ((DType==DTYPE_CROM)|| + (DType==DTYPE_WORM)) + { + SCSIMaxBlocks(hDrive,&HostAdapterMemory[HostNum]); + if (SCSIType) + { + int i; + for (i=0; (i<MaxMappings) && _stricmp(Info.VendorID,MapInfo.GetTypName(i)); i++); + if (i>=MaxMappings) i=1; //pgo: avoid array out of bound + Info.Type=i; + } + else + Info.Type=MapInfo.GetTypMappingRev(CDTYPE_ATAPI); + if (MapInfo.GetTypMapping(Info.Type)==CDTYPE_TOSHIBA) + { + char szNumbers[17]; + szNumbers[0]=0; + for (size_t i=0; i<strlen(Info.ProductID); i++) + if (isdigit(Info.ProductID[i])) + strncat(szNumbers,&Info.ProductID[i],1); + int nProductID=atoi(szNumbers); + if (((nProductID>3800) && + (nProductID<4000)) || + ((nProductID>5700) && + (nProductID<10000))) + Info.Type=MapInfo.GetTypMappingRev(CDTYPE_TOSHNEW); + } + // pgo + else if (MapInfo.GetTypMapping(Info.Type)==CDTYPE_SONY) + { + if (SCSIType) + { + char szNumbers[17] = {0}; + for (size_t i=0; i<strlen(Info.ProductID); i++) + if (isdigit(Info.ProductID[i])) + strncat(szNumbers,&Info.ProductID[i],1); + int nProductID=atoi(szNumbers); + if ((nProductID == 140) || (nProductID == 928)) + Info.Type=MapInfo.GetTypMappingRev(CDTYPE_CYBERDRV); + } + } + Info.ID=IDNum; + Info.LUN=0; + Info.HostAdapterNumber=HostNum; + Info.Mode=ModeNormal; + Info.MaxSektors=MemStat.dwTotalPhys/16/2352; + if (Info.MaxSektors>(HostAdapterMemory[HostNum]/2352)) + Info.MaxSektors=HostAdapterMemory[HostNum]/2352; +#ifdef Bug64 + if (Info.MaxSektors>27) Info.MaxSektors=27; +#else + if (Info.MaxSektors>446) Info.MaxSektors=446; +#endif + if (!Info.MaxSektors || + (MapInfo.GetTypMapping(Info.Type)==CDTYPE_ATAPI)) + Info.MaxSektors=26; + Info.SynchSektors=3; + Info.Speed=0; + Info.PerformDATest=DATEST_FIRSTTRACK; + Info.SpinUpMode=SPINUP_NEVER; + Info.dwSpinUpTime=5; + Info.bUseLastReadableAddress=FALSE; + Info.bUseC2ErrorInfo=FALSE; + Info.bSpinDown=FALSE; + Akt=new TDriveInfoMem; + Akt->Info=Info; + Akt->Next=0; + if (!First) First=Akt; + else Last->Next=Akt; + //Last must be set to Akt + Last=Akt; + HostNum++; + } + CloseHandle(hDrive); + } + } + pDrives+=4; + Index+=4; + } + } + } +}; + +int CMapDrive::GetMaxDrives() +{ + int i=0; + TDriveInfoMem *Akt; + Akt=First; + while (Akt) + { + i++; + Akt=Akt->Next; + } + return i; +}; + +TDriveInfo &CMapDrive::GetInfo(int index) +{ + int i=0; + TDriveInfoMem *Akt; + Akt=First; + while ((Akt) && (i<index)) + { + i++; + Akt=Akt->Next; + } + return Akt->Info; +}; + +BOOL CMapDrive::GetInfoEx(int index, TDriveInfo *&pInfo) +{ + int i=0; + TDriveInfoMem *Akt; + Akt=First; + while ((Akt) && (i<index)) + { + i++; + Akt=Akt->Next; + } + if (!Akt) + return FALSE; + pInfo=&Akt->Info; + return TRUE; +}; + +void CMapDrive::DeleteInfo(int index) +{ + int i=0; + TDriveInfoMem *Akt,*Prev; + Akt=First; + Prev=0; + while ((Akt) && (i<index)) + { + i++; + Prev=Akt; + Akt=Akt->Next; + } + if (!Akt) return; + if (Prev) Prev->Next=Akt->Next; + else First=Akt->Next; + delete Akt; +}; + +int CMapDrive::GetMaxHostAdapters() +{ + return NumberOfHostAdapters; +}; + +int CMapDrive::GetSupportedHostAdapterMemory(int index) +{ + if ((index<NumberOfHostAdapters) && + (index>=0)) + return HostAdapterMemory[index]; + else + return -1; +}; + +void CMapDrive::SetSupportedHostAdapterMemory(int index,int Memory) +{ + if ((index<NumberOfHostAdapters) && + (index>=0)) + HostAdapterMemory[index]=Memory; +} + +int CMapDrive::GetMaxSektors(int HostAdapterNumber) +{ +#ifdef Bug64 + int Result=HostAdapterMemory[HostAdapterNumber]/2352; + if (Result>27) + Result=27; + return Result; +#else + return HostAdapterMemory[HostAdapterNumber]/2352; +#endif +}; + +// ---------------------------------------------------------------------------------------- +// - Implementation of the class members of CMapInfoJuke - +// - - +// - Author: Christoph Schmelnik - +// - Purpose: Implement the information of the TypeMappings - +// ---------------------------------------------------------------------------------------- +CMapInfoJuke::CMapInfoJuke() +{ + strncpy(TypNamen[0],"SONY ", 9); + strncpy(TypNamen[1],"PIONEER ", 9); + int const t[]={JUKETYPE_SONY,JUKETYPE_PIONEER}; + for (int i=0; i<MaxMappingsJuke; i++) + TypMapping[i]=t[i]; +} + +char *CMapInfoJuke::GetTypName(int Index) +{ + return TypNamen[Index]; +}; + +int CMapInfoJuke::GetTypMapping(int Index) +{ + return TypMapping[Index]; +}; + +int CMapInfoJuke::GetTypMappingRev(int JukeType) +{ + int Index=0; + while ((Index<MaxMappingsJuke) && + (TypMapping[Index]!=JukeType)) + Index++; + return Index; +}; + +// ---------------------------------------------------------------------------------------- +// - Implementation of the class members of CMapJuke - +// - - +// - Author: Christoph Schmelnik - +// - Purpose: Administration of the jukebox configuration - +// ---------------------------------------------------------------------------------------- +CMapJuke::CMapJuke(BOOL bDoReset) +{ + First=0; + + m_hJukeEvent=INVALID_HANDLE_VALUE; + wchar_t szEventName[32] = {0}; + wsprintf(szEventName,L"%X",this); + m_hJukeEvent=CreateEvent(NULL,TRUE,FALSE,szEventName); + if (bDoReset) + Reset(); +}; + +CMapJuke::~CMapJuke() +{ + DeleteAll(); + if (m_hJukeEvent!=INVALID_HANDLE_VALUE) + CloseHandle(m_hJukeEvent); +}; + +void CMapJuke::DeleteAll() +{ + TJukeInfoMem *Akt; + Akt=First; + while (First) + { + Akt=First; + First=Akt->Next; + if (Akt->Info.pConnectedDrives) + delete Akt->Info.pConnectedDrives; + delete Akt; + } +}; + +void CMapJuke::Reset() +{ + TJukeInfoMem *Akt,*Last; + DeleteAll(); + BYTE SCSIType = 0; + TJukeInfo Info = {0}; + int DType = DTYPE_UNKNOWN; + char Revision[5] = {0}; + char ManagerID[17] = {0}; + char HAID[17] = {0}; + + THAUnique HAUnique; + CMapInfoJuke MapInfo; + for (int HostNum=0; HostNum<NumberOfHostAdapters; HostNum++) + { + memset(Revision,0,sizeof(Revision)); + memset(ManagerID,0,sizeof(ManagerID)); + memset(HAID,0,sizeof(HAID)); + memset(&HAUnique,0,sizeof(HAUnique)); + if (HAInquiry(HostNum,ManagerID,HAID,HAUnique)) + { + for (int IDNum=0; IDNum<8; IDNum++) + { + for (int LUNNum=0; LUNNum<=MAXLUN; LUNNum++) + { + memset(&Info,0,sizeof(Info)); + DType=GetDeviceInfo(HostNum,IDNum,LUNNum,SCSIType,Info.VendorID,Info.ProductID,Revision,m_hJukeEvent); + if (DType==DTYPE_JUKE) + { + int i; + for (i=0; (i<MaxMappingsJuke) && _stricmp(Info.VendorID,MapInfo.GetTypName(i)); i++); + if (_stricmp(Info.VendorID,MapInfo.GetTypName(i))) i=JUKETYPE_SONY; + Info.Type=i; + switch (Info.Type) + { + case JUKETYPE_PIONEER : + Info.MaxDrives=4; //currently only data for the 500x changer implemeneted + Info.MaxDiscs=500; + break; + case JUKETYPE_SONY : + default: + Info.MaxDrives=2; + Info.MaxDiscs=100; + break; + } + Info.pConnectedDrives=new int[Info.MaxDrives]; + for (int nDriveNum=0; nDriveNum<Info.MaxDrives; nDriveNum++) + Info.pConnectedDrives[nDriveNum]=-1; + Info.ID=IDNum; + Info.LUN=LUNNum; + Info.HostAdapterNumber=HostNum; + + Akt=new TJukeInfoMem; + Akt->Info=Info; + Akt->bIsWorking=FALSE; + Akt->Next=0; + if (!First) First=Akt; + else Last->Next=Akt; + //Last must be set to Akt + Last=Akt; + } + } + } + } + } +}; + +int CMapJuke::GetMaxJukes() +{ + int i=0; + TJukeInfoMem *Akt; + Akt=First; + while (Akt) + { + i++; + Akt=Akt->Next; + } + return i; +}; + +TJukeInfo &CMapJuke::GetInfo(int index) +{ + int i=0; + TJukeInfoMem *Akt; + Akt=First; + while ((Akt) && (i<index)) + { + i++; + Akt=Akt->Next; + } + return Akt->Info; +}; + +BOOL CMapJuke::IsWorking(int index) +{ + int i=0; + TJukeInfoMem *Akt; + Akt=First; + while ((Akt) && (i<index)) + { + i++; + Akt=Akt->Next; + } + return Akt->bIsWorking; +}; + +void CMapJuke::SetWorking(int index,BOOL bIsWorking) +{ + int i=0; + TJukeInfoMem *Akt; + Akt=First; + while ((Akt) && (i<index)) + { + i++; + Akt=Akt->Next; + } + Akt->bIsWorking=bIsWorking; +}; + +void CMapJuke::DeleteInfo(int index) +{ + int i=0; + TJukeInfoMem *Akt,*Prev; + Akt=First; + Prev=0; + while ((Akt) && (i<index)) + { + i++; + Prev=Akt; + Akt=Akt->Next; + } + if (!Akt) return; + if (Prev) Prev->Next=Akt->Next; + else First=Akt->Next; + if (Akt->Info.pConnectedDrives) + delete Akt->Info.pConnectedDrives; + delete Akt; +}; + +// ---------------------------------------------------------------------------------------- +// - Implementation of the class members of CJukeBox - +// - - +// - Author: Christoph Schmelnik - +// - Purpose: Control the basic JukeBox functions over ASPI - +// ---------------------------------------------------------------------------------------- +CJukeBox::CJukeBox (TJukeInfo &xInfo):Config(xInfo) +{ + m_hJukeEvent=INVALID_HANDLE_VALUE; + wchar_t szEventName[32] = {0}; + wsprintf(szEventName,L"%X",this); + m_hJukeEvent=CreateEvent(NULL,TRUE,FALSE,szEventName); + assert(m_hJukeEvent); +} + +CJukeBox::~CJukeBox() +{ + if (m_hJukeEvent!=INVALID_HANDLE_VALUE) + CloseHandle(m_hJukeEvent); +} + +BOOL CJukeBox::MoveMedium(int Source,int Destination) +{ + switch (MapInfo.GetTypMapping(Config.Type)) + { + case JUKETYPE_PIONEER : + if ((Source<1) || (Source>Config.MaxDiscs)) + if ((Source<0x4000) || (Source>=(0x4000+Config.MaxDrives))) + return FALSE; + if ((Destination<1) || (Destination>Config.MaxDiscs)) + if ((Destination<0x4000) || (Destination>=(0x4000+Config.MaxDrives))) + return FALSE; + break; + case JUKETYPE_SONY : + default: + if ((Source<1) || (Source>Config.MaxDiscs)) + { + if ((Source<0x4000) || (Source>=(0x4000+Config.MaxDrives))) + return FALSE; + else + Source-=0x3fff; + } + else + Source+=10; + if ((Destination<1) || (Destination>Config.MaxDiscs)) + { + if ((Destination<0x4000) || (Destination>=(0x4000+Config.MaxDrives))) + return FALSE; + else + Destination-=0x3fff; + } + else + Destination+=10; + break; + } + + while (!TestUnitReady(Config.HostAdapterNumber,Config.ID,Config.LUN,m_hJukeEvent)); + TOpcode OpC; + OpC[0]=0xa5; + OpC[1]=Config.LUN>>5; + OpC[2]=0x00; + OpC[3]=0x00; + OpC[4]=Source/256; + OpC[5]=Source%256; + OpC[6]=Destination/256; + OpC[7]=Destination%256; + OpC[8]=0x00; + OpC[9]=0x00; + OpC[10]=0x00; + OpC[11]=0x00; + return ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,0,OpC,12,NULL,0,m_hJukeEvent); +} + +// ---------------------------------------------------------------------------------------- +// - Implementation of the class members of CBaseCD - +// - - +// - Author: Christoph Schmelnik - +// - Purpose: The drive independent functions to access the CD-ROM drives - +// ---------------------------------------------------------------------------------------- +int CBaseCD::Lasterror() +{ + int h; + h=Error; + Error=CDOK; + return h; +} + +int CBaseCD::ReadFirstTrackInfo(TTrackList &Infos) +{ + if (!FirstTrack) return 0; + AktTrack=FirstTrack; + Infos=AktTrack->Info; + return 1; +} + +int CBaseCD::ReadNextTrackInfo(TTrackList &Infos) +{ + if ((!FirstTrack) || + (!AktTrack->Next)) return 0; + AktTrack=AktTrack->Next; + Infos=AktTrack->Info; + return 1; +} + +int CBaseCD::ReadPrevTrackInfo(TTrackList &Infos) +{ + if ((!FirstTrack) || + (!AktTrack->Prev)) return 0; + AktTrack=AktTrack->Prev; + Infos=AktTrack->Info; + return 1; +} + +int CBaseCD::ReadTrackInfo(TTrackList &Infos) +{ + if ((!FirstTrack) || + (Infos.TrackNummer<1)) return 0; + if (!AktTrack) + AktTrack=FirstTrack; + if (AktTrack->Info.TrackNummer==Infos.TrackNummer) + { + Infos=AktTrack->Info; + return 1; + } + while ((AktTrack->Info.TrackNummer>Infos.TrackNummer) && + (AktTrack->Prev)) + AktTrack=AktTrack->Prev; + while ((AktTrack->Info.TrackNummer<Infos.TrackNummer) && + (AktTrack->Next)) + AktTrack=AktTrack->Next; + if (AktTrack->Info.TrackNummer!=Infos.TrackNummer) return 0; + Infos=AktTrack->Info; + return 1; +} + +int CBaseCD::ReadMaxTracks() +{ + TTrackListeMem *Laeufer; + if (!FirstTrack) return 0; + Laeufer=AktTrack; + while (Laeufer->Next) + Laeufer=Laeufer->Next; + return Laeufer->Info.TrackNummer; +} + +void CBaseCD::DeleteTrackList() +{ + while (FirstTrack) + { + AktTrack=FirstTrack->Next; + delete FirstTrack; + FirstTrack=AktTrack; + } +} + + +// ---------------------------------------------------------------------------------------- +// - Implementation of the class members of CSCSICD - +// - - +// - Author: Christoph Schmelnik - +// - Purpose: Control the basic CDROM functions over ASPI - +// ---------------------------------------------------------------------------------------- +CSCSICD::CSCSICD (char drive, TDriveInfo &xInfo):Config(xInfo) +{ + m_bSpeedTableInitialized=FALSE; + FirstTrack=0; + NECRotationSpeed = 0; // pgo + Changed=FALSE; + CDPresentLast=TRUE; + Error=CDOK; + m_hDriveEvent=INVALID_HANDLE_VALUE; + memset(&m_SenseInfo,0,sizeof(TSenseInfo)); + if ((Config.HostAdapterNumber>=NumberOfHostAdapters) && + RunningNT) + { + DWORD dwFlags; + OSVERSIONINFO osver; + wchar_t CDDevice[10]=L"\\\\.\\x:"; + CDDevice[4]=(wchar_t)drive;//(Config.HostAdapterNumber + 'A'/* + 1*/); + //For Windows NT 5 use other file flags + memset( &osver, 0x00, sizeof(osver) ); + osver.dwOSVersionInfoSize = sizeof(osver); + GetVersionEx( &osver ); + + // if Win2K or greater, add GENERIC_WRITE + dwFlags = GENERIC_READ; + if ( (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) && (osver.dwMajorVersion > 4) ) dwFlags |= GENERIC_WRITE; + + m_hDriveEvent = CreateFile( CDDevice, dwFlags, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); + } + else + { + wchar_t szEventName[32] = {0}; + wsprintf(szEventName,L"%X",this); + m_hDriveEvent=CreateEvent(NULL,TRUE,FALSE,szEventName); + assert(m_hDriveEvent); + } + TDriveStatus DInfo=Get_DriveStatus(); + if (DInfo.CDPresent) ReRead(); +} + +CSCSICD::~CSCSICD() +{ + DeleteTrackList(); + if (m_hDriveEvent!=INVALID_HANDLE_VALUE) + CloseHandle(m_hDriveEvent); +} + +void CSCSICD::PrepareCDDA() +{ + switch (MapInfo.GetTypMapping(Config.Type)) + { + case CDTYPE_TOSHIBA : + { + memset(&ModeData,0,sizeof(ModeData)); + if (!ModeSense(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeData,12,0,m_hDriveEvent)) + { + Error=CDASPIError; + return; + } + ModeData[0]=0; + TDriveMode ModeSelectData; + ModeSelectData[0]=0x00; + ModeSelectData[1]=0x00; + ModeSelectData[2]=0x00; + ModeSelectData[3]=0x08; + ModeSelectData[4]=0x82; + ModeSelectData[5]=0x00; + ModeSelectData[6]=0x00; + ModeSelectData[7]=0x00; + ModeSelectData[8]=0x00; + ModeSelectData[9]=0x00; + ModeSelectData[10]=0x09; + ModeSelectData[11]=0x30; + if (!ModeSelect(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSelectData,12,m_hDriveEvent)) + { + Error=CDASPIError; + return; + } + break; + } + case CDTYPE_TOSHNEW : + { + memset(&ModeData,0,sizeof(ModeData)); + if (!ModeSense(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeData,15,0x20,m_hDriveEvent)) + { + Error=CDASPIError; + return; + } + ModeData[0]=0; + TDriveMode ModeSelectData; + ModeSelectData[0]=0x00; + ModeSelectData[1]=0x00; + ModeSelectData[2]=0x00; + ModeSelectData[3]=0x08; + ModeSelectData[4]=0x82; + ModeSelectData[5]=0x00; + ModeSelectData[6]=0x00; + ModeSelectData[7]=0x00; + ModeSelectData[8]=0x00; + ModeSelectData[9]=0x00; + ModeSelectData[10]=0x09; + ModeSelectData[11]=0x30; + + ModeSelectData[12]=0x20; + ModeSelectData[13]=0x01; + ModeSelectData[14]=(ModeData[14] & 0xcf)|0x10; + if (!ModeSelect(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSelectData,15,m_hDriveEvent)) + { + Error=CDASPIError; + return; + } + break; + } + case CDTYPE_NEC : + { + memset(&ModeData,0,sizeof(ModeData)); + if (!addModeSense(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeData,12,m_hDriveEvent)) + { + Error=CDASPIError; + return; + } + NECRotationSpeed=ModeData[6] & 0x20; + ModeData[6]=ModeData[6]|0x20; + if (!addModeSelect(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeData,12,m_hDriveEvent)) + { + Error=CDASPIError; + return; + } + break; + } + case CDTYPE_PHILIPS : + case CDTYPE_MATSHITA : + { + memset(&ModeData,0,sizeof(ModeData)); + if (!ModeSense(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeData,12,0,m_hDriveEvent)) + { + Error=CDASPIError; + return; + } + ModeData[0]=0; + TDriveMode ModeSelectData; + ModeSelectData[0]=0x00; + ModeSelectData[1]=0x00; + ModeSelectData[2]=0x00; + ModeSelectData[3]=0x08; + ModeSelectData[4]=0x00; + ModeSelectData[5]=0x00; + ModeSelectData[6]=0x00; + ModeSelectData[7]=0x00; + ModeSelectData[8]=0x00; + ModeSelectData[9]=0x00; + ModeSelectData[10]=0x09; + ModeSelectData[11]=0x30; + if (!ModeSelect(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSelectData,12,m_hDriveEvent)) + { + Error=CDASPIError; + return; + } + break; + } + } +} + +void CSCSICD::ReadCDDA(CCDAdress StartSektor,long Sektoranzahl,void *Buffer,BOOL bUseC2ErrorInfo) +{ + TOpcode OpC; + OpC[1]=0x00; + OpC[2]=0x00; + OpC[3]=LOBYTE(HIWORD(StartSektor.GetHSG())); + OpC[4]=HIBYTE(LOWORD(StartSektor.GetHSG())); + OpC[5]=LOBYTE(LOWORD(StartSektor.GetHSG())); + OpC[6]=0x00; + switch (MapInfo.GetTypMapping(Config.Type)) + { + case CDTYPE_TOSHIBA : + case CDTYPE_TOSHNEW : + { + OpC[0]=0x28; + OpC[7]=HIBYTE(LOWORD(Sektoranzahl)); + OpC[8]=LOBYTE(LOWORD(Sektoranzahl)); + OpC[9]=0x00; + FillSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,SRB_DIR_IN,OpC,10,Buffer,Sektoranzahl*2352,ReadSRB,m_hDriveEvent); + break; + } + case CDTYPE_SONY : + case CDTYPE_RICOH : + case CDTYPE_PLEXTOR : + { + OpC[0]=0xD8; + OpC[7]=0x00; + OpC[8]=HIBYTE(LOWORD(Sektoranzahl)); + OpC[9]=LOBYTE(LOWORD(Sektoranzahl)); + // benski + if (bUseC2ErrorInfo) + OpC[10]=0x04; + else + OpC[10]=0x00; + OpC[11]=0x00; + FillSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,SRB_DIR_IN,OpC,12,Buffer,(bUseC2ErrorInfo)?(Sektoranzahl*2646):(Sektoranzahl*2352),ReadSRB,m_hDriveEvent); + break; + } + case CDTYPE_NEC : + { + OpC[0]=0xD4; + OpC[7]=HIBYTE(LOWORD(Sektoranzahl)); + OpC[8]=LOBYTE(LOWORD(Sektoranzahl)); + OpC[9]=0x00; + FillSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,SRB_DIR_IN,OpC,10,Buffer,Sektoranzahl*2352,ReadSRB,m_hDriveEvent); + break; + } + case CDTYPE_MATSHITA : + { + OpC[0]=0xD4; + OpC[7]=0x00; + OpC[8]=HIBYTE(LOWORD(Sektoranzahl)); + OpC[9]=LOBYTE(LOWORD(Sektoranzahl)); + OpC[10]=0x00; + OpC[11]=0x00; + FillSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,SRB_DIR_IN,OpC,12,Buffer,Sektoranzahl*2352,ReadSRB,m_hDriveEvent); + break; + } + case CDTYPE_PHILIPS : + { + OpC[0]=0x28; + OpC[7]=HIBYTE(LOWORD(Sektoranzahl)); + OpC[8]=LOBYTE(LOWORD(Sektoranzahl)); + OpC[9]=0x00; + FillSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,SRB_DIR_IN,OpC,10,Buffer,Sektoranzahl*2352,ReadSRB,m_hDriveEvent); + break; + } + case CDTYPE_ATAPI : + case CDTYPE_CYBERDRV : + { + OpC[0]=0xBE; + OpC[1]=0x04; + OpC[7]=HIBYTE(LOWORD(Sektoranzahl)); + OpC[8]=LOBYTE(LOWORD(Sektoranzahl)); + OpC[9]=0xF0; + // benski + if (bUseC2ErrorInfo) + OpC[9]|=2; // flag 2 is supposed to mean check for C2 error info + OpC[10]=0x00; + OpC[11]=0x00; + // with C2 error info, our sector size is now 2646 bytes + FillSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,SRB_DIR_IN,OpC,12,Buffer,bUseC2ErrorInfo?(Sektoranzahl*2646):(Sektoranzahl*2352),ReadSRB,m_hDriveEvent); + break; + } + } + ExecuteSCSIRequest(ReadSRB,m_hDriveEvent); + StartReadTime=GetTickCount(); +} + +BOOL CSCSICD::WaitCDDA(BOOL bImmediate) +{ + BYTE Status=WaitSCSIRequest(ReadSRB,m_hDriveEvent,bImmediate); + if ((Status!=SS_PENDING) && + (Status!=SS_COMP)) + { + memcpy(&m_SenseInfo,&ReadSRB.SenseArea,SENSE_LEN); + Error=CDASPIError; + } + if ((Status==SS_PENDING) && + !bImmediate) + { + DWORD AktReadTime=GetTickCount(); + if (abs((long long)AktReadTime-StartReadTime)>12000) + { + AbortSCSIRequest(ReadSRB); + Error=CDTimeOut; + Status=SS_COMP; + } + } + return (Status!=SS_PENDING); +} + +void CSCSICD::FinishCDDA() +{ + switch (MapInfo.GetTypMapping(Config.Type)) + { + case CDTYPE_TOSHIBA : + { + if (!ModeSelect(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeData,12,m_hDriveEvent)) + { + Error=CDASPIError; + return; + } + break; + } + case CDTYPE_TOSHNEW : + { + if (!ModeSelect(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeData,15,m_hDriveEvent)) + { + Error=CDASPIError; + return; + } + break; + } + case CDTYPE_NEC : + { + ModeData[6]=ModeData[6]|NECRotationSpeed; + if (!addModeSelect(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeData,12,m_hDriveEvent)) + { + Error=CDASPIError; + return; + } + break; + } + case CDTYPE_PHILIPS : + case CDTYPE_MATSHITA : + { + if (!ModeSelect(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeData,12,m_hDriveEvent)) + { + Error=CDASPIError; + return; + } + break; + } + } +} + +void CSCSICD::SortWaveData(DWORD *Data,int Samples) +{ + switch (MapInfo.GetTypMapping(Config.Type)) + { + case CDTYPE_PHILIPS : +// RICOH Drives doesn't seem to swap the bytes +// This has been evaluated with the CD-RW drives +// So they are even just handled like SONY drives, but i don't remove this type +// case CDTYPE_RICOH : + { + for (int i=0; i<Samples; i++) + Data[i]=((Data[i]&0xff00ff00)>>8)| + ((Data[i]&0x00ff00ff)<<8); + break; + } + } +} + +CCDAdress CSCSICD::GetErrorAdress() +{ + CCDAdress h; + h.SetHSG(0); + if ((Error!=CDOK)&& + (ReadSRB.SRB_TargStat==STATUS_CHKCOND)) + h.SetHSG((ReadSRB.SenseArea[3]<<24)+ + (ReadSRB.SenseArea[4]<<16)+ + (ReadSRB.SenseArea[5]<<8)+ + ReadSRB.SenseArea[6]); + return h; +} + +void CSCSICD::Play_Audio(CCDAdress StartSektor,long Sektoranzahl) +{ + while (!TestUnitReady(Config.HostAdapterNumber,Config.ID,Config.LUN,m_hDriveEvent)); + TOpcode OpC; + OpC[0]=0x45; + OpC[1]=0x00; + OpC[2]=HIBYTE(HIWORD(StartSektor.GetHSG())); + OpC[3]=LOBYTE(HIWORD(StartSektor.GetHSG())); + OpC[4]=HIBYTE(LOWORD(StartSektor.GetHSG())); + OpC[5]=LOBYTE(LOWORD(StartSektor.GetHSG())); + OpC[6]=0x00; + OpC[7]=HIBYTE(LOWORD(Sektoranzahl)); + OpC[8]=LOBYTE(LOWORD(Sektoranzahl)); + OpC[9]=0x00; + if (!ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,0,OpC,10,NULL,0,m_hDriveEvent)) + Error=CDDriveNotReady; +} + +void CSCSICD::Stop_Audio() +{ + while (!TestUnitReady(Config.HostAdapterNumber,Config.ID,Config.LUN,m_hDriveEvent)); + TOpcode OpC; +/* OpC[0]=0x1b; + OpC[1]=0x00; + OpC[2]=0x00; + OpC[3]=0x00; + OpC[4]=0x00; + OpC[5]=0x00; +*/ + OpC[0]=0x2b; + OpC[1]=0x00; + OpC[2]=0x00; + OpC[3]=0x00; + OpC[4]=0x00; + OpC[5]=0x00; + OpC[6]=0x00; + OpC[7]=0x00; + OpC[8]=0x00; + OpC[9]=0x00; + if (!ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,0,OpC,10,NULL,0,m_hDriveEvent)) + Error=CDDriveNotReady; +} + +void CSCSICD::Pause_Audio() +{ + while (!TestUnitReady(Config.HostAdapterNumber,Config.ID,Config.LUN,m_hDriveEvent)); + TOpcode OpC; + OpC[0]=0x4b; + OpC[1]=0x00; + OpC[2]=0x00; + OpC[3]=0x00; + OpC[4]=0x00; + OpC[5]=0x00; + OpC[6]=0x00; + OpC[7]=0x00; + OpC[8]=0x00; + OpC[9]=0x00; + if (!ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,0,OpC,10,NULL,0,m_hDriveEvent)) + Error=CDDriveNotReady; +} + +void CSCSICD::Resume_Audio() +{ + while (!TestUnitReady(Config.HostAdapterNumber,Config.ID,Config.LUN,m_hDriveEvent)); + TOpcode OpC; + OpC[0]=0x4b; + OpC[1]=0x00; + OpC[2]=0x00; + OpC[3]=0x00; + OpC[4]=0x00; + OpC[5]=0x00; + OpC[6]=0x00; + OpC[7]=0x00; + OpC[8]=0x01; + OpC[9]=0x00; + if (!ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,0,OpC,10,NULL,0,m_hDriveEvent)) + Error=CDDriveNotReady; +} + +TDriveStatus CSCSICD::Get_DriveStatus() +{ + TDriveStatus h = {0}; +/* TOpcode OpC; + TQChannelInfo ChannelInfo; + BOOL b; + memset(&ChannelInfo,0,sizeof(ChannelInfo)); + OpC[0]=0x42; + OpC[1]=0x02; + OpC[2]=0x40; + OpC[3]=0x01; + OpC[4]=0x00; + OpC[5]=0x00; + OpC[6]=0x00; + OpC[7]=0x00; + OpC[8]=sizeof(ChannelInfo); + OpC[9]=0x00; + if (MapInfo.GetTypMapping(Config.Type)==CDTYPE_ATAPI) + { + OpC[10]=0; + OpC[11]=0; + b=ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,0,OpC,12,(void *)&ChannelInfo,sizeof(ChannelInfo),m_hDriveEvent); + } + else + b=ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,0,OpC,10,(void *)&ChannelInfo,sizeof(ChannelInfo),m_hDriveEvent); + if (b && ChannelInfo.DataLen) +*/ + if (TestUnitReady(Config.HostAdapterNumber,Config.ID,Config.LUN,m_hDriveEvent)) + h.CDPresent=TRUE; + else + h.CDPresent=FALSE; + if (h.CDPresent!=CDPresentLast) Changed=TRUE; + CDPresentLast=h.CDPresent; + return h; +} + +BOOL CSCSICD::MediaChanged() +{ + BOOL h; + h=Changed; + //if (CDPresentLast) + Changed=FALSE; + return h; +} + +TAudioStatus CSCSICD::Get_AudioStatus_Info() +{ + TAudioStatus h; + TOpcode OpC; + TQChannelInfo ChannelInfo; + BOOL b; + memset(&ChannelInfo,0,sizeof(ChannelInfo)); + OpC[0]=0x42; + OpC[1]=0x02; + OpC[2]=0x40; + OpC[3]=0x01; + OpC[4]=0x00; + OpC[5]=0x00; + OpC[6]=0x00; + OpC[7]=0x00; + OpC[8]=0x10; + OpC[9]=0x00; + h.Pause=FALSE; + h.IsPlaying=FALSE; + h.IsDone=FALSE; + h.PlayError=FALSE; + if (MapInfo.GetTypMapping(Config.Type)==CDTYPE_ATAPI) + { + OpC[10]=0; + OpC[11]=0; + b=ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,SRB_DIR_IN,OpC,12,(void *)&ChannelInfo,16,m_hDriveEvent); + } + else + b=ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,SRB_DIR_IN,OpC,10,(void *)&ChannelInfo,16,m_hDriveEvent); + if (b && ChannelInfo.DataLen) + { +// if (!CDPresentLast) Changed=TRUE; +// CDPresentLast=TRUE; + switch (ChannelInfo.AudioStatus) + { + case 0x11 : h.IsPlaying=TRUE; break; + case 0x12 : h.Pause=TRUE; break; + case 0x13 : h.IsDone=TRUE; break; + case 0x14 : h.PlayError=TRUE; break; + } + h.AbsSektor.SetRedbook(ChannelInfo.AbsCDAdress); + h.RelSektor.SetRedbook(ChannelInfo.RelTrackAdress); + h.TrackNummer=ChannelInfo.TrackNumber; + } + else + { +// if (CDPresentLast) Changed=TRUE; +// CDPresentLast=FALSE; + h.PlayError=TRUE; + Error=CDNoCD; + } + return h; +} + +void CSCSICD::Get_MediaCatalogNumber(char szUPC[16]) +{ + TOpcode OpC; + BYTE Info[24] = {0}; + BOOL b; + szUPC[0]=0; + OpC[0]=0x42; + OpC[1]=0x02; + OpC[2]=0x40; + OpC[3]=0x02; + OpC[4]=0x00; + OpC[5]=0x00; + OpC[6]=0x00; + OpC[7]=0x00; + OpC[8]=sizeof(Info); + OpC[9]=0x00; + if (MapInfo.GetTypMapping(Config.Type)==CDTYPE_ATAPI) + { + OpC[10]=0; + OpC[11]=0; + b=ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,SRB_DIR_IN,OpC,12,(void *)&Info,sizeof(Info),m_hDriveEvent); + } + else + b=ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,SRB_DIR_IN,OpC,10,(void *)&Info,sizeof(Info),m_hDriveEvent); + if (b && (Info[8]&0x80)) + { + BOOL bIsEmpty=TRUE; + for (int i=0; i<15; i++) + { + BYTE Value=Info[i+9]; + if (Value) + bIsEmpty=FALSE; + if (Value<10) + Value+=0x30; + szUPC[i]=Value; + } + szUPC[15]=0; + if (bIsEmpty) + szUPC[0]=0; + } +} + +void CSCSICD::EjectDisk() +{ + TOpcode OpC; + OpC[0]=0x1b; + OpC[1]=0x00; + OpC[2]=0x00; + OpC[3]=0x00; + OpC[4]=0x02; + OpC[5]=0x00; + if (!ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,0,OpC,6,NULL,0,m_hDriveEvent)) + Error=CDDriveNotReady; +} + +void CSCSICD::LockDoor(int Lock) +{ + TOpcode OpC; + OpC[0]=0x1e; + OpC[1]=0x00; + OpC[2]=0x00; + OpC[3]=0x00; + OpC[4]=((BYTE) Lock) & 1; + OpC[5]=0x00; + if (!ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,0,OpC,6,NULL,0,m_hDriveEvent)) + Error=CDDriveNotReady; +} + +void CSCSICD::CloseTray() +{ + TOpcode OpC; + OpC[0]=0x1b; + OpC[1]=0x00; + OpC[2]=0x00; + OpC[3]=0x00; + OpC[4]=0x03; + OpC[5]=0x00; + if (!ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,0,OpC,6,NULL,0,m_hDriveEvent)) + Error=CDDriveNotReady; +} + +int Swap(int value) +{ + int result=(value & 0xff000000)>>24; + result|=(value & 0x00ff0000)>>8; + result|=(value & 0x0000ff00)<<8; + result|=(value & 0x000000ff)<<24; + return result; +} + +void CSCSICD::ReRead() +{ + DeleteTrackList(); + m_bSpeedTableInitialized=FALSE; + TTOCHeader TOCHeader; + memset(&TOCHeader,0,sizeof(TOCHeader)); + TOpcode OpC; + OpC[0]=0x43; + OpC[1]=0; + OpC[2]=0; + OpC[3]=0; + OpC[4]=0; + OpC[5]=0; + OpC[6]=0; + OpC[7]=HIBYTE(sizeof(TTOCHeader)); + OpC[8]=LOBYTE(sizeof(TTOCHeader)); + OpC[9]=0; + + BOOL r=ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,SRB_DIR_IN,OpC,10,(void*)&TOCHeader,sizeof(TOCHeader),m_hDriveEvent); + if (r && TOCHeader.FirstTrack && TOCHeader.LastTrack) + { + TTrackListeMem *Last; + CCDAdress Ende; + Last=FirstTrack; + for (int i=TOCHeader.FirstTrack; i<=TOCHeader.LastTrack; i++) + { + AktTrack=new TTrackListeMem; + //AbsCDAdress + //AdrCtrl + AktTrack->Info.TrackNummer=TOCHeader.Info[i-1].TrackNummer; + AktTrack->Info.StartSektor.SetHSG(Swap(TOCHeader.Info[i-1].AbsCDAdress)+150); + Ende.SetHSG(Swap(TOCHeader.Info[i].AbsCDAdress)+150); + AktTrack->Info.Laenge=Ende.GetHSG()-AktTrack->Info.StartSektor.GetHSG(); + AktTrack->Info.StartSektor.SetHSG(AktTrack->Info.StartSektor.GetHSG()-150); + if (TOCHeader.Info[i-1].AdrCtrl & 8) + AktTrack->Info.Flags.AudioChannels=4; + else + AktTrack->Info.Flags.AudioChannels=2; + AktTrack->Info.Flags.PreEmphasis=(TOCHeader.Info[i-1].AdrCtrl & 1); + AktTrack->Info.Flags.DataTrack=(TOCHeader.Info[i-1].AdrCtrl & 4); + AktTrack->Info.Flags.CopyProhibeted=!(TOCHeader.Info[i-1].AdrCtrl & 2); + AktTrack->Prev=Last; + AktTrack->Next=0; + if (FirstTrack) + Last->Next=AktTrack; + else + FirstTrack=AktTrack; + Last=AktTrack; + } + // check for CD-Extra + if (AktTrack) + { + if (AktTrack->Info.Flags.DataTrack) + { + Last=AktTrack->Prev; + if (Last && !Last->Info.Flags.DataTrack) + { + if (Last->Info.Laenge>11400) + Last->Info.Laenge-=11400; + } + } + } + } + else + { + if (CDPresentLast) Changed=TRUE; + CDPresentLast=FALSE; + Error=CDDriveNotReady; + } +} + +CCDAdress CSCSICD::GetLastReadableAddress(CCDAdress StartSektor) +{ + CCDAdress LastSektor; + BYTE RetVal[8] = {0}; + TOpcode OpC; + OpC[0]=0x25; + OpC[1]=0; + OpC[2]=HIBYTE(HIWORD(StartSektor.GetHSG())); + OpC[3]=LOBYTE(HIWORD(StartSektor.GetHSG())); + OpC[4]=HIBYTE(LOWORD(StartSektor.GetHSG())); + OpC[5]=LOBYTE(LOWORD(StartSektor.GetHSG())); + OpC[6]=0; + OpC[7]=0; + OpC[8]=1; //Set PMI Bit + OpC[9]=0; + + BOOL r=ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,SRB_DIR_IN,OpC,10,(void*)&RetVal,sizeof(RetVal),m_hDriveEvent); + if (!r) + return StartSektor; + LastSektor.SetHSG((RetVal[0]<<24)+(RetVal[1]<<16)+(RetVal[2]<<8)+RetVal[3]); + return LastSektor; +} + +int CSCSICD::GetMaxSektors() +{ + return Config.MaxSektors; +}; + +int CSCSICD::GetSynchSektors() +{ + return Config.SynchSektors; +}; + +int CSCSICD::GetMode() +{ + return Config.Mode; +}; + +int CSCSICD::GetSpeed() +{ + return Config.Speed; +}; + +TSenseInfo CSCSICD::GetSense() +{ + TSenseInfo SenseInfo; + memset(&SenseInfo,0,sizeof(SenseInfo)); + TOpcode OpC; + OpC[0]=0x03; + OpC[1]=0; + OpC[2]=0; + OpC[3]=0; + OpC[4]=sizeof(SenseInfo); + OpC[5]=0; + + BOOL r=ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,SRB_DIR_IN,OpC,6,(void*)&SenseInfo,sizeof(SenseInfo),m_hDriveEvent); + if (!r) + memset(&SenseInfo,0,sizeof(SenseInfo)); + return SenseInfo; +} + +TSenseInfo CSCSICD::GetLastSenseInfo() +{ + TSenseInfo Info=m_SenseInfo; + memset(&m_SenseInfo,0,sizeof(TSenseInfo)); + return Info; +} + +void CSCSICD::InitSpeedTable() +{ + if (m_bSpeedTableInitialized) + return; + SupportedSpeeds=0; + switch (MapInfo.GetTypMapping(Config.Type)) + { + case CDTYPE_TOSHIBA : + break; + case CDTYPE_TOSHNEW : + { + SpeedTable[0]=2352*75; + SpeedTable[1]=2352*75*4; + SpeedTable[2]=2352*75*4; + SpeedTable[3]=-2; + SupportedSpeeds=4; + break; + } + case CDTYPE_SONY : + case CDTYPE_RICOH : + case CDTYPE_ATAPI : + case CDTYPE_CYBERDRV : + { + int LastSpeed=GetCurrentSpeed(); + int Speed=65532000; + BOOL bFound=FALSE; + while (!bFound && (Speed>0)) + { + SetCurrentSpeed(Speed); + if (Lasterror()==CDOK) + { + int ResultingSpeed=GetCurrentSpeed(); + if (Lasterror()==CDOK) + { + bFound=FALSE; + for (int i=0; i<SupportedSpeeds; i++) + { + if (SpeedTable[i]==ResultingSpeed) + bFound=TRUE; + } + if (!bFound) + { + SpeedTable[SupportedSpeeds]=ResultingSpeed; + SupportedSpeeds++; + Speed=ResultingSpeed-2352*75; + } + else + { + Speed-=(2352*75); + bFound=FALSE; + } + } + } + else + Speed=0; + } + if (SupportedSpeeds>1) + { + //Swap entries + for (int i=0; i<(SupportedSpeeds/2); i++) + { + int Help=SpeedTable[i]; + SpeedTable[i]=SpeedTable[SupportedSpeeds-1-i]; + SpeedTable[SupportedSpeeds-1-i]=Help; + } + } + SetCurrentSpeed(LastSpeed); + break; + } + case CDTYPE_PLEXTOR : + { + int LastSpeed=GetCurrentSpeed(); + for (int index=1; index<=20; index++) + { + SetCurrentSpeed(index*2352*75); + if (Lasterror()==CDOK) + { + int Speed=GetCurrentSpeed(); + if (Lasterror()==CDOK) + { + BOOL found=FALSE; + for (int i=0; i<SupportedSpeeds; i++) + { + if (SpeedTable[i]==Speed) + found=TRUE; + } + if (!found) + { + SpeedTable[SupportedSpeeds]=Speed; + SupportedSpeeds++; + } + } + } + } + SetCurrentSpeed(LastSpeed); + break; + } + case CDTYPE_NEC : + { + break; + } + case CDTYPE_PHILIPS : + case CDTYPE_MATSHITA : + { + int LastSpeed=GetCurrentSpeed(); + SpeedTable[0]=-1; + SupportedSpeeds++; + for (int index=1; index<8; index++) + { + SetCurrentSpeed(2352*75*index); + if (Lasterror()==CDOK) + { + int Speed=GetCurrentSpeed(); + if ((Lasterror()==CDOK) && + (Speed==2352*75*index)) + { + SpeedTable[SupportedSpeeds]=2352*75*index; + SupportedSpeeds++; + } + } + } + SetCurrentSpeed(LastSpeed); + break; + } + } + m_bSpeedTableInitialized=TRUE; +} + +BYTE CSCSICD::GetSupportedSpeeds() +{ + return SupportedSpeeds; +} + +BOOL IsOldPhilips(TDriveInfo *pConfig) +{ + BOOL bResult=FALSE; + if (strstr(pConfig->ProductID,"2000")) + bResult=TRUE; + if (strstr(pConfig->ProductID,"4020")) + bResult=TRUE; + return bResult; +} + +//return the identifictaion number for the plextor models +//defined values: +#define PX4X 0 +#define PX6X 1 +#define PX8X 2 +#define PX12X 3 +#define PX20X 4 +#define PX32X 5 +#define PXR412 6 +#define PX40X 7 + +DWORD GetPlextorModel(TDriveInfo *pConfig) +{ + DWORD dwResult=PX40X; + char szId[7] = {0}; + strncpy(szId,&pConfig->ProductID[10],6); + szId[6]=0; + if (!_stricmp(szId,"W4220T")) + { + dwResult=PXR412; + } + else + { + if (!_stricmp(szId,"W8220T")) + { + dwResult=PXR412; + } + else + { + szId[5]=0; + if (!_stricmp(szId,"R412C")) + { + dwResult=PXR412; + } + else + { + if (!_stricmp(szId,"R820T")) + { + dwResult=PXR412; + } + else + { + szId[2]=0; + if (!strcmp(szId,"40")) + dwResult=PX40X; + else + { + if (!strcmp(szId,"32")) + dwResult=PX32X; + else + { + if (!strcmp(szId,"20")) + dwResult=PX20X; + else + { + if (!strcmp(szId,"12")) + dwResult=PX12X; + else + { + if (!isdigit(szId[1])) + szId[1]=0; + if (!strcmp(szId,"8")) + dwResult=PX8X; + else + { + if (!strcmp(szId,"6")) + dwResult=PX6X; + else + { + if (!strcmp(szId,"4")) + dwResult=PX4X; + } + } + } + } + } + } + } + } + } + } + return dwResult; +} + +int CSCSICD::GetCurrentSpeed() +{ + TDriveMode ModeSenseData; + int Speed=0; + switch (MapInfo.GetTypMapping(Config.Type)) + { + case CDTYPE_TOSHIBA : + { + Speed=0; + break; + } + case CDTYPE_TOSHNEW : + { + if (!ModeSense(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSenseData,15,0x20,m_hDriveEvent)) + { + Error=CDASPIError; + return 0; + } + int Index=(ModeSenseData[14] & 0x30)>>4; + switch (Index) + { + case 0 : + Speed=2352*75; + break; + case 1 : + Speed=2352*75*4; + break; + case 2 : + Speed=2352*75*4; + break; + case 3 : + Speed=-2; + break; + } + break; + } + case CDTYPE_SONY : + case CDTYPE_RICOH : + case CDTYPE_CYBERDRV : + { + if (!ModeSense(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSenseData,34,0x2A,m_hDriveEvent)) + { + Error=CDASPIError; + return 0; + } + Speed=(ModeSenseData[26]*256+ModeSenseData[27])*1000; + break; + } + case CDTYPE_ATAPI : + { + if (!ATAPIModeSense(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSenseData,34,0x2A,m_hDriveEvent)) + { + Error=CDASPIError; + return 0; + } + if ((ModeSenseData[8]&0x3F)==0x2A) + Speed=(ModeSenseData[22]*256+ModeSenseData[23])*1000; + else if ((ModeSenseData[4]&0x3F)==0x2A) + Speed=(ModeSenseData[18]*256+ModeSenseData[19])*1000; + else Speed=-1; + break; + } + case CDTYPE_PLEXTOR : + { + DWORD dwModel=GetPlextorModel(&Config); + if (dwModel!=PXR412) + { + if (!ModeSense(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSenseData,16,0x31,m_hDriveEvent)) + { + Error=CDASPIError; + return 0; + } + int Index=ModeSenseData[14]; + switch (dwModel) + { + case PX4X : + { + switch (Index) + { + case 0 : + Speed=2352*75; + break; + case 1 : + Speed=2352*75*2; + break; + case 2 : + Speed=2352*75*4; + break; + default : + Speed=-1; + break; + } + break; + } + case PX6X : + { + switch (Index) + { + case 0 : + Speed=2352*75; + break; + case 1 : + Speed=2352*75*4; + break; + case 2 : + Speed=2352*75*6; + break; + default : + Speed=-1; + break; + } + break; + } + case PX8X : + { + switch (Index) + { + case 0 : + Speed=2352*75; + break; + case 1 : + Speed=2352*75*2; + break; + case 2 : + Speed=2352*75*4; + break; + case 3 : + Speed=2352*75*8; + break; + default : + Speed=-1; + break; + } + break; + } + case PX12X : + { + switch (Index) + { + case 0 : + Speed=2352*75; + break; + case 1 : + Speed=2352*75*2; + break; + case 2 : + Speed=2352*75*4; + break; + case 3 : + Speed=2352*75*8; + break; + case 4 : + Speed=2352*75*8; + break; + case 5 : + Speed=2352*75*12; + break; + default : + Speed=-1; + break; + } + break; + } + case PX20X : + { + switch (Index) + { + case 0 : + Speed=2352*75; + break; + case 1 : + Speed=2352*75*2; + break; + case 2 : + Speed=2352*75*4; + break; + case 3 : + Speed=2352*75*8; + break; + case 4 : + Speed=2352*75*8; + break; + case 5 : + Speed=-2; + break; + case 6 : + Speed=2352*75*12; + break; + default : + Speed=-1; + break; + } + break; + } + case PX32X : + { + switch (Index) + { + case 0 : + Speed=2352*75; + break; + case 1 : + Speed=2352*75*2; + break; + case 2 : + Speed=2352*75*4; + break; + case 3 : + Speed=2352*75*8; + break; + case 4 : + Speed=2352*75*8; + break; + case 5 : + Speed=2352*75*8; + break; + case 6 : + Speed=2352*75*14; + break; + default : + Speed=-1; + break; + } + break; + } + case PX40X : + { + switch (Index) + { + case 0 : + Speed=2352*75; + break; + case 1 : + Speed=2352*75*2; + break; + case 2 : + Speed=2352*75*4; + break; + case 3 : + Speed=2352*75*8; + break; + case 4 : + Speed=2352*75*8; + break; + case 5 : + Speed=2352*75*10; + break; + case 6 : + Speed=2352*75*17; + break; + default : + Speed=-1; + break; + } + break; + } + } + } + else + { + if (!ModeSense(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSenseData,32,0x2A,m_hDriveEvent)) + { + Error=CDASPIError; + return 0; + } + Speed=(ModeSenseData[26]*256+ModeSenseData[27])*1000; + } + break; + } + case CDTYPE_NEC : + { + if (!addModeSense(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSenseData,12,m_hDriveEvent)) + { + Error=CDASPIError; + return 0; + } +// Speed=ModeSenseData[6] & 0x20; + break; + } + case CDTYPE_PHILIPS : + case CDTYPE_MATSHITA : + { + if (!ModeSense(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSenseData,20,0x23,m_hDriveEvent)) + { + Error=CDASPIError; + return 0; + } + int Index; + if (IsOldPhilips(&Config)) + Index=ModeSenseData[14]; + else + Index=ModeSenseData[16]; + switch (Index) + { + case 0 : + Speed=-1; + break; + case 1 : + Speed=2352*75; + break; + case 2 : + Speed=2352*75*2; + break; + case 4 : + Speed=2352*75*4; + break; + case 6 : + Speed=2352*75*6; + break; + case 8 : + Speed=2352*75*8; + break; + default : + Speed=-2; + } + break; + } + default : + Speed=0; + } + return Speed; +} + +void CSCSICD::SetCurrentSpeed(int Speed) +{ + TDriveMode ModeSenseData; + if (Speed==0) + return; + switch (MapInfo.GetTypMapping(Config.Type)) + { + case CDTYPE_TOSHIBA : + break; + case CDTYPE_TOSHNEW : + { + if (!ModeSense(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSenseData,15,0x20,m_hDriveEvent)) + { + Error=CDASPIError; + return; + } + ModeSenseData[0]=0; + int Index; + switch (Speed) + { + case -2 : + Index=3; + break; + case 2352*75 : + Index=0; + break; + case 2352*75*4 : + Index=1; + break; + default : + Index=2; + } + ModeSenseData[14]=(ModeSenseData[14] & 0xcf)|(Index<<4); + if (!ModeSelect(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSenseData,15,m_hDriveEvent)) + Error=CDASPIError; + break; + } + case CDTYPE_SONY : + case CDTYPE_RICOH : + case CDTYPE_CYBERDRV : + case CDTYPE_ATAPI : + { + TOpcode OpC; + OpC[0]=0xbb; + OpC[1]=0x00; + OpC[4]=0x00; + OpC[5]=0x00; + OpC[6]=0x00; + OpC[7]=0x00; + OpC[8]=0x00; + OpC[9]=0x00; + OpC[10]=0x00; + OpC[11]=0x00; + int NewSpeed=Speed/1000; + int ModSpeed=NewSpeed*1000; + int Direction=0; + int AktSpeed=0; + int Counter=0; + do + { + Error=CDOK; + Counter++; + NewSpeed+=Direction; + OpC[2]=NewSpeed/256; + OpC[3]=NewSpeed%256; + if (!ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,SRB_DIR_OUT,OpC,12,NULL,0,m_hDriveEvent)) + Error=CDASPIError; + // Is the speed really the one we have selected? + AktSpeed=GetCurrentSpeed(); + if (!Direction) + { + if (AktSpeed<Speed) + Direction=1; + else if (AktSpeed>Speed) + Direction=-1; + } + } + while ((AktSpeed!=ModSpeed) && NewSpeed && (Counter<3)); + break; + } + case CDTYPE_PLEXTOR : + { + DWORD dwModel=GetPlextorModel(&Config); + if (dwModel!=PXR412) + { + if (!ModeSense(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSenseData,16,0x31,m_hDriveEvent)) + { + Error=CDASPIError; + return; + } + int Index; + switch (dwModel) + { + case PX4X : + { + switch (Speed) + { + case 2352*75 : + Index=0; + break; + case 2352*75*2 : + Index=1; + break; + case -1 : + case 2352*75*4 : + Index=2; + break; + default : + Index=-1; + break; + } + break; + } + case PX6X : + { + switch (Speed) + { + case 2352*75 : + Index=0; + break; + case 2352*75*4 : + Index=1; + break; + case -1 : + case 2352*75*6 : + Index=2; + break; + default : + Index=-1; + break; + } + break; + } + case PX8X : + { + switch (Speed) + { + case 2352*75 : + Index=0; + break; + case 2352*75*2 : + Index=1; + break; + case 2352*75*4 : + Index=2; + break; + case -1 : + case 2352*75*8 : + Index=3; + break; + default : + Index=-1; + break; + } + break; + } + case PX12X : + { + switch (Speed) + { + case 2352*75 : + Index=0; + break; + case 2352*75*2 : + Index=1; + break; + case 2352*75*4 : + Index=2; + break; + case 2352*75*8 : + Index=3; + break; + case -1 : + case 2352*75*12 : + Index=5; + break; + default : + Index=-1; + break; + } + break; + } + case PX20X : + { + switch (Speed) + { + case 2352*75 : + Index=0; + break; + case 2352*75*2 : + Index=1; + break; + case 2352*75*4 : + Index=2; + break; + case 2352*75*8 : + Index=3; + break; + case -1 : + case 2352*75*12 : + Index=6; + break; + default : + Index=-1; + break; + } + break; + } + case PX32X : + { + switch (Speed) + { + case 2352*75 : + Index=0; + break; + case 2352*75*2 : + Index=1; + break; + case 2352*75*4 : + Index=2; + break; + case 2352*75*8 : + Index=3; + break; + case -1 : + case 2352*75*14 : + Index=6; + break; + default : + Index=-1; + break; + } + break; + } + case PX40X : + { + switch (Speed) + { + case 2352*75 : + Index=0; + break; + case 2352*75*2 : + Index=1; + break; + case 2352*75*4 : + Index=2; + break; + case 2352*75*8 : + Index=3; + break; + case 2352*75*10 : + Index=5; + break; + case -1 : + case 2352*75*17 : + Index=6; + break; + default : + Index=-1; + break; + } + break; + } + } + if (Index>=0) + { + ModeSenseData[14]=Index; + if (!ModeSelect(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSenseData,16,m_hDriveEvent)) + Error=CDASPIError; + } + else + Error=CDASPIError; + } + else + { + TOpcode OpC; + OpC[0]=0xbb; + OpC[1]=0x00; + OpC[4]=0xff; + OpC[5]=0xff; + OpC[6]=0x00; + OpC[7]=0x00; + OpC[8]=0x00; + OpC[9]=0x00; + OpC[10]=0x00; + OpC[11]=0x00; + int NewSpeed=Speed/1000; + int ModSpeed=NewSpeed*1000; + int Direction=0; + int AktSpeed=0; + int Counter=0; + do + { + Error=CDOK; + Counter++; + NewSpeed+=Direction; + OpC[2]=NewSpeed/256; + OpC[3]=NewSpeed%256; + if (!ExecuteSCSIRequest(Config.HostAdapterNumber,Config.ID,Config.LUN,SRB_DIR_OUT,OpC,12,NULL,0,m_hDriveEvent)) + Error=CDASPIError; + // Is the speed really the one we have selected? + AktSpeed=GetCurrentSpeed(); + if (!Direction) + { + if (AktSpeed<Speed) + Direction=1; + else if (AktSpeed>Speed) + Direction=-1; + } + } + while ((AktSpeed!=ModSpeed) && NewSpeed && (Counter<3)); + } + break; + } + case CDTYPE_NEC : + { + if (!addModeSense(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSenseData,12,m_hDriveEvent)) + { + Error=CDASPIError; + return; + } +// Speed=ModeSenseData[6] & 0x20; + if (!addModeSelect(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSenseData,12,m_hDriveEvent)) + Error=CDASPIError; + break; + } + case CDTYPE_PHILIPS : + case CDTYPE_MATSHITA : + { + if (!ModeSense(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSenseData,20,0x23,m_hDriveEvent)) + { + Error=CDASPIError; + return; + } + ModeSenseData[0]=0; + int Index; + switch (Speed) + { + case -2 : + case -1 : + Index=0; + break; + case 2352*75 : + Index=1; + break; + case 2352*75*2 : + Index=2; + break; + case 2352*75*4 : + Index=4; + break; + case 2352*75*6 : + Index=6; + break; + case 2352*75*8 : + Index=8; + break; + default : + Index=0; + } + if (IsOldPhilips(&Config)) + ModeSenseData[14]=Index; + else + ModeSenseData[16]=Index; + if (!ModeSelect(Config.HostAdapterNumber,Config.ID,Config.LUN,ModeSenseData,20,m_hDriveEvent)) + Error=CDASPIError; + break; + } + } +} + +int CSCSICD::GetSpeed(BYTE Index) +{ + if (Index<SupportedSpeeds) + return SpeedTable[Index]; + return 0; +} + + +// ---------------------------------------------------------------------------------------- +// - Implementation of general functions - +// - - +// - Author: Christoph Schmelnik - +// - Purposee: variour initialisations - +// ---------------------------------------------------------------------------------------- + +extern "C" DWORD NtScsiSendASPI32Command( LPSRB lpsrb ); +int LoadASPI2() +{ + OSVERSIONINFO VersionInfo; + VersionInfo.dwOSVersionInfoSize=sizeof(VersionInfo); + GetVersionEx(&VersionInfo); + if (VersionInfo.dwPlatformId==VER_PLATFORM_WIN32_NT) + RunningNT=TRUE; + else + RunningNT=FALSE; + ASPIInstalled=TRUE; + + hDLL=LoadLibrary(L"WNASPI32.DLL"); // load DLL + + if (hDLL==0) + { + if(RunningNT) { + // ok, let's try to see if we can use NT's internal SCSI manager + extern int NtScsiInit( void ); + int nb; + if(nb=NtScsiInit()) { + NumberOfHostAdapters=nb; + SendASPI32Command=(SRBPROC)&NtScsiSendASPI32Command; + return TRUE; + } + } + ASPIInstalled=FALSE; + return FALSE; + } + GetASPI32SupportInfo=(VOIDPROC)GetProcAddress(hDLL,"GetASPI32SupportInfo"); // get Address + SendASPI32Command=(SRBPROC)GetProcAddress(hDLL,"SendASPI32Command"); // get Address + if (GetASPI32SupportInfo==NULL) + { + ASPIInstalled=FALSE; + return FALSE; + } + if (SendASPI32Command==NULL) + { + ASPIInstalled=FALSE; + return FALSE; + } + int r=GetASPI32SupportInfo(); + if (HIBYTE(r)!=SS_COMP) + { + ASPIInstalled=FALSE; + return FALSE; + } + NumberOfHostAdapters=LOBYTE(r); + return TRUE; +} + +int LoadASPI() { + int ret=0; + __try { + ret=LoadASPI2(); + } __except(EXCEPTION_EXECUTE_HANDLER) + { + ret=0; + } + return ret; +} + + +int FreeASPI() +{ + extern int NtScsiDeInit( void ); + NtScsiDeInit(); + if (hDLL) FreeLibrary(hDLL); + hDLL=NULL; + return TRUE; +} + + +int CheckASPI() +{ + return ASPIInstalled; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/windac/Dac32.dsp b/Src/Plugins/Input/in_cdda/windac/Dac32.dsp new file mode 100644 index 00000000..714b0166 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/windac/Dac32.dsp @@ -0,0 +1,127 @@ +# Microsoft Developer Studio Project File - Name="Dac32" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** NICHT BEARBEITEN ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=Dac32 - Win32 Release +!MESSAGE Dies ist kein gültiges Makefile. Zum Erstellen dieses Projekts mit NMAKE +!MESSAGE verwenden Sie den Befehl "Makefile exportieren" und führen Sie den Befehl +!MESSAGE +!MESSAGE NMAKE /f "Dac32.mak". +!MESSAGE +!MESSAGE Sie können beim Ausführen von NMAKE eine Konfiguration angeben +!MESSAGE durch Definieren des Makros CFG in der Befehlszeile. Zum Beispiel: +!MESSAGE +!MESSAGE NMAKE /f "Dac32.mak" CFG="Dac32 - Win32 Release" +!MESSAGE +!MESSAGE Für die Konfiguration stehen zur Auswahl: +!MESSAGE +!MESSAGE "Dac32 - Win32 Release" (basierend auf "Win32 (x86) Dynamic-Link Library") +!MESSAGE "Dac32 - Win32 Debug" (basierend auf "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/Win32/Digital Audio Copy/DAC32 DLL", FAAAAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "Dac32 - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\WinRel" +# PROP BASE Intermediate_Dir ".\WinRel" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir ".\Release" +# PROP Intermediate_Dir ".\Release" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FR /YX /c +# ADD CPP /nologo /MT /W3 /GX /O2 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "DLL" /Fr /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x407 /d "NDEBUG" +# ADD RSC /l 0x407 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# SUBTRACT LINK32 /map /nodefaultlib + +!ELSEIF "$(CFG)" == "Dac32 - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\WinDebug" +# PROP BASE Intermediate_Dir ".\WinDebug" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir ".\Debug" +# PROP Intermediate_Dir ".\Debug" +# ADD BASE CPP /nologo /MT /W3 /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "DLL" /Fr /YX /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x407 /d "_DEBUG" +# ADD RSC /l 0x407 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /debug /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 +# SUBTRACT LINK32 /nodefaultlib + +!ENDIF + +# Begin Target + +# Name "Dac32 - Win32 Release" +# Name "Dac32 - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\aspifunc.cpp +# End Source File +# Begin Source File + +SOURCE=.\dac32.cpp +# End Source File +# Begin Source File + +SOURCE=.\dac32.rc +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\Aspifunc.h +# End Source File +# Begin Source File + +SOURCE=.\Dac32.h +# End Source File +# Begin Source File + +SOURCE=.\Scsidefs.h +# End Source File +# Begin Source File + +SOURCE=.\Winaspi.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/Src/Plugins/Input/in_cdda/windac/Dac32.h b/Src/Plugins/Input/in_cdda/windac/Dac32.h new file mode 100644 index 00000000..4e4ab624 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/windac/Dac32.h @@ -0,0 +1,775 @@ +// ---------------------------------------------- +// - DAC32.DLL Header Datei - +// - Written 1996-1998 by Christoph Schmelnik - +// ---------------------------------------------- + +// Changes +// =========== +// Version 1.2 : +// -CMapDrive supports now up to 34 Host Adapters, because now it could access the drives under +// NT with the DeviceControl Interface +// The required information for this is coded as: +// Drives accessed over the new Interface have a HostAdapterNumber above or equal to the +// NumberOfHostAdapters (global Information). +// The ID consists of the good old drive number, like in DOS. +// The LUN is not used for the configuration, but it is used to hold the open handle to the device +// The Burstmodus couldn't be used with the new access mode, so the default setting for those +// drives is the Normal-mode. (For those drives is no difference between Burst- and Normal-mode) +// -LoadASPI checks now the Windows Version and hold this result global in the DLL +// -The Constructor of CSCSCD opens the handles to the new devices +// -The destructor closes those handles +// Interface changes are not required but some values must be handled differnt to avoid +// misconfiguring, e.g. it is not allowed to configure the new drives ID, LUN, and HostAdapterNumber +// -A bug in CWaveSave regarding conversion to mixed mono has been fixed. +// +// Version 1.33 : 18.01.1998 +// Changes: +// Added speed selection support for all current Plextor drives +// +// Version 1.40 : 24.02.1998 +// Changes: +// Set correct direction flags, to work with NT device IO interface and ATAPI drives +// Changed main CD detection to TestUnitReady +// Removed CD detection from Audio Status Info +// Added hopefully correct read command for Matsushita/Panasonic drives +// Added Parameters to CDAC class to allow the disabling of the audio test and to spin up the drive for a specified time +// in seconds to avoid spin up problems on some drives. Both parameters have default values so it behaves like the old version +// without the additional parameters +// Added Parameter to the constructor of CWaveSave to disable writing any Headers. Also this parameter has a default to work like +// before. +// Added virtual function in CDAC to report buffer underruns in Burst Copy Mode +// For the last feature an immediate parameter in WaitCDDA is added +// GetLastSense function added to return the sense information for the last read audio command +// Configuration in CMapDrive extended by new features +// Fixed GetRedBook operator in CCDAdress +// Added function to CD Class to read Media Cataloge Number +// +// Version 1.41 : 28.04.1998 +// Changes: +// New GetInfoEx() function in CMapDrive, to allow a better result checking. +// +// Version 1.42 : 02.08.1998 +// Changes: +// Added GetLastReadableAddress function to get the last readable Sektor of a session. +// Added a flag in the drive properties for the function. +// Added this function to the CDAC object. +// +// Version 1.43 : 23.12.1998 +// Changes: +// Added Wave and DAC classes are now available in a MT version, the old versions will not longer be updated. +// +// Version 1.44 : 10.03.1999 +// Changes: +// Added Support for current Plextor CDROM drives and CD-Writers. +// Added Support for Jukeboxes +// Changed Handling of the Ringbuffer +// +// Version 1.45 : 15.08.1999 +// Changes: +// Added Enhanced error detection for Plextor drives. +// Several Bugfixes (initialising under NT and Ringbuffer specific) +// +// Version 1.45-Build 11 : 11.11.1999 +// Changes: +// Added a check for the MaxSektor parameter in CBaseWaveMT to avoid Program failures even if the applications provides an invalid value. +// Changed source to comile with Borland compiler +// Added MMC-2 Type which will be default for Sony CD-Writers 140 and 928 +// Skip Virtual CD devices in Bus scan +// Fixed Array out of bound bug in drive detection. + + +#ifndef _DAC32_H +#define _DAC32_H + +#ifndef STRICT +#define STRICT // Use strct typechecking +#define WIN32_LEAN_AND_MEAN // compile only important Headerfiles +#endif + +#include <windows.h> +#include <stdio.h> +#include "aspifunc.h" + +/*#ifdef DLL +#define DACDLL __declspec(dllexport) +#else +#define DACDLL __declspec(dllimport) +#endif*/ +#define DACDLL + +#ifdef _DEBUG +#define DBGOUT(sz) OutputDebugString(sz) +#else +#define DBGOUT(sz) +#endif + +//The Errormessages for the CD and DAC Classes +#define CDOK 0x000 +#define CDUnknownDrive 0x001 +#define CDDriveNotReady 0x002 +#define CDUnknownCommand 0x003 +#define CDSeekError 0x006 +#define CDSectorNotFound 0x008 +#define CDReadError 0x00B +#define CDGeneralError 0x00C +#define CDNoCD 0x00E +#define CDIllegalCDChange 0x00F +#define CDDriveNotFound 0x100 +#define CDNoMemory 0x101 +#define CDDACUnable 0x102 +#define CDASPIError 0x103 +#define CDUserBreak 0x104 +#define CDTimeOut 0x105 + +//The Errormessage for the wave classes +#define WAVEFileOpenError 0x200 +#define WAVEFileWriteError 0x201 +#define WAVEChannelError 0x202 +#define WAVEBitsError 0x203 +#define WAVEFreqError 0x204 +#define WAVENameError 0x205 +#define WAVEMemoryError 0x206 + +//The supported base drive types +#define CDTYPE_TOSHIBA 0 +#define CDTYPE_SONY 1 +#define CDTYPE_NEC 2 +#define CDTYPE_PHILIPS 3 +#define CDTYPE_ATAPI 4 +#define CDTYPE_TOSHNEW 5 +#define CDTYPE_RICOH 6 +#define CDTYPE_MATSHITA 7 +#define CDTYPE_PLEXTOR 8 +#define CDTYPE_CYBERDRV 9 + +#define JUKETYPE_SONY 0 +#define JUKETYPE_PIONEER 1 + +//Amount of predefined drive mappings (relationship between real drives and base drive types) +#define MaxMappings 22 // pgo + +//Amount of predefined jukebox mappings (relationship between real jukeboxes and base jukebox types) +#define MaxMappingsJuke 2 + +//The possible Copy modes +#define ModeNormal 0 +#define ModeSynch 1 +#define ModeBurst 2 + +//The possible Values for the DA Test +#define DATEST_ALLWAYS 0 +#define DATEST_FIRSTTRACK 1 +#define DATEST_NEVER 2 + +//The possible SpinUp modes +#define SPINUP_ALLWAYS 0 +#define SPINUP_FIRSTTRACK 1 +#define SPINUP_NEVER 2 + +// Amount of DWORD for the synchronisation +#define SynLen 512 + +// The Class for the Addressformats +class DACDLL CCDAdress +{ +public: + void SetRedbook(long Value); + void SetHSG(long Value) + { + Adresse=Value; + }; + long GetHSG() + { + return Adresse; + }; + long GetRedbook() + { + long t; + t=(Adresse % 75)<<24; + t+=((Adresse / 75) % 60)<<16; + t+=((Adresse / 75) / 60)<<8; + return t; + }; + CCDAdress &operator = (const CCDAdress &other) + { + Adresse=other.Adresse; + return (*this); + }; + CCDAdress &operator + (const long Value) + { + Adresse+=Value; + return (*this); + }; + CCDAdress &operator - (const long Value) + { + Adresse-=Value; + return (*this); + }; + CCDAdress &operator += (const long Value) + { + Adresse+=Value; + return (*this); + }; + CCDAdress &operator -= (const long Value) + { + Adresse-=Value; + return (*this); + }; +private: + long Adresse; +}; + +// Typendeclarations +struct TDriveStatus +{ + int DoorOpen; //Door open/closed + int DoorLocked; //Door locked/unlocked + int Cooked_RAW; //supports Cooked and RAW or Cooked + int Read_Write; //supports read and write + int Data_Audio_Video; //supports Data/Audio/Video or only Data + int Interleave; //supports Interleave regarding ISO + int CommandPrefetch; //supports Command Prefetching + int AudioChannelManipulation; //supports Audio-Channel Manipulation + int HSG_Redbook; //supports HSG and Redbook Addressing or only HSG + int CDPresent; //CD inserted or not + int RWSupport; //supports R-W-Sub Channels +}; + +struct TAudioStatus +{ + BOOL Pause; //Play is paused + BOOL IsPlaying; //CD is playing + BOOL IsDone; //Play is stopped + BOOL PlayError; //Play completed with error + int TrackNummer; //Number of actual track + CCDAdress AbsSektor,RelSektor; //Startsector and Endsector of last/next Play +}; + +struct TTrackFlag +{ + BYTE AudioChannels; //Amount of Audio Channels (2/4) + BOOL PreEmphasis; //Audio Channel with or without... + BOOL DataTrack; //Data track or Audio track + BOOL CopyProhibeted; //Digital copy prohibited +}; + +struct TTrackList +{ + BYTE TrackNummer; //Number of Track + CCDAdress StartSektor; //First sector in HSG Format + long Laenge; //Amount of Sectors + TTrackFlag Flags; +}; + +struct TTrackListeMem +{ + TTrackList Info; + TTrackListeMem *Prev,*Next; +}; + +struct TDriveInfo +{ + int ID; + int LUN; + int HostAdapterNumber; + int Type; + int MaxSektors; + int SynchSektors; + int Mode; + char VendorID[9]; + char ProductID[17]; + int Speed; + int PerformDATest; + int SpinUpMode; + DWORD dwSpinUpTime; + BOOL bUseLastReadableAddress; + BOOL bUseC2ErrorInfo; + BOOL bSpinDown; +}; + +struct TJukeInfo +{ + int ID; + int LUN; + int HostAdapterNumber; + int Type; + int MaxDrives; + int *pConnectedDrives; + int MaxDiscs; + char VendorID[9]; + char ProductID[17]; +}; + +// The class with the infos for the type mapping +class DACDLL CMapInfo +{ +public: + CMapInfo(); + char *GetTypName(int Index); + int GetTypMapping(int Index); + int GetTypMappingRev(int CDType); +private: + char TypNamen[MaxMappings][9]; + int TypMapping[MaxMappings]; +}; + +// The class with the infos for the type mapping +class DACDLL CMapInfoJuke +{ +public: + CMapInfoJuke(); + char *GetTypName(int Index); + int GetTypMapping(int Index); + int GetTypMappingRev(int JukeType); +private: + char TypNamen[MaxMappingsJuke][9]; + int TypMapping[MaxMappingsJuke]; +}; + +// The base class (pure virtual) for the CD access +class DACDLL CBaseCD +{ +public: + int Lasterror(); + virtual void PrepareCDDA()=0; + virtual void ReadCDDA(CCDAdress StartSektor,long Sektoranzahl,void *Buffer,BOOL bUseC2ErrorInfo=FALSE)=0; + virtual BOOL WaitCDDA(BOOL bImmediate=FALSE)=0; + virtual void FinishCDDA()=0; + virtual void SortWaveData(DWORD *Data,int Samples)=0; + virtual CCDAdress GetErrorAdress()=0; + virtual void Play_Audio(CCDAdress StartSektor,long Sektoranzahl)=0; + virtual void Stop_Audio()=0; + virtual void Pause_Audio()=0; + virtual void Resume_Audio()=0; + virtual TDriveStatus Get_DriveStatus()=0; + virtual BOOL MediaChanged()=0; + virtual void Get_MediaCatalogNumber(char szUPC[16])=0; + virtual void EjectDisk()=0; + virtual void LockDoor(int Lock)=0; + virtual void CloseTray()=0; + virtual void ReRead()=0; + virtual CCDAdress GetLastReadableAddress(CCDAdress StartSektor)=0; + virtual int GetMaxSektors()=0; + virtual int GetSynchSektors()=0; + virtual int GetMode()=0; + virtual int GetSpeed()=0; + virtual void InitSpeedTable()=0; + virtual BYTE GetSupportedSpeeds()=0; + virtual int GetCurrentSpeed()=0; + virtual void SetCurrentSpeed(int Speed)=0; + virtual int GetSpeed(BYTE Index)=0; + int ReadFirstTrackInfo(TTrackList &Infos); + int ReadNextTrackInfo(TTrackList &Infos); + int ReadPrevTrackInfo(TTrackList &Infos); + int ReadTrackInfo(TTrackList &Infos); + int ReadMaxTracks(); +protected: + int Error,BusyFlag,DoneFlag; + TTrackListeMem *FirstTrack,*AktTrack; + void DeleteTrackList(); +private: + CBaseCD& operator = (const CBaseCD &other); +}; + +// The class for the access to SCSI drives +class DACDLL CSCSICD:public CBaseCD +{ +public: + CSCSICD (char drive, TDriveInfo &xInfo); + ~CSCSICD(); + virtual void PrepareCDDA(); + virtual void ReadCDDA(CCDAdress StartSektor,long Sektoranzahl,void *Buffer,BOOL bUseC2ErrorInfo=FALSE); + virtual BOOL WaitCDDA(BOOL bImmediate=FALSE); + virtual void FinishCDDA(); + virtual void SortWaveData(DWORD *Data,int Samples); + virtual CCDAdress GetErrorAdress(); + virtual void Play_Audio(CCDAdress StartSektor,long Sektoranzahl); + virtual void Stop_Audio(); + virtual void Pause_Audio(); + virtual void Resume_Audio(); + virtual TDriveStatus Get_DriveStatus(); + virtual BOOL MediaChanged(); + virtual TAudioStatus Get_AudioStatus_Info(); + virtual void Get_MediaCatalogNumber(char szUPC[16]); + virtual void EjectDisk(); + virtual void LockDoor(int Lock); + virtual void CloseTray(); + virtual void ReRead(); + virtual CCDAdress GetLastReadableAddress(CCDAdress StartSektor); + virtual int GetMaxSektors(); + virtual int GetSynchSektors(); + virtual int GetMode(); + virtual int GetSpeed(); + virtual void InitSpeedTable(); + virtual BYTE GetSupportedSpeeds(); + virtual int GetCurrentSpeed(); + virtual void SetCurrentSpeed(int Speed); + virtual int GetSpeed(BYTE Index); + TDriveInfo &GetInfo() + { + return Config; + }; + TSenseInfo GetSense(); + TSenseInfo GetLastSenseInfo(); + +private: + CSCSICD& operator = (const CSCSICD &other); + + TDriveInfo &Config; // Drive Configuration + BOOL CDPresentLast,Changed; // Helpvariables for the MediaChanged function + SRB_ExecSCSICmd ReadSRB; // SCSI Commando Block + TDriveMode ModeData; + BYTE NECRotationSpeed; + CMapInfo MapInfo; + DWORD StartReadTime; + int SpeedTable[256]; + BYTE SupportedSpeeds; + HANDLE m_hDriveEvent; + TSenseInfo m_SenseInfo; + BOOL m_bSpeedTableInitialized; +}; + +// The base class for saving/converting the audio data +class DACDLL CBaseWave +{ +public: + int Lasterror(); + virtual void WritePuffer(long Samples,DWORD *Buffer)=0; +protected: + int Error; //last occured error +private: + CBaseWave& operator = (const CBaseWave &other); +}; + +typedef struct +{ + BOOL bIsUsed; //Is Buffer used? + BOOL bReady; //Is Nuffer ready to write? + int nSamples; //Number of Samples in Buffer. + int nZeroSamples; //Number of Silence Samples to insert before the buffer + DWORD *dwBuffer; //Buffer for Audio Data +} WAVEBUFFER, *PWAVEBUFFER; + +typedef struct _WAVEBUFFERLIST +{ + PWAVEBUFFER pWaveBuffer; + _WAVEBUFFERLIST *pNext; +} WAVEBUFFERLIST, *PWAVEBUFFERLIST; + + +typedef struct +{ + HANDLE hEvent; + LPVOID pData; +} TWAVEMTSTRUCT; + +// The base class for saving/converting the audio data as its own thread +class DACDLL CBaseWaveMT +{ +public: + CBaseWaveMT(DWORD dwBufferSize,BOOL bUseHighPriority=FALSE,int MaxSektors=27,DWORD dwExtraBytes=0); + ~CBaseWaveMT(); + + void SetFadeInOut(int dwTotalSamples,int dwFadeSamples); + + PWAVEBUFFER GetBuffer(); //returns NULL if no Buffer is available + void SignalBuffer(); //signal if a buffer is filled; + int Lasterror(); + double GetBufferFullRatio(); + DWORD GetBytesInBuffer(); + + virtual void WritePuffer(long Samples,DWORD *Buffer)=0; + + friend unsigned _stdcall WaveThreadProc(LPVOID pUserData); + +protected: + int Error; //last occured error + + void StartThread(); //call this from your own initialisation function + BOOL WriteData(); + void StopThread(BOOL bImmediate=FALSE); //call this from your own cleanup function + +private: + CBaseWaveMT& operator = (const CBaseWaveMT &other); + + DWORD m_Nullen[256]; + PWAVEBUFFERLIST m_pFirstBuffer, m_pReadBuffer, m_pWriteBuffer; + TWAVEMTSTRUCT m_WaveInfo; + BOOL m_bStopThread,m_bAbortThread,m_bIsWorking; + HANDLE m_hWaveThread; + BOOL m_bUseHighPriority; + int m_dwTotalSamples,m_dwFadeSamples,m_dwCurrentSample; + int m_nBufferNum,m_nReadBufferNum,m_nWriteBufferNum; + int m_nMaxSektors; +}; + +// The class for saving audio data in a wave file +class DACDLL CWaveSave:public CBaseWave +{ +public: + CWaveSave(const char *DateiName,BYTE Freq,BYTE Channels,BYTE Bits,BOOL bWriteHeaders=TRUE); + ~CWaveSave(); + virtual void WritePuffer(long Samples,DWORD *Buffer); +private: + void WMono8(long Samples,DWORD *Buffer); + void WStereo8(long Samples,DWORD *Buffer); + void WMono16(long Samples,DWORD *Buffer); + void WStereo16(long Samples,DWORD *Buffer); + void WLR8(long Samples,int Mode,DWORD *Buffer); + void WLR16(long Samples,int Mode,DWORD *Buffer); + + CWaveSave& operator = (const CWaveSave &other); + + BYTE ConvertType; //CodeNumber of the conversion type + FILE *Datei; //file variable to access the wave file + WORD *DPM16; //Pointer to the file data buffer + BYTE *DPM8; //Pointer to the file data buffer + DWORD *DPS16; //Pointer to the file data buffer + BYTE *DPS8; //Pointer to the file data buffer + int DatenCount; //Counter of data in buffer + long WaveBytes; //Counts all written bytes + BYTE SAdd; //Value to increment the source counter + BOOL m_bWriteHeaders; //Write Headers of Wave file +}; + +// The class for saving audio data in a wave file in its own thread +class DACDLL CWaveSaveMT:public CBaseWaveMT +{ +public: + CWaveSaveMT(DWORD dwBufferSize,BOOL bUseHighPriority=FALSE,int MaxSektors=27,DWORD dwExtraBytes=0):CBaseWaveMT(dwBufferSize,bUseHighPriority,MaxSektors,dwExtraBytes) + { + }; + void Init(const char *DateiName,BYTE Freq,BYTE Channels,BYTE Bits,BOOL bWriteHeaders=TRUE); + void Done(BOOL bImmediate=FALSE); + virtual void WritePuffer(long Samples,DWORD *Buffer); +private: + void WMono8(long Samples,DWORD *Buffer); + void WStereo8(long Samples,DWORD *Buffer); + void WMono16(long Samples,DWORD *Buffer); + void WStereo16(long Samples,DWORD *Buffer); + void WLR8(long Samples,int Mode,DWORD *Buffer); + void WLR16(long Samples,int Mode,DWORD *Buffer); + + CWaveSave& operator = (const CWaveSave &other); + + BYTE ConvertType; //CodeNumber of the conversion type + FILE *Datei; //file variable to access the wave file + WORD *DPM16; //Pointer to the file data buffer + BYTE *DPM8; //Pointer to the file data buffer + DWORD *DPS16; //Pointer to the file data buffer + BYTE *DPS8; //Pointer to the file data buffer + int DatenCount; //Counter of data in buffer + long WaveBytes; //Counts all written bytes + BYTE SAdd; //Value to increment the source counter + BOOL m_bWriteHeaders; //Write Headers of Wave file +}; + +// The class for copying the audio data from CD. +class DACDLL CDAC +{ +public: + CDAC(CBaseCD *pDrive,CBaseWave *pWave, + CCDAdress Start,long Laenge,BOOL xKillZeros,BOOL bPerformDATest=TRUE,DWORD dwSpinUpTime=0,BOOL bUseLastReadableAddress=FALSE); + ~CDAC(); + int Lasterror(); + void Copy(); + int Errors(); + void StopCopy(); + + // The following member functions are declared as virtual. They could be used to display + // information to the user. They do nothing by default. + virtual void WriteInit(); + virtual void WritePercent(float Percent); + virtual void WriteReading(); + virtual void WriteReadingEnd(); + virtual void WriteSynch(); + virtual void WriteSynchEnd(); + virtual void WriteFlushing(); + virtual void WriteFlushingEnd(); + virtual void WriteSynchError(); + virtual void WriteBufferUnderrun(CCDAdress Start); + virtual void WriteReRead(CCDAdress Start,long Laenge); + virtual void WriteSektorsSkipped(CCDAdress Start,long Laenge); + virtual void WriteDone(); + virtual void OnIdle(); + +protected: + CCDAdress StartSektor,StartOld; + int SynchErrors,Error; + long Anzahl,AnzahlOld,Remain; + +private: + CBaseCD *m_pCD; + BOOL RunCopy,KillFirst,KillLast,KillZero,Found; + DWORD m_dwSpinUpTime; + CBaseWave *m_pWaveSave; + long SektorAnzahl,Retries,SynchDiff,ZeroCount; + DWORD *MemBlocks[2]; + int BlockCount,S_Offset; + int SpeedSave; + DWORD m_Nullen[256]; + + void ReadCDDA(CCDAdress Start,long Sektoranzahl,void *Buffer); + void FlushWave(); + void FlushSynch(int Samples,DWORD *Data); + void MakeTable(DWORD *Werte,DWORD *Table); + int SynchSearch(DWORD *String1,DWORD *String2,DWORD *Table); + void SynchWave(); + + CDAC& operator = (const CDAC &other); +}; + +// The class for copying the audio data from CD. +class DACDLL CDACMT +{ +public: + CDACMT(CBaseCD *pDrive,CBaseWaveMT *pWave, + CCDAdress Start,long Laenge,BOOL xKillZeros,BOOL bPerformDATest=TRUE,DWORD dwSpinUpTime=0,BOOL bUseHighPriority=FALSE,BOOL bUseC2ErrorInfo=FALSE,BOOL bSpinDown=FALSE,BOOL bUseLastReadableAddress=FALSE); + ~CDACMT(); + int Lasterror(); + void Copy(); + int Errors(); + void StopCopy(); + + // The following member functions are declared as virtual. They could be used to display + // information to the user. They do nothing by default. + virtual void WriteInit(); + virtual void WritePercent(float Percent); + virtual void WriteReading(); + virtual void WriteReadingEnd(); + virtual void WriteSynch(); + virtual void WriteSynchEnd(); + virtual void WriteFlushing(); + virtual void WriteFlushingEnd(); + virtual void WriteSynchError(); + virtual void WriteBufferUnderrun(CCDAdress Start); + virtual void WriteReRead(CCDAdress Start,long Laenge); + virtual void WriteSektorsSkipped(CCDAdress Start,long Laenge); + virtual void WriteDone(); + virtual void OnIdle(BOOL bReturnFast=TRUE); + + friend unsigned _stdcall DACThreadProc(LPVOID pUserData); +protected: + CCDAdress StartSektor,StartOld; + int SynchErrors,Error; + long Anzahl,AnzahlOld,Remain; + + void CopyMT(); +private: + CBaseCD *m_pCD; + BOOL RunCopy,KillFirst,KillLast,KillZero,Found; + DWORD m_dwSpinUpTime; + CBaseWaveMT *m_pWaveSave; + BOOL m_bUseC2ErrorInfo; + BOOL m_bSpinDown; + long SektorAnzahl,Retries,SynchDiff,ZeroCount; + PWAVEBUFFER MemBlocks[2]; + int BlockCount,S_Offset; + int SpeedSave; + int m_MaxSektors; + int m_SynchSektors; + + void ReadCDDA(CCDAdress Start,long Sektoranzahl,void *Buffer); + void FlushWave(int nMax=2); + void FlushSynch(int Samples,PWAVEBUFFER Data); + void MakeTable(DWORD *Werte,DWORD *Table); + int SynchSearch(DWORD *String1,DWORD *String2,DWORD *Table); + void SynchWave(); + + CDACMT& operator = (const CDACMT &other); + + HANDLE m_hDACThread; + BOOL m_bUseHighPriority; +}; + +// The class for configuring the SCSI CDROM drives +class DACDLL CMapDrive +{ +public: + CMapDrive(BOOL bDoReset=TRUE); + ~CMapDrive(); + void Reset(); + int GetMaxDrives(); + TDriveInfo &GetInfo(int index); + BOOL GetInfoEx(int index, TDriveInfo *&pInfo); + void DeleteInfo(int index); + int GetMaxHostAdapters(); + int GetSupportedHostAdapterMemory(int index); + void SetSupportedHostAdapterMemory(int index,int Memory); + int GetMaxSektors(int HostAdapterNumber); +protected: + void DeleteAll(); + +private: + struct TDriveInfoMem + { + TDriveInfo Info; + TDriveInfoMem *Next; + }; + TDriveInfoMem *First; + int HostAdapterMemory[34]; + HANDLE m_hDriveEvent; +}; + + +// The class for configuring the SCSI Jukeboxes +class DACDLL CMapJuke +{ +public: + CMapJuke(BOOL bDoReset=TRUE); + ~CMapJuke(); + void Reset(); + int GetMaxJukes(); + TJukeInfo &GetInfo(int index); + BOOL IsWorking(int index); + void SetWorking(int index,BOOL bIsWorking); + void DeleteInfo(int index); +protected: + void DeleteAll(); + +private: + struct TJukeInfoMem + { + TJukeInfo Info; + BOOL bIsWorking; + TJukeInfoMem *Next; + }; + TJukeInfoMem *First; + HANDLE m_hJukeEvent; +}; + +//The class to access Jukeboxes +class DACDLL CJukeBox +{ +public: + CJukeBox (TJukeInfo &xInfo); + ~CJukeBox(); + //use following defines to address an item in the jukebox: + //drive0..x : 0x4000...0x400x + //storage1..xxx : 0x0001...0x0xxx + BOOL MoveMedium(int Source,int Destination); + TJukeInfo &GetInfo() + { + return Config; + }; + +private: + TJukeInfo &Config; // Drive Configuration + CMapInfoJuke MapInfo; + HANDLE m_hJukeEvent; +}; + + +// ---------------------- +// function declarations +// ---------------------- + +// initialize and deinatialize the WNASPI32.DLL and some internal flags +int DACDLL LoadASPI(); +int DACDLL FreeASPI(); + +int DACDLL CheckASPI(); + + + +#endif //_DAC32_H
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/windac/Dac32.mak b/Src/Plugins/Input/in_cdda/windac/Dac32.mak new file mode 100644 index 00000000..46b175f7 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/windac/Dac32.mak @@ -0,0 +1,285 @@ +# Microsoft Developer Studio Generated NMAKE File, Format Version 4.20 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +!IF "$(CFG)" == "" +CFG=Dac32 - Win32 Debug +!MESSAGE No configuration specified. Defaulting to Dac32 - Win32 Debug. +!ENDIF + +!IF "$(CFG)" != "Dac32 - Win32 Release" && "$(CFG)" != "Dac32 - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!MESSAGE You can specify a configuration when running NMAKE on this makefile +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "Dac32.mak" CFG="Dac32 - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Dac32 - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "Dac32 - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF +################################################################################ +# Begin Project +# PROP Target_Last_Scanned "Dac32 - Win32 Debug" +CPP=cl.exe +RSC=rc.exe +MTL=mktyplib.exe + +!IF "$(CFG)" == "Dac32 - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "WinRel" +# PROP BASE Intermediate_Dir "WinRel" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +OUTDIR=.\Release +INTDIR=.\Release + +ALL : "$(OUTDIR)\Dac32.dll" "$(OUTDIR)\Dac32.bsc" + +CLEAN : + -@erase "$(INTDIR)\aspifunc.obj" + -@erase "$(INTDIR)\aspifunc.sbr" + -@erase "$(INTDIR)\dac32.obj" + -@erase "$(INTDIR)\dac32.res" + -@erase "$(INTDIR)\dac32.sbr" + -@erase "$(OUTDIR)\Dac32.bsc" + -@erase "$(OUTDIR)\Dac32.dll" + -@erase "$(OUTDIR)\Dac32.exp" + -@erase "$(OUTDIR)\Dac32.lib" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FR /YX /c +# ADD CPP /nologo /MT /W3 /GX /O2 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "DLL" /Fr /YX /c +CPP_PROJ=/nologo /MT /W3 /GX /O2 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "DLL"\ + /Fr"$(INTDIR)/" /Fp"$(INTDIR)/Dac32.pch" /YX /Fo"$(INTDIR)/" /c +CPP_OBJS=.\Release/ +CPP_SBRS=.\Release/ +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /win32 +MTL_PROJ=/nologo /D "NDEBUG" /win32 +# ADD BASE RSC /l 0x407 /d "NDEBUG" +# ADD RSC /l 0x407 /d "NDEBUG" +RSC_PROJ=/l 0x407 /fo"$(INTDIR)/dac32.res" /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +BSC32_FLAGS=/nologo /o"$(OUTDIR)/Dac32.bsc" +BSC32_SBRS= \ + "$(INTDIR)\aspifunc.sbr" \ + "$(INTDIR)\dac32.sbr" + +"$(OUTDIR)\Dac32.bsc" : "$(OUTDIR)" $(BSC32_SBRS) + $(BSC32) @<< + $(BSC32_FLAGS) $(BSC32_SBRS) +<< + +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# SUBTRACT LINK32 /map /nodefaultlib +LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib\ + advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib\ + odbccp32.lib /nologo /subsystem:windows /dll /incremental:no\ + /pdb:"$(OUTDIR)/Dac32.pdb" /machine:I386 /out:"$(OUTDIR)/Dac32.dll"\ + /implib:"$(OUTDIR)/Dac32.lib" +LINK32_OBJS= \ + "$(INTDIR)\aspifunc.obj" \ + "$(INTDIR)\dac32.obj" \ + "$(INTDIR)\dac32.res" + +"$(OUTDIR)\Dac32.dll" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +!ELSEIF "$(CFG)" == "Dac32 - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "WinDebug" +# PROP BASE Intermediate_Dir "WinDebug" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +OUTDIR=.\Debug +INTDIR=.\Debug + +ALL : "$(OUTDIR)\Dac32.dll" "$(OUTDIR)\Dac32.bsc" + +CLEAN : + -@erase "$(INTDIR)\aspifunc.obj" + -@erase "$(INTDIR)\aspifunc.sbr" + -@erase "$(INTDIR)\dac32.obj" + -@erase "$(INTDIR)\dac32.res" + -@erase "$(INTDIR)\dac32.sbr" + -@erase "$(INTDIR)\vc40.idb" + -@erase "$(INTDIR)\vc40.pdb" + -@erase "$(OUTDIR)\Dac32.bsc" + -@erase "$(OUTDIR)\Dac32.dll" + -@erase "$(OUTDIR)\Dac32.exp" + -@erase "$(OUTDIR)\Dac32.ilk" + -@erase "$(OUTDIR)\Dac32.lib" + -@erase "$(OUTDIR)\Dac32.pdb" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +# ADD BASE CPP /nologo /MT /W3 /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "DLL" /Fr /YX /c +CPP_PROJ=/nologo /MTd /W3 /Gm /GX /Zi /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS"\ + /D "DLL" /Fr"$(INTDIR)/" /Fp"$(INTDIR)/Dac32.pch" /YX /Fo"$(INTDIR)/"\ + /Fd"$(INTDIR)/" /c +CPP_OBJS=.\Debug/ +CPP_SBRS=.\Debug/ +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /win32 +MTL_PROJ=/nologo /D "_DEBUG" /win32 +# ADD BASE RSC /l 0x407 /d "_DEBUG" +# ADD RSC /l 0x407 /d "_DEBUG" +RSC_PROJ=/l 0x407 /fo"$(INTDIR)/dac32.res" /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +BSC32_FLAGS=/nologo /o"$(OUTDIR)/Dac32.bsc" +BSC32_SBRS= \ + "$(INTDIR)\aspifunc.sbr" \ + "$(INTDIR)\dac32.sbr" + +"$(OUTDIR)\Dac32.bsc" : "$(OUTDIR)" $(BSC32_SBRS) + $(BSC32) @<< + $(BSC32_FLAGS) $(BSC32_SBRS) +<< + +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /debug /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 +# SUBTRACT LINK32 /nodefaultlib +LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib\ + advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib\ + odbccp32.lib /nologo /subsystem:windows /dll /incremental:yes\ + /pdb:"$(OUTDIR)/Dac32.pdb" /debug /machine:I386 /out:"$(OUTDIR)/Dac32.dll"\ + /implib:"$(OUTDIR)/Dac32.lib" +LINK32_OBJS= \ + "$(INTDIR)\aspifunc.obj" \ + "$(INTDIR)\dac32.obj" \ + "$(INTDIR)\dac32.res" + +"$(OUTDIR)\Dac32.dll" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +!ENDIF + +.c{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.c{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +################################################################################ +# Begin Target + +# Name "Dac32 - Win32 Release" +# Name "Dac32 - Win32 Debug" + +!IF "$(CFG)" == "Dac32 - Win32 Release" + +!ELSEIF "$(CFG)" == "Dac32 - Win32 Debug" + +!ENDIF + +################################################################################ +# Begin Source File + +SOURCE=.\dac32.cpp + +!IF "$(CFG)" == "Dac32 - Win32 Release" + +DEP_CPP_DAC32=\ + ".\Aspifunc.h"\ + ".\Dac32.h"\ + ".\Scsidefs.h"\ + ".\Winaspi.h"\ + + +"$(INTDIR)\dac32.obj" : $(SOURCE) $(DEP_CPP_DAC32) "$(INTDIR)" + +"$(INTDIR)\dac32.sbr" : $(SOURCE) $(DEP_CPP_DAC32) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "Dac32 - Win32 Debug" + +DEP_CPP_DAC32=\ + ".\Aspifunc.h"\ + ".\Dac32.h"\ + ".\Scsidefs.h"\ + ".\Winaspi.h"\ + + +"$(INTDIR)\dac32.obj" : $(SOURCE) $(DEP_CPP_DAC32) "$(INTDIR)" + +"$(INTDIR)\dac32.sbr" : $(SOURCE) $(DEP_CPP_DAC32) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\aspifunc.cpp +DEP_CPP_ASPIF=\ + ".\Aspifunc.h"\ + ".\Scsidefs.h"\ + ".\Winaspi.h"\ + + +"$(INTDIR)\aspifunc.obj" : $(SOURCE) $(DEP_CPP_ASPIF) "$(INTDIR)" + +"$(INTDIR)\aspifunc.sbr" : $(SOURCE) $(DEP_CPP_ASPIF) "$(INTDIR)" + + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\dac32.rc + +"$(INTDIR)\dac32.res" : $(SOURCE) "$(INTDIR)" + $(RSC) $(RSC_PROJ) $(SOURCE) + + +# End Source File +# End Target +# End Project +################################################################################ diff --git a/Src/Plugins/Input/in_cdda/windac/Dac32.rc b/Src/Plugins/Input/in_cdda/windac/Dac32.rc new file mode 100644 index 00000000..1e81aeee --- /dev/null +++ b/Src/Plugins/Input/in_cdda/windac/Dac32.rc @@ -0,0 +1,121 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Neutral resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEU) +#ifdef _WIN32 +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +#pragma code_page(1252) +#endif //_WIN32 + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,4,5,11 + PRODUCTVERSION 1,4,5,11 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "000004b0" + BEGIN + VALUE "Comments", "\0" + VALUE "CompanyName", "CASH\0" + VALUE "FileDescription", "Christoph Schmelnik's Digital Audio Copy 32 Bit Copy Engine\0" + VALUE "FileVersion", "1, 4, 5, 11\0" + VALUE "InternalName", "DAC32 DLL\0" + VALUE "LegalCopyright", "Copyright © 1996-1999 by Christoph Schmelnik\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "Dac32.dll\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "Christoph Schmelnik's Digital Audio Copy for Win32\0" + VALUE "ProductVersion", "Version 1.45\0" + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1200 + END +END + +#endif // !_MAC + +#endif // Neutral resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// Deutsch (Deutschland) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_DEU) +#ifdef _WIN32 +LANGUAGE LANG_GERMAN, SUBLANG_GERMAN +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // Deutsch (Deutschland) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/Src/Plugins/Input/in_cdda/windac/NTScsi.cpp b/Src/Plugins/Input/in_cdda/windac/NTScsi.cpp new file mode 100644 index 00000000..8112b100 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/windac/NTScsi.cpp @@ -0,0 +1,501 @@ +#include <stdio.h> +#include <stddef.h> +#include "NTScsi.h" + +typedef struct { + BYTE ha; + BYTE tgt; + BYTE lun; + BYTE driveLetter; + BOOL bUsed; + HANDLE hDevice; + BYTE inqData[36]; +} NTSCSIDRIVE; + +typedef struct +{ + BYTE numAdapters; + NTSCSIDRIVE drive[26]; +} NTSCSIDRIVES; + +void GetDriveInformation( BYTE i, NTSCSIDRIVE *pDrive ); + +static HANDLE GetFileHandle( BYTE i ); + +static BOOL bNtScsiAvailable = FALSE; +static NTSCSIDRIVES NtScsiDrives; +static BOOL bUseNtScsi = FALSE; + +/* + * Initialization of SCSI Pass Through Interface code. Responsible for + * setting up the array of SCSI devices. This code will be a little + * different from the normal code -- it will query each drive letter from + * C: through Z: to see if it is a CD. When we identify a CD, we then + * send CDB with the INQUIRY command to it -- NT will automagically fill in + * the PathId, TargetId, and Lun for us. + */ + +int NtScsiInit( void ) +{ + BYTE i; + wchar_t buf[4] = {0}; + UINT uDriveType; + int retVal = 0; + + if ( bNtScsiAvailable ) + { + for( i = 2; i < 26; i++ ) if ( NtScsiDrives.drive[i].bUsed ) retVal++; + bUseNtScsi = (retVal > 0 ); + return retVal; + } + + memset( &NtScsiDrives, 0x00, sizeof(NtScsiDrives) ); + + for( i = 0; i < 26; i++ ) + { + NtScsiDrives.drive[i].hDevice = INVALID_HANDLE_VALUE; + } + + for( i = 2; i < 26; i++ ) + { + wsprintf( buf, L"%c:\\", (wchar_t)('A'+i) ); + uDriveType = GetDriveType( buf ); + + /* check if this is a CDROM drive */ + if ( uDriveType == DRIVE_CDROM ) + { + GetDriveInformation( i, &NtScsiDrives.drive[i] ); + + if ( NtScsiDrives.drive[i].bUsed ) + retVal++; + } + } + + NtScsiDrives.numAdapters = NtScsiGetNumAdapters( ); + + bNtScsiAvailable = TRUE; + + if ( retVal > 0 ) + { + bUseNtScsi = TRUE; + } + + return retVal; +} + + +int NtScsiDeInit( void ) +{ + BYTE i; + + if ( !bNtScsiAvailable ) + return 0; + + for( i = 2; i < 26; i++ ) + { + if ( NtScsiDrives.drive[i].bUsed ) + { + CloseHandle( NtScsiDrives.drive[i].hDevice ); + } + } + + NtScsiDrives.numAdapters = NtScsiGetNumAdapters( ); + + ZeroMemory( &NtScsiDrives, sizeof(NtScsiDrives) ); + bNtScsiAvailable = FALSE; + return -1; +} + + +/* + * Returns the number of "adapters" present. + */ +BYTE NtScsiGetNumAdapters( void ) +{ + BYTE buf[256] = {0}; + WORD i; + BYTE numAdapters = 0; + + // PortNumber 0 should exist, so pre-mark it. This avoids problems + // when the primary IDE drives are on PortNumber 0, but can't be opened + // because of insufficient privelege (ie. non-admin). + buf[0] = 1; + + for( i = 0; i < 26; i++ ) + { + if ( NtScsiDrives.drive[i].bUsed ) + buf[NtScsiDrives.drive[i].ha] = 1; + } + + for( i = 0; i <= 255; i++ ) + { + if ( buf[i] ) + numAdapters++; + } + + return numAdapters; +} + + +/* + * Replacement for GetASPI32SupportInfo from wnaspi32.dll + */ +DWORD NtScsiGetASPI32SupportInfo( void ) +{ + DWORD retVal; + + + if ( !NtScsiDrives.numAdapters ) + retVal = (DWORD)(MAKEWORD(0,SS_NO_ADAPTERS)); + else + retVal = (DWORD)(MAKEWORD(NtScsiDrives.numAdapters,SS_COMP)); + + return retVal; +} + +/* + * Needs to call the appropriate function for the lpsrb->SRB_Cmd specified. + * Valid types are SC_HA_INQUIRY, SC_GET_DEV_TYPE, SC_EXEC_SCSI_CMD, + * and SC_RESET_DEV. + */ +DWORD NtScsiSendASPI32Command( LPSRB lpsrb ) +{ + if ( !lpsrb ) + return SS_ERR; + + switch( lpsrb->SRB_Cmd ) + { + case SC_HA_INQUIRY: + return NtScsiHandleHaInquiry( (LPSRB_HAINQUIRY)lpsrb ); + break; + + case SC_GET_DEV_TYPE: + return NtScsiGetDeviceType( (LPSRB_GDEVBLOCK)lpsrb ); + break; + + case SC_EXEC_SCSI_CMD: + return NtScsiExecSCSICommand( (LPSRB_EXECSCSICMD)lpsrb, FALSE ); + break; + + case SC_RESET_DEV: + default: + lpsrb->SRB_Status = SS_ERR; + return SS_ERR; + break; + } + + return SS_ERR; // should never get to here... +} + + +/* + * Universal function to get a file handle to the CD device. Since + * NT 4.0 wants just the GENERIC_READ flag, and Win2K wants both + * GENERIC_READ and GENERIC_WRITE (why a read-only CD device needs + * GENERIC_WRITE access is beyond me...), the easist workaround is to just + * try them both. + */ +static HANDLE GetFileHandle( BYTE i ) +{ + wchar_t buf[12] = {0}; + HANDLE fh = NULL; + OSVERSIONINFO osver; + DWORD dwFlags; + + memset( &osver, 0x00, sizeof(osver) ); + osver.dwOSVersionInfoSize = sizeof(osver); + GetVersionEx( &osver ); + + // if Win2K or greater, add GENERIC_WRITE + dwFlags = GENERIC_READ; + + if ( (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) && (osver.dwMajorVersion > 4) ) + { + dwFlags |= GENERIC_WRITE; + } + + wsprintf( buf, L"\\\\.\\%c:", (wchar_t)('A'+i) ); + fh = CreateFile( buf, dwFlags, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,OPEN_EXISTING, 0, NULL ); + + if ( fh == INVALID_HANDLE_VALUE ) + { + // it went foobar somewhere, so try it with the GENERIC_WRITE bit flipped + dwFlags ^= GENERIC_WRITE; + fh = CreateFile( buf, dwFlags, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL ); + } + + if ( fh == INVALID_HANDLE_VALUE ) + { + } + else + { + } + + return fh; +} + + + +/* + * fills in a pDrive structure with information from a SCSI_INQUIRY + * and obtains the ha:tgt:lun values via IOCTL_SCSI_GET_ADDRESS + */ +void GetDriveInformation( BYTE i, NTSCSIDRIVE *pDrive ) +{ + HANDLE fh; + char buf[2048] = {0}; + BOOL status; + PSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER pswb; + PSCSI_ADDRESS pscsiAddr; + ULONG length, returned; + BYTE inqData[100] = {0}; + + fh = GetFileHandle( i ); + + if ( fh == INVALID_HANDLE_VALUE ) + { + return; + } + + /* + * Get the drive inquiry data + */ + ZeroMemory( &buf, 2048 ); + ZeroMemory( inqData, 100 ); + pswb = (PSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER)buf; + pswb->spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + pswb->spt.CdbLength = 6; + pswb->spt.SenseInfoLength = 24; + pswb->spt.DataIn = SCSI_IOCTL_DATA_IN; + pswb->spt.DataTransferLength = 100; + pswb->spt.TimeOutValue = 2; + pswb->spt.DataBuffer = inqData; + pswb->spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER,ucSenseBuf ); + pswb->spt.Cdb[0] = 0x12; + pswb->spt.Cdb[4] = 100; + + length = sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER); + status = DeviceIoControl( fh, + IOCTL_SCSI_PASS_THROUGH_DIRECT, + pswb, + length, + pswb, + length, + &returned, + NULL ); + + if ( !status ) + { + CloseHandle( fh ); + return; + } + + memcpy( pDrive->inqData, inqData, 36 ); + + /* + * get the address (path/tgt/lun) of the drive via IOCTL_SCSI_GET_ADDRESS + */ + ZeroMemory( &buf, 2048 ); + pscsiAddr = (PSCSI_ADDRESS)buf; + pscsiAddr->Length = sizeof(SCSI_ADDRESS); + if ( DeviceIoControl( fh, IOCTL_SCSI_GET_ADDRESS, NULL, 0, + pscsiAddr, sizeof(buf), &returned, + NULL ) ) + { + pDrive->bUsed = TRUE; + pDrive->ha = pscsiAddr->PortNumber; + pDrive->tgt = pscsiAddr->TargetId; + pDrive->lun = pscsiAddr->Lun; + pDrive->driveLetter = i; + pDrive->hDevice = INVALID_HANDLE_VALUE; + } + else if (50 == GetLastError()) // usb/firewire + { + pDrive->bUsed = TRUE; + pDrive->ha = i; + pDrive->tgt = 0; + pDrive->lun = 0; + pDrive->driveLetter = i; + pDrive->hDevice = INVALID_HANDLE_VALUE; + } + else + { + pDrive->bUsed = FALSE; + } + + CloseHandle( fh ); +} + + + +DWORD NtScsiHandleHaInquiry( LPSRB_HAINQUIRY lpsrb ) +{ + DWORD *pMTL; + + lpsrb->HA_Count = NtScsiDrives.numAdapters; + + if ( lpsrb->SRB_HaId >= NtScsiDrives.numAdapters ) + { + lpsrb->SRB_Status = SS_INVALID_HA; + return SS_INVALID_HA; + } + lpsrb->HA_SCSI_ID = 7; // who cares... we're not really an ASPI manager + memcpy( lpsrb->HA_ManagerId, "blahblahblahblah", 16 ); + memcpy( lpsrb->HA_Identifier, "blahblahblahblah", 16 ); + lpsrb->HA_Identifier[13] = (char)('0'+lpsrb->SRB_HaId); + ZeroMemory( lpsrb->HA_Unique, 16 ); + lpsrb->HA_Unique[3] = 8; + pMTL = (LPDWORD)&lpsrb->HA_Unique[4]; + *pMTL = 64 * 1024; + + lpsrb->SRB_Status = SS_COMP; + return SS_COMP; +} + + +/* + * Scans through the drive array and returns DTYPE_CDROM type for all items + * found, and DTYPE_UNKNOWN for all others. + */ +DWORD NtScsiGetDeviceType( LPSRB_GDEVBLOCK lpsrb ) +{ + lpsrb->SRB_Status = SS_NO_DEVICE; + if ( NtScsiGetDeviceIndex( lpsrb->SRB_HaId, lpsrb->SRB_Target, lpsrb->SRB_Lun ) ) + lpsrb->SRB_Status = SS_COMP; + + if ( lpsrb->SRB_Status == SS_COMP ) + lpsrb->SRB_DeviceType = DTC_CDROM; + else + lpsrb->SRB_DeviceType = DTC_UNKNOWN; + + return lpsrb->SRB_Status; +} + + +/* + * Looks up the index in the drive array for a given ha:tgt:lun triple + */ +BYTE NtScsiGetDeviceIndex( BYTE ha, BYTE tgt, BYTE lun ) +{ + BYTE i; + + for( i = 2; i < 26; i++ ) + { + if ( NtScsiDrives.drive[i].bUsed ) + { + NTSCSIDRIVE *lpd; + lpd = &NtScsiDrives.drive[i]; + if ( (lpd->ha == ha) && (lpd->tgt == tgt) && (lpd->lun == lun) ) + return i; + } + } + return 0; +} + +/* + * Converts ASPI-style SRB to SCSI Pass Through IOCTL + */ +DWORD NtScsiExecSCSICommand( LPSRB_EXECSCSICMD lpsrb, BOOL bBeenHereBefore ) +{ + BOOL status; + SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER swb; + ULONG length, returned; + BYTE idx; + + idx = NtScsiGetDeviceIndex( lpsrb->SRB_HaId, lpsrb->SRB_Target, lpsrb->SRB_Lun ); + + if ( idx == 0 ) + { + lpsrb->SRB_Status = SS_ERR; + return SS_ERR; + } + + if ( lpsrb->CDBByte[0] == 0x12 ) // is it an INQUIRY? + { + lpsrb->SRB_Status = SS_COMP; + memcpy( lpsrb->SRB_BufPointer, NtScsiDrives.drive[idx].inqData, 36 ); + return SS_COMP; + } + + if ( NtScsiDrives.drive[idx].hDevice == INVALID_HANDLE_VALUE ) + NtScsiDrives.drive[idx].hDevice = GetFileHandle( NtScsiDrives.drive[idx].driveLetter ); + + ZeroMemory( &swb, sizeof(swb) ); + swb.spt.Length = sizeof(SCSI_PASS_THROUGH); + swb.spt.CdbLength = lpsrb->SRB_CDBLen; + if ( lpsrb->SRB_Flags & SRB_DIR_IN ) + swb.spt.DataIn = SCSI_IOCTL_DATA_IN; + else if ( lpsrb->SRB_Flags & SRB_DIR_OUT ) + swb.spt.DataIn = SCSI_IOCTL_DATA_OUT; + else + swb.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED; + swb.spt.DataTransferLength = lpsrb->SRB_BufLen; + swb.spt.TimeOutValue = 5; + swb.spt.DataBuffer = lpsrb->SRB_BufPointer; + swb.spt.SenseInfoOffset = + offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, ucSenseBuf ); + memcpy( swb.spt.Cdb, lpsrb->CDBByte, lpsrb->SRB_CDBLen ); + length = sizeof(swb); + + status = DeviceIoControl( NtScsiDrives.drive[idx].hDevice, + IOCTL_SCSI_PASS_THROUGH_DIRECT, + &swb, + length, + &swb, + length, + &returned, + NULL ); + + if ( status ) + { + lpsrb->SRB_Status = SS_COMP; + } + else + { + DWORD dwErrCode; + + lpsrb->SRB_Status = SS_ERR; + lpsrb->SRB_TargStat = 0x0004; + dwErrCode = GetLastError(); + /* + * KLUDGE ALERT! KLUDGE ALERT! KLUDGE ALERT! + * Whenever a disk changer switches disks, it may render the device + * handle invalid. We try to catch these errors here and recover + * from them. + */ + if ( !bBeenHereBefore && + ((dwErrCode == ERROR_MEDIA_CHANGED) || (dwErrCode == ERROR_INVALID_HANDLE)) ) + { + if ( dwErrCode != ERROR_INVALID_HANDLE ) + CloseHandle( NtScsiDrives.drive[idx].hDevice ); + GetDriveInformation( idx, &NtScsiDrives.drive[idx] ); + + return NtScsiExecSCSICommand( lpsrb, TRUE ); + } + } + + return lpsrb->SRB_Status; +} + + + +BOOL UsingSCSIPT( void ) +{ + return bUseNtScsi; +} + + + +/* + * Calls GetFileHandle for the CD refered to by ha:tgt:lun to open it for + * use + */ +void NtScsiOpenCDHandle( BYTE ha, BYTE tgt, BYTE lun ) +{ + BYTE idx; + + idx = NtScsiGetDeviceIndex( ha, tgt, lun ); + + if ( idx && NtScsiDrives.drive[idx].hDevice == INVALID_HANDLE_VALUE ) + NtScsiDrives.drive[idx].hDevice = GetFileHandle( NtScsiDrives.drive[idx].driveLetter ); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/windac/NTScsi.h b/Src/Plugins/Input/in_cdda/windac/NTScsi.h new file mode 100644 index 00000000..895c787d --- /dev/null +++ b/Src/Plugins/Input/in_cdda/windac/NTScsi.h @@ -0,0 +1,147 @@ +/* + * distilled information from various header files from Microsoft's + * DDK for Windows NT 4.0 + */ +#ifndef NTSCSI_H_INCLUDED +#define NTSCSI_H_INCLUDED + +#include <windows.h> +#include "Aspi.h" + +typedef struct { + USHORT Length; + UCHAR ScsiStatus; + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; + UCHAR CdbLength; + UCHAR SenseInfoLength; + UCHAR DataIn; + ULONG DataTransferLength; + ULONG TimeOutValue; + ULONG DataBufferOffset; + ULONG SenseInfoOffset; + UCHAR Cdb[16]; +} SCSI_PASS_THROUGH, *PSCSI_PASS_THROUGH; + + +typedef struct { + USHORT Length; + UCHAR ScsiStatus; + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; + UCHAR CdbLength; + UCHAR SenseInfoLength; + UCHAR DataIn; + ULONG DataTransferLength; + ULONG TimeOutValue; + PVOID DataBuffer; + ULONG SenseInfoOffset; + UCHAR Cdb[16]; +} SCSI_PASS_THROUGH_DIRECT, *PSCSI_PASS_THROUGH_DIRECT; + + +typedef struct { + SCSI_PASS_THROUGH spt; + ULONG Filler; + UCHAR ucSenseBuf[32]; + UCHAR ucDataBuf[512]; +} SCSI_PASS_THROUGH_WITH_BUFFERS, *PSCSI_PASS_THROUGH_WITH_BUFFERS; + + +typedef struct { + SCSI_PASS_THROUGH_DIRECT spt; + ULONG Filler; + UCHAR ucSenseBuf[32]; +} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, *PSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER; + + + +typedef struct { + UCHAR NumberOfLogicalUnits; + UCHAR InitiatorBusId; + ULONG InquiryDataOffset; +} SCSI_BUS_DATA, *PSCSI_BUS_DATA; + + +typedef struct { + UCHAR NumberOfBusses; + SCSI_BUS_DATA BusData[1]; +} SCSI_ADAPTER_BUS_INFO, *PSCSI_ADAPTER_BUS_INFO; + + +typedef struct { + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; + BOOLEAN DeviceClaimed; + ULONG InquiryDataLength; + ULONG NextInquiryDataOffset; + UCHAR InquiryData[1]; +} SCSI_INQUIRY_DATA, *PSCSI_INQUIRY_DATA; + + +typedef struct { + ULONG Length; + UCHAR PortNumber; + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; +} SCSI_ADDRESS, *PSCSI_ADDRESS; + + +/* + * method codes + */ +#define METHOD_BUFFERED 0 +#define METHOD_IN_DIRECT 1 +#define METHOD_OUT_DIRECT 2 +#define METHOD_NEITHER 3 + +/* + * file access values + */ +#define FILE_ANY_ACCESS 0 +#ifndef FILE_READ_ACCESS +#define FILE_READ_ACCESS (0x0001) +#define FILE_WRITE_ACCESS (0x0002) +#endif + + +#define IOCTL_SCSI_BASE 0x00000004 + +/* + * constants for DataIn member of SCSI_PASS_THROUGH* structures + */ +#define SCSI_IOCTL_DATA_OUT 0 +#define SCSI_IOCTL_DATA_IN 1 +#define SCSI_IOCTL_DATA_UNSPECIFIED 2 + +/* + * Standard IOCTL define + */ +#define CTL_CODE( DevType, Function, Method, Access ) ( \ + ((DevType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \ +) + +#define IOCTL_SCSI_PASS_THROUGH CTL_CODE( IOCTL_SCSI_BASE, 0x0401, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS ) +#define IOCTL_SCSI_MINIPORT CTL_CODE( IOCTL_SCSI_BASE, 0x0402, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS ) +#define IOCTL_SCSI_GET_INQUIRY_DATA CTL_CODE( IOCTL_SCSI_BASE, 0x0403, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SCSI_GET_CAPABILITIES CTL_CODE( IOCTL_SCSI_BASE, 0x0404, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SCSI_PASS_THROUGH_DIRECT CTL_CODE( IOCTL_SCSI_BASE, 0x0405, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS ) +#define IOCTL_SCSI_GET_ADDRESS CTL_CODE( IOCTL_SCSI_BASE, 0x0406, METHOD_BUFFERED, FILE_ANY_ACCESS ) + +int NtScsiInit( void ); +int NtScsiDeInit( void ); + +BYTE NtScsiGetNumAdapters( void ); +DWORD NtScsiGetASPI32SupportInfo( void ); +DWORD NtScsiGetDeviceType( LPSRB_GDEVBLOCK lpsrb ); +BYTE NtScsiGetDeviceIndex( BYTE ha, BYTE tgt, BYTE lun ); + +DWORD NtScsiHandleHaInquiry( LPSRB_HAINQUIRY lpsrb ); +extern "C" DWORD NtScsiSendASPI32Command( LPSRB lpsrb ); +DWORD NtScsiExecSCSICommand( LPSRB_EXECSCSICMD lpsrb, BOOL bBeenHereBefore ); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/windac/RESOURCE.H b/Src/Plugins/Input/in_cdda/windac/RESOURCE.H new file mode 100644 index 00000000..3a24dff9 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/windac/RESOURCE.H @@ -0,0 +1,17 @@ +#if USE_WINDAC +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by dac32.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/windac/SCSIDEFS.H b/Src/Plugins/Input/in_cdda/windac/SCSIDEFS.H new file mode 100644 index 00000000..5012429f --- /dev/null +++ b/Src/Plugins/Input/in_cdda/windac/SCSIDEFS.H @@ -0,0 +1,252 @@ +//********************************************************************** +// +// Name: SCSIDEFS.H +// +// Description: SCSI definitions ('C' Language) +// +//********************************************************************** +//********************************************************************** +// %%% TARGET STATUS VALUES %%% +//********************************************************************** +#define STATUS_GOOD 0x00 // Status Good +#define STATUS_CHKCOND 0x02 // Check Condition +#define STATUS_CONDMET 0x04 // Condition Met +#define STATUS_BUSY 0x08 // Busy +#define STATUS_INTERM 0x10 // Intermediate +#define STATUS_INTCDMET 0x14 // Intermediate-condition met +#define STATUS_RESCONF 0x18 // Reservation conflict +#define STATUS_COMTERM 0x22 // Command Terminated +#define STATUS_QFULL 0x28 // Queue full +//********************************************************************** +// %%% SCSI MISCELLANEOUS EQUATES %%% +//********************************************************************** +#define MAXLUN 7 // Maximum Logical Unit Id +#define MAXTARG 7 // Maximum Target Id +#define MAX_SCSI_LUNS 64 // Maximum Number of SCSI LUNs +#define MAX_NUM_HA 8 // Maximum Number of SCSI HA's +///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ +// +// %%% SCSI COMMAND OPCODES %%% +// +///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ +//********************************************************************** +// %%% Commands for all Device Types %%% +//********************************************************************** +#define SCSI_CHANGE_DEF 0x40 // Change Definition (Optional) +#define SCSI_COMPARE 0x39 // Compare (O) +#define SCSI_COPY 0x18 // Copy (O) +#define SCSI_COP_VERIFY 0x3A // Copy and Verify (O) +#define SCSI_INQUIRY 0x12 // Inquiry (MANDATORY) +#define SCSI_LOG_SELECT 0x4C // Log Select (O) +#define SCSI_LOG_SENSE 0x4D // Log Sense (O) +#define SCSI_MODE_SEL6 0x15 // Mode Select 6-byte (Device Specific) +#define SCSI_MODE_SEL10 0x55 // Mode Select 10-byte (Device Specific) +#define SCSI_MODE_SEN6 0x1A // Mode Sense 6-byte (Device Specific) +#define SCSI_MODE_SEN10 0x5A // Mode Sense 10-byte (Device Specific) +#define SCSI_READ_BUFF 0x3C // Read Buffer (O) +#define SCSI_REQ_SENSE 0x03 // Request Sense (MANDATORY) +#define SCSI_SEND_DIAG 0x1D // Send Diagnostic (O) +#define SCSI_RCV_DIAG 0x1C // Receive Diagnostic Results (O) +#define SCSI_TST_U_RDY 0x00 // Test Unit Ready (MANDATORY) +#define SCSI_WRITE_BUFF 0x3B // Write Buffer (O) +//********************************************************************** +// %%% Commands Unique to Direct Access Devices %%% +//********************************************************************** +#define SCSI_FORMAT 0x04 // Format Unit (MANDATORY) +#define SCSI_LCK_UN_CAC 0x36 // Lock Unlock Cache (O) +#define SCSI_PREFETCH 0x34 // Prefetch (O) +#define SCSI_MED_REMOVL 0x1E // Prevent/Allow medium Removal (O) +#define SCSI_READ6 0x08 // Read 6-byte (MANDATORY) +#define SCSI_READ10 0x28 // Read 10-byte (MANDATORY) +#define SCSI_RD_CAPAC 0x25 // Read Capacity (MANDATORY) +#define SCSI_RD_DEFECT 0x37 // Read Defect Data (O) +#define SCSI_READ_LONG 0x3E // Read Long (O) +#define SCSI_REASS_BLK 0x07 // Reassign Blocks (O) +#define SCSI_RELEASE 0x17 // Release Unit (MANDATORY) +#define SCSI_RESERVE 0x16 // Reserve Unit (MANDATORY) +#define SCSI_REZERO 0x01 // Rezero Unit (O) +#define SCSI_SRCH_DAT_E 0x31 // Search Data Equal (O) +#define SCSI_SRCH_DAT_H 0x30 // Search Data High (O) +#define SCSI_SRCH_DAT_L 0x32 // Search Data Low (O) +#define SCSI_SEEK6 0x0B // Seek 6-Byte (O) +#define SCSI_SEEK10 0x2B // Seek 10-Byte (O) +#define SCSI_SET_LIMIT 0x33 // Set Limits (O) +#define SCSI_START_STP 0x1B // Start/Stop Unit (O) +#define SCSI_SYNC_CACHE 0x35 // Synchronize Cache (O) +#define SCSI_VERIFY 0x2F // Verify (O) +#define SCSI_WRITE6 0x0A // Write 6-Byte (MANDATORY) +#define SCSI_WRITE10 0x2A // Write 10-Byte (MANDATORY) +#define SCSI_WRT_VERIFY 0x2E // Write and Verify (O) +#define SCSI_WRITE_LONG 0x3F // Write Long (O) +#define SCSI_WRITE_SAME 0x41 // Write Same (O) +//********************************************************************** +// %%% Commands Unique to Sequential Access Devices %%% +//********************************************************************** +#define SCSI_ERASE 0x19 // Erase (MANDATORY) +#define SCSI_LOAD_UN 0x1B // Load/Unload (O) +#define SCSI_LOCATE 0x2B // Locate (O) +#define SCSI_RD_BLK_LIM 0x05 // Read Block Limits (MANDATORY) +#define SCSI_READ_POS 0x34 // Read Position (O) +#define SCSI_READ_REV 0x0F // Read Reverse (O) +#define SCSI_REC_BF_DAT 0x14 // Recover Buffer Data (O) +#define SCSI_REWIND 0x01 // Rewind (MANDATORY) +#define SCSI_SPACE 0x11 // Space (MANDATORY) +#define SCSI_VERIFY_T 0x13 // Verify (Tape) (O) +#define SCSI_WRT_FILE 0x10 // Write Filemarks (MANDATORY) +#define SCSI_PARTITION 0x0D // DAT/QFA Partition Select +#define SCSI_READWRITE 0x06 // Set Read/Write Parameters +//********************************************************************** +// %%% Commands Unique to Printer Devices %%% +//********************************************************************** +#define SCSI_PRINT 0x0A // Print (MANDATORY) +#define SCSI_SLEW_PNT 0x0B // Slew and Print (O) +#define SCSI_STOP_PNT 0x1B // Stop Print (O) +#define SCSI_SYNC_BUFF 0x10 // Synchronize Buffer (O) +//********************************************************************** +// %%% Commands Unique to Processor Devices %%% +//********************************************************************** +#define SCSI_RECEIVE 0x08 // Receive (O) +#define SCSI_SEND 0x0A // Send (O) +//********************************************************************** +// %%% Commands Unique to Write-Once Devices %%% +//********************************************************************** +#define SCSI_MEDIUM_SCN 0x38 // Medium Scan (O) +#define SCSI_SRCHDATE10 0x31 // Search Data Equal 10-Byte (O) +#define SCSI_SRCHDATE12 0xB1 // Search Data Equal 12-Byte (O) +#define SCSI_SRCHDATH10 0x30 // Search Data High 10-Byte (O) +#define SCSI_SRCHDATH12 0xB0 // Search Data High 12-Byte (O) +#define SCSI_SRCHDATL10 0x32 // Search Data Low 10-Byte (O) +#define SCSI_SRCHDATL12 0xB2 // Search Data Low 12-Byte (O) +#define SCSI_SET_LIM_10 0x33 // Set Limits 10-Byte (O) +#define SCSI_SET_LIM_12 0xB3 // Set Limits 10-Byte (O) +#define SCSI_VERIFY10 0x2F // Verify 10-Byte (O) +#define SCSI_VERIFY12 0xAF // Verify 12-Byte (O) +#define SCSI_WRITE12 0xAA // Write 12-Byte (O) +#define SCSI_WRT_VER10 0x2E // Write and Verify 10-Byte (O) +#define SCSI_WRT_VER12 0xAE // Write and Verify 12-Byte (O) +//********************************************************************** +// %%% Commands Unique to CD-ROM Devices %%% +//********************************************************************** +#define SCSI_PLAYAUD_10 0x45 // Play Audio 10-Byte (O) +#define SCSI_PLAYAUD_12 0xA5 // Play Audio 12-Byte 12-Byte (O) +#define SCSI_PLAYAUDMSF 0x47 // Play Audio MSF (O) +#define SCSI_PLAYA_TKIN 0x48 // Play Audio Track/Index (O) +#define SCSI_PLYTKREL10 0x49 // Play Track Relative 10-Byte (O) +#define SCSI_PLYTKREL12 0xA9 // Play Track Relative 12-Byte (O) +#define SCSI_READCDCAP 0x25 // Read CD-ROM Capacity (MANDATORY) +#define SCSI_READHEADER 0x44 // Read Header (O) +#define SCSI_SUBCHANNEL 0x42 // Read Subchannel (O) +#define SCSI_READ_TOC 0x43 // Read TOC (O) +//********************************************************************** +// %%% Commands Unique to Scanner Devices %%% +//********************************************************************** +#define SCSI_GETDBSTAT 0x34 // Get Data Buffer Status (O) +#define SCSI_GETWINDOW 0x25 // Get Window (O) +#define SCSI_OBJECTPOS 0x31 // Object Position (O) +#define SCSI_SCAN 0x1B // Scan (O) +#define SCSI_SETWINDOW 0x24 // Set Window (MANDATORY) +//********************************************************************** +// %%% Commands Unique to Optical Memory Devices %%% +//********************************************************************** +#define SCSI_UpdateBlk 0x3D // Update Block (O) +//********************************************************************** +// %%% Commands Unique to Medium Changer Devices %%% +//********************************************************************** +#define SCSI_EXCHMEDIUM 0xA6 // Exchange Medium (O) +#define SCSI_INITELSTAT 0x07 // Initialize Element Status (O) +#define SCSI_POSTOELEM 0x2B // Position to Element (O) +#define SCSI_REQ_VE_ADD 0xB5 // Request Volume Element Address (O) +#define SCSI_SENDVOLTAG 0xB6 // Send Volume Tag (O) +//********************************************************************** +// %%% Commands Unique to Communication Devices %%% +//********************************************************************** +#define SCSI_GET_MSG_6 0x08 // Get Message 6-Byte (MANDATORY) +#define SCSI_GET_MSG_10 0x28 // Get Message 10-Byte (O) +#define SCSI_GET_MSG_12 0xA8 // Get Message 12-Byte (O) +#define SCSI_SND_MSG_6 0x0A // Send Message 6-Byte (MANDATORY) +#define SCSI_SND_MSG_10 0x2A // Send Message 10-Byte (O) +#define SCSI_SND_MSG_12 0xAA // Send Message 12-Byte (O) +///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ +// +// %%% END OF SCSI COMMAND OPCODES %%% +// +///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ +//********************************************************************** +// %%% Request Sense Data Format %%% +//********************************************************************** +typedef struct { + BYTE ErrorCode; // Error Code (70H or 71H) + BYTE SegmentNum; // Number of current segment descriptor + BYTE SenseKey; // Sense Key(See bit definitions too) + BYTE InfoByte0; // Information MSB + BYTE InfoByte1; // Information MID + BYTE InfoByte2; // Information MID + BYTE InfoByte3; // Information LSB + BYTE AddSenLen; // Additional Sense Length + BYTE ComSpecInf0; // Command Specific Information MSB + BYTE ComSpecInf1; // Command Specific Information MID + BYTE ComSpecInf2; // Command Specific Information MID + BYTE ComSpecInf3; // Command Specific Information LSB + BYTE AddSenseCode; // Additional Sense Code + BYTE AddSenQual; // Additional Sense Code Qualifier +// BYTE FieldRepUCode; // Field Replaceable Unit Code +// BYTE SenKeySpec15; // Sense Key Specific 15th byte +// BYTE SenKeySpec16; // Sense Key Specific 16th byte +// BYTE SenKeySpec17; // Sense Key Specific 17th byte +// BYTE AddSenseBytes; // Additional Sense Bytes +} SENSE_DATA_FMT; +//********************************************************************** +// %%% REQUEST SENSE ERROR CODE %%% +//********************************************************************** +#define SERROR_CURRENT 0x70 // Current Errors +#define SERROR_DEFERED 0x71 // Deferred Errors +//********************************************************************** +// %%% REQUEST SENSE BIT DEFINITIONS %%% +//********************************************************************** +#define SENSE_VALID 0x80 // Byte 0 Bit 7 +#define SENSE_FILEMRK 0x80 // Byte 2 Bit 7 +#define SENSE_EOM 0x40 // Byte 2 Bit 6 +#define SENSE_ILI 0x20 // Byte 2 Bit 5 +//********************************************************************** +// %%% REQUEST SENSE SENSE KEY DEFINITIONS %%% +//********************************************************************** +#define KEY_NOSENSE 0x00 // No Sense +#define KEY_RECERROR 0x01 // Recovered Error +#define KEY_NOTREADY 0x02 // Not Ready +#define KEY_MEDIUMERR 0x03 // Medium Error +#define KEY_HARDERROR 0x04 // Hardware Error +#define KEY_ILLGLREQ 0x05 // Illegal Request +#define KEY_UNITATT 0x06 // Unit Attention +#define KEY_DATAPROT 0x07 // Data Protect +#define KEY_BLANKCHK 0x08 // Blank Check +#define KEY_VENDSPEC 0x09 // Vendor Specific +#define KEY_COPYABORT 0x0A // Copy Abort +#define KEY_ABORT 0x0B // Abort +#define KEY_EQUAL 0x0C // Equal (Search) +#define KEY_VOLOVRFLW 0x0D // Volume Overflow +#define KEY_MISCOMP 0x0E // Miscompare (Search) +#define KEY_RESERVED 0x0F // Reserved +//********************************************************************** +// %%% PERIPHERAL DEVICE TYPE DEFINITIONS %%% +//********************************************************************** +#define DTYPE_DASD 0x00 // Disk Device +#define DTYPE_SEQD 0x01 // Tape Device +#define DTYPE_PRNT 0x02 // Printer +#define DTYPE_PROC 0x03 // Processor +#define DTYPE_WORM 0x04 // Write-once read-multiple +#define DTYPE_CROM 0x05 // CD-ROM device +#define DTYPE_SCAN 0x06 // Scanner device +#define DTYPE_OPTI 0x07 // Optical memory device +#define DTYPE_JUKE 0x08 // Medium Changer device +#define DTYPE_COMM 0x09 // Communications device +#define DTYPE_RESL 0x0A // Reserved (low) +#define DTYPE_RESH 0x1E // Reserved (high) +#define DTYPE_UNKNOWN 0x1F // Unknown or no device type +//********************************************************************** +// %%% ANSI APPROVED VERSION DEFINITIONS %%% +//********************************************************************** +#define ANSI_MAYBE 0x0 // Device may or may not be ANSI approved stand +#define ANSI_SCSI1 0x1 // Device complies to ANSI X3.131-1986 (SCSI-1) +#define ANSI_SCSI2 0x2 // Device complies to SCSI-2 +#define ANSI_RESLO 0x3 // Reserved (low) +#define ANSI_RESHI 0x7 // Reserved (high)
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/windac/Winaspi.h b/Src/Plugins/Input/in_cdda/windac/Winaspi.h new file mode 100644 index 00000000..5fc78fe5 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/windac/Winaspi.h @@ -0,0 +1,229 @@ +//********************************************************************** +// +// Name: WINASPI.H +// +// Description: ASPI for Windows definitions ('C' Language) +// +//********************************************************************** + +#ifndef _WINASPI_H +#define _WINASPI_H + +typedef BYTE *LPSRB; +#define SENSE_LEN 14 // Default sense buffer length +#define SRB_DIR_SCSI 0x00 // Direction determined by SCSI command +#define SRB_DIR_IN 0x08 // Transfer from SCSI target to host +#define SRB_DIR_OUT 0x10 // Transfer from host to SCSI targetw +#define SRB_POSTING 0x01 // Enable ASPI posting +#define SRB_EVENT_NOTIFY 0x40 // Enable ASPI command notification +#define SRB_ENABLE_RESIDUAL_COUNT 0x04 //Enable reporting of residual byte count +#define WM_ASPIPOST 0x4D42 // ASPI Post message +#define TIMEOUT 30000 // Wait 30 seconds + +//********************************************************************** +// %%% ASPI Command Definitions %%% +//********************************************************************** +#define SC_HA_INQUIRY 0x00 // Host adapter inquiry +#define SC_GET_DEV_TYPE 0x01 // Get device type +#define SC_EXEC_SCSI_CMD 0x02 // Execute SCSI command +#define SC_ABORT_SRB 0x03 // Abort an SRB +#define SC_RESET_DEV 0x04 // SCSI bus device reset +//********************************************************************** +// %%% SRB Status %%% +//********************************************************************** +#define SS_PENDING 0x00 // SRB being processed +#define SS_COMP 0x01 // SRB completed without error +#define SS_ABORTED 0x02 // SRB aborted +#define SS_ABORT_FAIL 0x03 // Unable to abort SRB +#define SS_ERR 0x04 // SRB completed with error +#define SS_INVALID_CMD 0x80 // Invalid ASPI command +#define SS_INVALID_HA 0x81 // Invalid host adapter number +#define SS_NO_DEVICE 0x82 // SCSI device not installed +#define SS_INVALID_SRB 0xE0 // Invalid parameter set in SRB +#define SS_OLD_MANAGER 0xE1 // ASPI manager doesn't support Window +#define SS_ILLEGAL_MODE 0xE2 // Unsupported Windows mode +#define SS_NO_ASPI 0xE3 // No ASPI managers resident +#define SS_FAILED_INIT 0xE4 // ASPI for windows failed init +#define SS_ASPI_IS_BUSY 0xE5 // No resources available to execute cmd +#define SS_BUFFER_TO_BIG 0xE6 // Buffer size to big to handle! +//********************************************************************** +// %%% Host Adapter Status %%% +//********************************************************************** +#define HASTAT_OK 0x00 // Host adapter did not detect an error +#define HASTAT_SEL_TO 0x11 // Selection Timeout +#define HASTAT_DO_DU 0x12 // Data overrun data underrun +#define HASTAT_BUS_FREE 0x13 // Unexpected bus free +#define HASTAT_PHASE_ERR 0x14 // Target bus phase sequence failure + + + + + +//********************************************************************** +// %%% SRB - HOST ADAPTER INQUIRY - SC_HA_INQUIRY %%% +//********************************************************************** +#pragma pack(push,ASPI_Structures,1) + +typedef BYTE TDriveMode[64]; + +struct THAUnique +{ + WORD BufferAlignmentMask; + BYTE AdapterUniqueFlags; + BYTE MaximumSCSITargets; + DWORD MaximumTransferLen; + BYTE Reserved[8]; +}; + +typedef struct +{ + BYTE SRB_Cmd; // ASPI command code = SC_HA_INQUIRY + BYTE SRB_Status; // ASPI command status byte + BYTE SRB_HaId; // ASPI host adapter number + BYTE SRB_Flags; // ASPI request flags + DWORD SRB_Hdr_Rsvd; // Reserved, MUST = 0 + BYTE HA_Count; // Number of host adapters present + BYTE HA_SCSI_ID; // SCSI ID of host adapter + BYTE HA_ManagerId[16]; // String describing the manager + BYTE HA_Identifier[16]; // String describing the host adapter + THAUnique HA_Unique; // Host Adapter Unique parameters + WORD HA_Rsvd1; +} SRB_HAInquiry, *PSRB_HAInquiry; +//********************************************************************** +// %%% SRB - GET DEVICE TYPE - SC_GET_DEV_TYPE %%% +//********************************************************************** +typedef struct +{ + BYTE SRB_Cmd; // ASPI command code = SC_GET_DEV_TYPE + BYTE SRB_Status; // ASPI command status byte + BYTE SRB_HaId; // ASPI host adapter number + BYTE SRB_Flags; // Reserved + DWORD SRB_Hdr_Rsvd; // Reserved + BYTE SRB_Target; // Target's SCSI ID + BYTE SRB_Lun; // Target's LUN number + BYTE SRB_DeviceType; // Target's peripheral device type + BYTE SRB_Rsvd1; // Reserved for alignment +} SRB_GDEVBlock, *PSRB_GDEVBlock; +//********************************************************************** +// %%% SRB - EXECUTE SCSI COMMAND - SC_EXEC_SCSI_CMD %%% +//********************************************************************** +typedef struct +{ + BYTE SRB_Cmd; // ASPI command code = SC_EXEC_SCSI_CMD + BYTE SRB_Status; // ASPI command status byte + BYTE SRB_HaId; // ASPI host adapter number + BYTE SRB_Flags; // ASPI request flags + DWORD SRB_Hdr_Rsvd; // Reserved + BYTE SRB_Target; // Target's SCSI ID + BYTE SRB_Lun; // Target's LUN number + WORD SRB_Rsvd1; // Reserved for Alignment + DWORD SRB_BufLen; // Data Allocation Length + BYTE *SRB_BufPointer; // Data Buffer Point + BYTE SRB_SenseLen; // Sense Allocation Length + BYTE SRB_CDBLen; // CDB Length + BYTE SRB_HaStat; // Host Adapter Status + BYTE SRB_TargStat; // Target Status + void (*SRB_PostProc)(); // Post routine + void *SRB_Rsvd2; // Reserved + BYTE SRB_Rsvd3[16]; // Reserved for expansion + BYTE CDBByte[16]; // SCSI CDB + BYTE SenseArea[SENSE_LEN+2]; // Request Sense buffer +} SRB_ExecSCSICmd, *PSRB_ExecSCSICmd; +//********************************************************************** +// %%% SRB - ABORT AN SRB - SC_ABORT_SRB %%% +//********************************************************************** +typedef struct +{ + BYTE SRB_Cmd; // ASPI command code = SC_ABORT_SRB + BYTE SRB_Status; // ASPI command status byte + BYTE SRB_HaId; // ASPI host adapter number + BYTE SRB_Flags; // ASPI request flags + DWORD SRB_Hdr_Rsvd; // Reserved, MUST = 0 + LPSRB SRB_ToAbort; // Pointer to SRB to abort +} SRB_Abort; +//********************************************************************** +// %%% SRB - BUS DEVICE RESET - SC_RESET_DEV %%% +//********************************************************************** +typedef struct +{ + BYTE SRB_Cmd; // ASPI command code = SC_RESET_DEV + BYTE SRB_Status; // ASPI command status byte + BYTE SRB_HaId; // ASPI host adapter number + BYTE SRB_Flags; // Reserved + DWORD SRB_Hdr_Rsvd; // Reserved + BYTE SRB_Target; // Target's SCSI ID + BYTE SRB_Lun; // Target's LUN number + BYTE SRB_Rsvd1[12]; // Reserved for Alignment + BYTE SRB_HaStat; // Host Adapter Status + BYTE SRB_TargStat; // Target Status + void *SRB_PostProc; // Post routine + void *SRB_Rsvd2; // Reserved + BYTE SRB_Rsvd3[32]; // Reserved +} SRB_BusDeviceReset, *PSRB_BusDeviceReset; + + +//********************************************************************** +// %%% Header for TOC Reading %%% +//********************************************************************** +struct TTrackInfo +{ + BYTE Reserved1; + BYTE AdrCtrl; + BYTE TrackNummer; + BYTE Reserved2; + DWORD AbsCDAdress; +}; + +struct TTOCHeader +{ + WORD TOCDataLength; + BYTE FirstTrack; + BYTE LastTrack; + TTrackInfo Info[100]; +}; + +//********************************************************************** +// %%% Structure for Read Sub-Channel %%% +//********************************************************************** +struct TQChannelInfo +{ + BYTE Reserved1; + BYTE AudioStatus; + WORD DataLen; + BYTE FormatCode; + BYTE ADRCtrl; + BYTE TrackNumber; + BYTE IndexNumber; + long AbsCDAdress; + long RelTrackAdress; +}; + +//********************************************************************** +// %%% Request Sense Data Format %%% +//********************************************************************** +typedef struct { + BYTE ErrorCode; // Error Code (70H or 71H) + BYTE SegmentNum; // Number of current segment descriptor + BYTE SenseKey; // Sense Key(See bit definitions too) + BYTE InfoByte0; // Information MSB + BYTE InfoByte1; // Information MID + BYTE InfoByte2; // Information MID + BYTE InfoByte3; // Information LSB + BYTE AddSenLen; // Additional Sense Length + BYTE ComSpecInf0; // Command Specific Information MSB + BYTE ComSpecInf1; // Command Specific Information MID + BYTE ComSpecInf2; // Command Specific Information MID + BYTE ComSpecInf3; // Command Specific Information LSB + BYTE AddSenseCode; // Additional Sense Code + BYTE AddSenQual; // Additional Sense Code Qualifier + BYTE FieldRepUCode; // Field Replaceable Unit Code + BYTE SenKeySpec15; // Sense Key Specific 15th byte + BYTE SenKeySpec16; // Sense Key Specific 16th byte + BYTE SenKeySpec17; // Sense Key Specific 17th byte + BYTE AddSenseBytes; // Additional Sense Bytes +} TSenseInfo; + +#pragma pack(pop,ASPI_Structures) + + +#endif //_WINASPI_H
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/workorder.cpp b/Src/Plugins/Input/in_cdda/workorder.cpp new file mode 100644 index 00000000..b5f8e8e9 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/workorder.cpp @@ -0,0 +1,53 @@ +#include "workorder.h" +#include "main.h" +#include "cddb.h" +#include <shlwapi.h> +CDDBModuleWorkOrderManagerInterface *workorder=0; +static HMODULE musicIDLib = 0; + +void OpenMusicIDWorkOrder() +{ + if (!workorder) + { + char pluginpath[MAX_PATH] = {0}; + GetModuleFileNameA(line.hDllInstance, pluginpath, MAX_PATH); + PathRemoveFileSpecA(pluginpath); + PathAppendA(pluginpath, "Gracenote"); + + char musicidpath[MAX_PATH] = {0}; + PathCombineA(musicidpath, pluginpath, "CddbWOManagerWinamp.dll"); + + musicIDLib = LoadLibraryA(musicidpath); + if (musicIDLib) + { + CDDBModuleQueryInterfaceFunc qi = (CDDBModuleQueryInterfaceFunc)GetProcAddress(musicIDLib, "CDDBModuleQueryInterface"); + if (qi) + { + ICDDBControl *pControl; + Cddb_GetIControl((void**)&pControl); + workorder = (CDDBModuleWorkOrderManagerInterface*)qi("workordermanager"); + if (!(workorder && workorder->base.version == CDDBMODULE_VERSION && workorder->version == CDDBMODULE_WORKORDER_MGR_VERSION + && workorder->base.Init && workorder->base.Init(0) + && workorder->Initialize(pControl, pluginpath) == 0)) + { + workorder = 0; + FreeLibrary(musicIDLib); + musicIDLib=0; + } + if (pControl) pControl->Release(); + } + } + } +} + +void ShutdownMusicIDWorkOrder() +{ + if (workorder) + workorder->Shutdown(); + + workorder=0; + + if (musicIDLib) + FreeLibrary(musicIDLib); + musicIDLib=0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_cdda/workorder.h b/Src/Plugins/Input/in_cdda/workorder.h new file mode 100644 index 00000000..664e77fa --- /dev/null +++ b/Src/Plugins/Input/in_cdda/workorder.h @@ -0,0 +1,10 @@ +#ifndef NULLSOFT_IN_CDDA_WORKORDER_H +#define NULLSOFT_IN_CDDA_WORKORDER_H + +#include "../gracenote/CDDBPluginWorkOrderManager.h" + +extern CDDBModuleWorkOrderManagerInterface *workorder; +void OpenMusicIDWorkOrder(); +void ShutdownMusicIDWorkOrder(); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_dshow/CSampleCB.h b/Src/Plugins/Input/in_dshow/CSampleCB.h new file mode 100644 index 00000000..b8c7cd02 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/CSampleCB.h @@ -0,0 +1,11 @@ +#ifndef NULLSOFT_IN_DSHOW_CSAMPLECB_H +#define NULLSOFT_IN_DSHOW_CSAMPLECB_H + +class CSampleCB +{ +public: + virtual void sample_cb(LONGLONG starttime, LONGLONG endtime, IMediaSample *pSample) { } + virtual void endofstream() { }} +; + +#endif diff --git a/Src/Plugins/Input/in_dshow/CWAAudioRenderer.cpp b/Src/Plugins/Input/in_dshow/CWAAudioRenderer.cpp new file mode 100644 index 00000000..fd59f733 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/CWAAudioRenderer.cpp @@ -0,0 +1,70 @@ +#include "CWAAudioRenderer.h" + + +CWAAudioRenderer::CWAAudioRenderer() + : CAudioSwitchRenderer(CLSID_WAAudioRend, TEXT("WAAudioRenderer"), NULL, NULL)//, + //CBaseReferenceClock(TEXT("WARefClock"), NULL, NULL, NULL) +{ + m_callback = NULL; +} + +CWAAudioRenderer::~CWAAudioRenderer() +{ + delete(m_callback); +} + +HRESULT CWAAudioRenderer::DoRenderSample(IMediaSample *pMediaSample) +{ + if (m_callback) + { + REFERENCE_TIME StartTime, StopTime; + pMediaSample->GetTime( &StartTime, &StopTime); + StartTime += m_pInputPin[m_inputSelected]->CurrentStartTime( ); + StopTime += m_pInputPin[m_inputSelected]->CurrentStartTime( ); + m_callback->sample_cb(StartTime, StopTime, pMediaSample); + } + return NOERROR; +} + +HRESULT CWAAudioRenderer::CheckMediaType(const CMediaType *pmt) +{ + if (pmt->majortype != MEDIATYPE_Audio) return E_INVALIDARG; + if (pmt->formattype != FORMAT_WaveFormatEx) return E_INVALIDARG; + if (pmt->subtype != MEDIASUBTYPE_PCM && pmt->subtype != MEDIASUBTYPE_IEEE_FLOAT) return E_INVALIDARG; + return NOERROR; +} + +HRESULT CWAAudioRenderer::SetMediaType(const CMediaType *pmt) +{ + m_mt = *pmt; + return NOERROR; +} + +CMediaType *CWAAudioRenderer::GetAcceptedType() +{ + return &m_mt; +} + +HRESULT CWAAudioRenderer::SetCallback(CSampleCB *Callback) +{ + m_callback = Callback; + return NOERROR; +} + + +HRESULT CWAAudioRenderer::EndOfStream(void) +{ + if (m_callback) + m_callback->endofstream(); + return CAudioSwitchRenderer::EndOfStream(); +} + +HRESULT CWAAudioRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample, REFERENCE_TIME *pStartTime, REFERENCE_TIME *pEndTime) +{ + return S_OK; +} + +HRESULT CWAAudioRenderer::GetSampleTimes(IMediaSample *pMediaSample, REFERENCE_TIME *pStartTime, REFERENCE_TIME *pEndTime) +{ + return S_OK; +} diff --git a/Src/Plugins/Input/in_dshow/CWAAudioRenderer.h b/Src/Plugins/Input/in_dshow/CWAAudioRenderer.h new file mode 100644 index 00000000..5bfb9f72 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/CWAAudioRenderer.h @@ -0,0 +1,30 @@ +#ifndef NULLSOFT_IN_DSHOW_CWAAUDIORENDERER_H +#define NULLSOFT_IN_DSHOW_CWAAUDIORENDERER_H + +#include "audioswitch.h" +#include "CSampleCB.h" + +EXTERN_C GUID DECLSPEC_SELECTANY CLSID_WAAudioRend = +{ 0x2fa4f053, 0x6d60, 0x4cb0, {0x95, 0x3, 0x8e, 0x89, 0x23, 0x4f, 0xcb, 0xca}}; + + + +class CWAAudioRenderer : public CAudioSwitchRenderer//, CBaseReferenceClock +{ +public: + CWAAudioRenderer(); + virtual ~CWAAudioRenderer(); + HRESULT DoRenderSample(IMediaSample *pMediaSample) ; + HRESULT CheckMediaType(const CMediaType *pmt); + HRESULT SetMediaType(const CMediaType *pmt); + CMediaType *GetAcceptedType(); + HRESULT SetCallback(CSampleCB *Callback); + HRESULT EndOfStream(void); + HRESULT ShouldDrawSampleNow(IMediaSample *pMediaSample, REFERENCE_TIME *pStartTime, REFERENCE_TIME *pEndTime); + HRESULT GetSampleTimes(IMediaSample *pMediaSample, REFERENCE_TIME *pStartTime, REFERENCE_TIME *pEndTime); + +private: + CSampleCB *m_callback; + CMediaType m_mt; +}; +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_dshow/CWAVideoRenderer.cpp b/Src/Plugins/Input/in_dshow/CWAVideoRenderer.cpp new file mode 100644 index 00000000..40065a3d --- /dev/null +++ b/Src/Plugins/Input/in_dshow/CWAVideoRenderer.cpp @@ -0,0 +1,131 @@ +#include "CWAVideoRenderer.h" +#include "Main.h" + +char *toChar(const GUID &guid, char *target) +{ + // {1B3CA60C-DA98-4826-B4A9-D79748A5FD73} + StringCchPrintfA(target, 39, "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", + (int)guid.Data1, (int)guid.Data2, (int)guid.Data3, + (int)guid.Data4[0], (int)guid.Data4[1], + (int)guid.Data4[2], (int)guid.Data4[3], + (int)guid.Data4[4], (int)guid.Data4[5], + (int)guid.Data4[6], (int)guid.Data4[7] ); + + return target; +} + +CWAVideoRenderer::CWAVideoRenderer() + : CBaseRenderer(CLSID_WAVideoRend, TEXT("WAVideoRenderer"), NULL, NULL) +{ + m_callback = NULL; + m_reent = 0; +} + +CWAVideoRenderer::~CWAVideoRenderer() +{ + delete m_callback; +} + +HRESULT CWAVideoRenderer::DoRenderSample(IMediaSample *pMediaSample) +{ + if (m_callback) + { + REFERENCE_TIME StartTime, StopTime; + pMediaSample->GetTime( &StartTime, &StopTime); + StartTime += m_pInputPin->CurrentStartTime( ); + StopTime += m_pInputPin->CurrentStartTime( ); + m_callback->sample_cb(StartTime, StopTime, pMediaSample); + } + return NOERROR; +} + +HRESULT CWAVideoRenderer::CheckMediaType(const CMediaType *pmt) +{ + if (pmt->majortype != MEDIATYPE_Video) return E_INVALIDARG; + if (pmt->formattype != FORMAT_VideoInfo && pmt->formattype != FORMAT_VideoInfo2) return E_INVALIDARG; +#if 0 + { + char blah[512] = {0}; + toChar(pmt->subtype, blah); + OutputDebugString(blah); + } +#endif + if (pmt->subtype != MEDIASUBTYPE_YUY2 && + pmt->subtype != MEDIASUBTYPE_YV12 && + pmt->subtype != MEDIASUBTYPE_RGB32 && + pmt->subtype != MEDIASUBTYPE_RGB24 && + pmt->subtype != MEDIASUBTYPE_RGB8 /* && + pmt->subtype!=MEDIASUBTYPE_YVYU*/) return E_INVALIDARG; + return NOERROR; +} + +HRESULT CWAVideoRenderer::SetMediaType(const CMediaType *pmt) +{ + m_mt = *pmt; + + //fix for upside down videos + if (!m_reent && (pmt->subtype == MEDIASUBTYPE_YUY2 || pmt->subtype == MEDIASUBTYPE_YV12) ) + { + m_reent = 1; + IPin *p = m_pInputPin->GetConnected(); + p->Disconnect(); + m_pInputPin->Disconnect(); + if (pmt->formattype == FORMAT_VideoInfo) + { + VIDEOINFOHEADER *pHeader = (VIDEOINFOHEADER*)pmt->pbFormat; + pHeader->bmiHeader.biHeight = -pHeader->bmiHeader.biHeight; + } + else + { + VIDEOINFOHEADER2 *pHeader = (VIDEOINFOHEADER2*)pmt->pbFormat; + pHeader->bmiHeader.biHeight = -pHeader->bmiHeader.biHeight; + } + if (p->Connect(m_pInputPin, pmt)) + { + //oops it failed (like with MJPEG decompressor) so lets get it back to normal + if (pmt->formattype == FORMAT_VideoInfo) + { + VIDEOINFOHEADER *pHeader = (VIDEOINFOHEADER*)pmt->pbFormat; + pHeader->bmiHeader.biHeight = -pHeader->bmiHeader.biHeight; + } + else + { + VIDEOINFOHEADER2 *pHeader = (VIDEOINFOHEADER2*)pmt->pbFormat; + pHeader->bmiHeader.biHeight = -pHeader->bmiHeader.biHeight; + } + p->Connect(m_pInputPin, pmt); + } + m_reent = 0; + } + return NOERROR; +} +// GUID GetAcceptedType() { +CMediaType *CWAVideoRenderer::GetAcceptedType() +{ + return &m_mt; + // return m_mt.subtype; +} + +HRESULT CWAVideoRenderer::SetCallback(CSampleCB *Callback) +{ + m_callback = Callback; + return NOERROR; +} + +HRESULT CWAVideoRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample, REFERENCE_TIME *pStartTime, REFERENCE_TIME *pEndTime) +{ + return S_OK; +} + +HRESULT CWAVideoRenderer::GetSampleTimes(IMediaSample *pMediaSample, REFERENCE_TIME *pStartTime, REFERENCE_TIME *pEndTime) +{ + return S_OK; +} + +HRESULT CWAVideoRenderer::EndOfStream(void) +{ + if (m_callback) + m_callback->endofstream(); + return CBaseRenderer::EndOfStream(); +} + diff --git a/Src/Plugins/Input/in_dshow/CWAVideoRenderer.h b/Src/Plugins/Input/in_dshow/CWAVideoRenderer.h new file mode 100644 index 00000000..cb029c50 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/CWAVideoRenderer.h @@ -0,0 +1,32 @@ +#ifndef NULLSOFT_IN_DSHOW_CWAVIDEORENDERERH +#define NULLSOFT_IN_DSHOW_CWAVIDEORENDERERH + +#include "audioswitch.h" +#include "CSampleCB.h" + + +EXTERN_C GUID DECLSPEC_SELECTANY CLSID_WAVideoRend = + {0x2fa4f053, 0x6d60, 0x4cb0, {0x95, 0x3, 0x8e, 0x89, 0x23, 0x4f, 0xca, 0xca}}; + + +class CWAVideoRenderer : public CBaseRenderer +{ +public: + CWAVideoRenderer(); + virtual ~CWAVideoRenderer(); + HRESULT DoRenderSample(IMediaSample *pMediaSample); + HRESULT CheckMediaType(const CMediaType *pmt); + HRESULT SetMediaType(const CMediaType *pmt); + CMediaType *GetAcceptedType(); + HRESULT SetCallback(CSampleCB *Callback); + HRESULT ShouldDrawSampleNow(IMediaSample *pMediaSample, REFERENCE_TIME *pStartTime, REFERENCE_TIME *pEndTime); + HRESULT GetSampleTimes(IMediaSample *pMediaSample, REFERENCE_TIME *pStartTime, REFERENCE_TIME *pEndTime); + HRESULT EndOfStream(void); + +private: + CSampleCB *m_callback; + CMediaType m_mt; + int m_reent; +}; + +#endif diff --git a/Src/Plugins/Input/in_dshow/DSTrackSelector.cpp b/Src/Plugins/Input/in_dshow/DSTrackSelector.cpp new file mode 100644 index 00000000..c033b682 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/DSTrackSelector.cpp @@ -0,0 +1,89 @@ +#include "DSTrackSelector.h" +#include "main.h" +#include "CWAAudioRenderer.h" +#include "../Agave/Language/api_language.h" +#include "resource.h" + +int DSTrackSelector::getNumAudioTracks() +{ + if (nullfilter) + return nullfilter->GetConnectedInputsCount(); + return 1; +} + +void DSTrackSelector::enumAudioTrackName(int n, char *buf, int size) +{ + char t[256] = {0}; + StringCchPrintfA(t, 256, WASABI_API_LNGSTRING(IDS_TRACK_X), n); + int l = min(size, 255); + lstrcpynA((char *)buf, t, l); +} + +int DSTrackSelector::getCurAudioTrack() +{ + if (nullfilter) + return nullfilter->GetSelectedInput(); + return 0; +} + +int DSTrackSelector::getNumVideoTracks() +{ + return 1; +} + +void DSTrackSelector::enumVideoTrackName(int n, char *buf, int size) +{ + WASABI_API_LNGSTRING_BUF(IDS_TRACK_1,buf,min(7, size)); +} + +int DSTrackSelector::getCurVideoTrack() +{ + return 0; +} + +void DSTrackSelector::setAudioTrack(int n) +{ + if (nullfilter) + { + CComPtr<IMediaPosition> pMediaPosition = NULL; + pGraphBuilder->QueryInterface(IID_IMediaPosition, (void **)&pMediaPosition); + pMediaControl->Pause(); + REFTIME pos; + pMediaPosition->get_CurrentPosition(&pos); + nullfilter->SetSelectedInput(n); + { + CMediaType *mt = nullfilter->GetAcceptedType(); + if (mt->subtype != MEDIASUBTYPE_PCM) + has_audio = NULL; + else + { + WAVEFORMATEX *pHeader = (WAVEFORMATEX*)mt->pbFormat; + // reget this cause this is the real UNCOMPRESSED format + audio_bps = pHeader->wBitsPerSample; + audio_srate = pHeader->nSamplesPerSec; + audio_nch = pHeader->nChannels; + if (audio_bps == 32 || audio_bps == 64) + { + m_float = 1; + m_src_bps = audio_bps; + audio_bps = 16; //TODO: read bits from AGAVE_API_CONFIG :) + } + } + mod.outMod->Close(); + int maxlat = mod.outMod->Open(audio_srate, audio_nch, audio_bps, -1, -1); + if (maxlat < 0) + { + releaseObjects(); + return ; + } + mod.SetInfo(m_bitrate, audio_srate / 1000, audio_nch, 1); + mod.SAVSAInit(maxlat, audio_srate); + mod.VSASetInfo(audio_srate, audio_nch); + mod.outMod->SetVolume( -666); + } + pMediaPosition->put_CurrentPosition(pos); + pMediaControl->Run(); + } +} + +void DSTrackSelector::setVideoTrack(int n){}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_dshow/DSTrackSelector.h b/Src/Plugins/Input/in_dshow/DSTrackSelector.h new file mode 100644 index 00000000..6d6166d5 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/DSTrackSelector.h @@ -0,0 +1,18 @@ +#ifndef NULLSOFT_IN_DSHOW_DSTRACKSELECTOR_H +#define NULLSOFT_IN_DSHOW_DSTRACKSELECTOR_H + +#include "../Winamp/wa_ipc.h" + +class DSTrackSelector : public ITrackSelector { +public: + virtual int getNumAudioTracks(); + virtual void enumAudioTrackName(int n, char *buf, int size); + virtual int getCurAudioTrack(); + virtual int getNumVideoTracks(); + virtual void enumVideoTrackName(int n, char *buf, int size); + virtual int getCurVideoTrack(); + virtual void setAudioTrack(int n); + virtual void setVideoTrack(int n); +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_dshow/Header.cpp b/Src/Plugins/Input/in_dshow/Header.cpp new file mode 100644 index 00000000..fd073dd3 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/Header.cpp @@ -0,0 +1,49 @@ +#include "Header.h" +#include <windows.h> +#include "header_avi.h" +#include "header_mpg.h" +#include "header_asf.h" +#include "header_wav.h" + +extern const wchar_t *extension(const wchar_t *fn); + +Header *MakeHeader(const wchar_t *filename, bool metadata) +{ + const wchar_t *ext=extension(filename); + + Header *header=0; + if(!_wcsicmp(ext, L"asf") || !_wcsicmp(ext, L"wmv") || !_wcsicmp(ext, L"wma")) + header = new HeaderAsf(); + else if(!_wcsicmp(ext, L"avi") || !_wcsicmp(ext, L"divx")) + header = new HeaderAvi(); + else if(!_wcsicmp(ext, L"mpg") || !_wcsicmp(ext, L"mpeg")) + header = new HeaderMpg(); + else if (!_wcsicmp(ext, L"wav")) + header = new HeaderWav(); + + if (header && !header->getInfos(filename, metadata)) + { + delete header; + return 0; + } + return header; +} + +Header::Header() +{ + length = -1; + has_audio = has_video = false; + title = artist = comment = genre = album = composer = publisher = 0; + audio_nch = audio_bps = audio_srate = video_w = video_h = 0; +} + +Header::~Header() +{ + free(title); + free(artist); + free(comment); + free(genre); + free(album); + free(composer); + free(publisher); +} diff --git a/Src/Plugins/Input/in_dshow/Header.h b/Src/Plugins/Input/in_dshow/Header.h new file mode 100644 index 00000000..c9cdefaf --- /dev/null +++ b/Src/Plugins/Input/in_dshow/Header.h @@ -0,0 +1,22 @@ +#ifndef NULLSOFT_IN_DSHOW_HEADER_H +#define NULLSOFT_IN_DSHOW_HEADER_H +#include <wchar.h> +class Header +{ +public: + Header(); + virtual ~Header(); + + virtual int getInfos(const wchar_t *filename, bool checkMetadata)=0; + + // benski> this is a shitty way to do it, but there's too much code I'd have to change + wchar_t *title, *artist, *comment, *genre, *album, *composer, *publisher; + bool has_audio, has_video; + int length; + int audio_nch, audio_bps, audio_srate; + int video_w, video_h; +}; + +Header *MakeHeader(const wchar_t *filename, bool metadata); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_dshow/IN2.H b/Src/Plugins/Input/in_dshow/IN2.H new file mode 100644 index 00000000..b54e067c --- /dev/null +++ b/Src/Plugins/Input/in_dshow/IN2.H @@ -0,0 +1 @@ +#include "../Winamp/in2.h"
\ No newline at end of file diff --git a/Src/Plugins/Input/in_dshow/Main.cpp b/Src/Plugins/Input/in_dshow/Main.cpp new file mode 100644 index 00000000..44ffd5aa --- /dev/null +++ b/Src/Plugins/Input/in_dshow/Main.cpp @@ -0,0 +1,1575 @@ +//#define PLUGIN_NAME "Nullsoft DirectShow Decoder" +#define PLUGIN_VERSION L"1.15" + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <math.h> +#include <assert.h> +#include "../Agave/Language/api_language.h" +#include <api/service/waServiceFactory.h> +#include "resource.h" +#include "../nu/ns_wc.h" + +#define IPC_GETINIFILE 334 // returns a pointer to winamp.ini +#define WM_WA_IPC WM_USER + +#define VIDUSER_SET_TRACKSELINTERFACE 0x1003 // give your ITrackSelector interface as param2 +#define VIDUSER_SET_INFOSTRING 0x1000 + +#define DEFGUID 1 + +#include <AtlBase.h> +//#include <streams.h> +//#include <qedit.h> +#include <qnetwork.h> +#ifdef DEFGUID +#include <initguid.h> // declares DEFINE_GUID to declare an EXTERN_C const. +#endif + +#include "main.h" + +#include "CWAAudioRenderer.h" +#include "CWAVideoRenderer.h" +#include "header_asf.h" +#include "header_avi.h" +#include "header_mpg.h" +#include "header_wav.h" +#include "../Winamp/wa_ipc.h" +#include "../nsutil/pcm.h" + +static Header *infoHeader=0; +wchar_t *infoFn=0; + +DEFINE_GUID(IID_IAMNetworkStatus,0xFA2AA8F3L,0x8B62,0x11D0,0xA5,0x20,0x00,0x00,0x00,0x00,0x00,0x00); + +// post this to the main window at end of file (after playback as stopped) +#define WM_WA_MPEG_EOF WM_USER+2 +#define IPC_GET_IVIDEOOUTPUT 500 +#define VIDEO_MAKETYPE(A,B,C,D) ((A) | ((B)<<8) | ((C)<<16) | ((D)<<24)) +#define VIDUSER_SET_VFLIP 0x1002 + +// wasabi based services for localisation support +api_language *WASABI_API_LNG = 0; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; +HINSTANCE g_hInstance=0; + +BOOL APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID) +{ + if (dwReason == DLL_PROCESS_ATTACH) + { + g_hInstance=hInstance; + } + return TRUE; + +} + +int GetFileLength(const wchar_t *filename) +{ + static wchar_t fn[MAX_PATH*4] = L""; + static int l = -1; + if(!_wcsicmp(filename,fn)) return l; + //return -1000; + + CComPtr<IGraphBuilder> graph; + graph.CoCreateInstance(CLSID_FilterGraph); + if (graph) + { + HRESULT hr; + try + { + hr = graph->RenderFile(filename,NULL); + } + catch (...) + { + return -1000; + } + if (hr == S_OK) + { + CComPtr<IMediaPosition> pMediaPosition; + graph->QueryInterface(IID_IMediaPosition, (void **)&pMediaPosition); + if (pMediaPosition) + { + REFTIME length; + pMediaPosition->get_Duration(&length); + lstrcpynW(fn,filename,sizeof(fn)/sizeof(wchar_t)); + l = (int)(length*1000.0); + return (int)(length*1000.0); + } + } + } + return -1000; +} + +wchar_t lastfn[MAX_PATH] = {0}; // currently playing file (used for getting info on the current file) + +int file_length; // file length, in bytes +int decode_pos_ms; // current decoding position, in milliseconds. +// Used for correcting DSP plug-in pitch changes +int paused; // are we paused? +volatile int seek_needed; // if != -1, it is the point that the decode +// thread should seek to, in ms. + +HANDLE input_file=INVALID_HANDLE_VALUE; // input file handle + +volatile int killDecodeThread=0; // the kill switch for the decode thread +HANDLE thread_handle=INVALID_HANDLE_VALUE; // the handle to the decode thread + +const char *INI_FILE; + +wchar_t m_lastfn[2048] = {0}; // currently playing file (used for getting info on the current file) +wchar_t m_status[512] = {0}; +DWORD m_laststatus; + +int m_bitrate=0; + +//config.cpp +extern void doConfig(HINSTANCE hInstance, HWND hwndParent); +extern void config_read(); +extern char *getfileextensions(); +extern void getInfo(const wchar_t *fn, wchar_t *linetext, int linetextCch, wchar_t *fulltext, int fulltextCch, int *bitrate, int *channel); + +//info.cpp +extern void doInfo(HINSTANCE hInstance, HWND hwndParent, const wchar_t *fn); + +int getoutputtime(); +void releaseObjects(); + +IVideoOutput *m_video_output; + +IGraphBuilder *pGraphBuilder=0; +static ICaptureGraphBuilder2 *pCapture=0; +static IBaseFilter *pNullFilter2=0; +static IMediaEvent *pMediaEventEx=0; +IMediaControl *pMediaControl=0; +static IMediaSeeking *pMediaSeeking=0; + +static IBaseFilter *pCapVidSrcFilter=0; +static IBaseFilter *pCapAudSrcFilter=0; + +bool has_audio, has_video, has_palette; +int video_mediatype; +int video_w,video_h,video_len; +int audio_bps, audio_srate, audio_nch; +RGBQUAD palette[0x100] = {0}; // for RGB8 +int m_length=-1; +int g_quit; +DWORD m_starttime,m_time_paused; +unsigned int m_nbframes; +DWORD m_avgfps_start; +int m_float, m_src_bps; +#ifdef IN_DSHOW_CAPTURE_SUPPORT // disable capture, for now +int m_is_capture; +#endif +HWND m_notif_hwnd; + +ITrackSelector *pTrackSelector = NULL; + +static int doingaudioshit=0; + +bool s_using_dsr = false; + +wchar_t lastfn_status[256]=L""; +static LONG_PTR m_buffering=0; +int g_bufferstat; + +//capture stuff +#include "../nsv/nsvbs.h" + +nsv_InBS g_video_refs; +#define G_MAX_FREE_FRAMES 64 +void *g_free_frames[G_MAX_FREE_FRAMES]; +int g_num_free_frames; + +#define PA_CLIP_( val, min, max )\ + { val = ((val) < (min)) ? (min) : (((val) > (max)) ? (max) : (val)); } + +#if !defined(__alpha) && !defined(_WIN64) +static __inline long float_to_long(double t) +{ + long r; + __asm fld t + __asm fistp r + return r; +} +#else +#define float_to_long(x) ((long)( x )) +#endif + +inline static void clip(double &x, double a, double b) +{ + double x1 = fabs(x - a); + double x2 = fabs(x - b); + x = x1 + (a + b); + x -= x2; + x *= 0.5; +} + +template <typename FLOAT_T> +void Float32_To_Int24_Clip(void *destinationBuffer, FLOAT_T *sourceBuffer, unsigned int count, double gain) +{ + FLOAT_T *src = sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + + gain*=65536. * 32768.0; + while (count--) + { + /* convert to 32 bit and drop the low 8 bits */ + double scaled = *src * gain; + clip(scaled, -2147483648., 2147483647.); + signed long temp = (signed long) scaled; + + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 24); + + src++; + dest += 3; + } +} + +template <typename FLOAT_T> +void Float32_To_Int16_Clip(void *destinationBuffer, FLOAT_T *sourceBuffer, unsigned int count, double gain) +{ + FLOAT_T *src = sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + gain*=32768.0; + while (count--) + { + long samp = float_to_long((*src) * gain/* - 0.5*/); + + PA_CLIP_(samp, -0x8000, 0x7FFF); + *dest = (signed short) samp; + + src ++; + dest ++; + } +} + + +class CAudioGrab : public CSampleCB +{ +public: + CAudioGrab() { } + + void sample_cb(LONGLONG starttime, LONGLONG endtime, IMediaSample *pSample) + { + if (g_quit) return; + doingaudioshit=1; + int l=pSample->GetActualDataLength(); + if (l) + { + unsigned char *b=NULL; + pSample->GetPointer(&b); + double t=(double)(starttime)/10000; + decode_pos_ms=(int)t; //it's a fix so we stay in sync according to what DirectShit is sending us + if (g_quit) + { + doingaudioshit=0; + return; + } + + //convert IEEE Floats to PCM + if (m_float) + { + if (m_src_bps==32) + { + l/=sizeof(float); + nsutil_pcm_FloatToInt_Interleaved(b, (const float *)b, audio_bps, l); + l *= (audio_bps/8); + } + else if (m_src_bps==64) + { + l/=sizeof(double); + switch (audio_bps) + { + case 16: + Float32_To_Int16_Clip(b, (double *)b, l, 1.0); + l*=2; + break; + case 24: + Float32_To_Int24_Clip(b, (double *)b, l, 1.0); + l*=3; + break; + } + } + } + + { + int len=l; + int s=576*audio_nch*(audio_bps/8); + while (len>0&&!g_quit) + { + if (len>=s) + { + mod.SAAddPCMData(b,audio_nch,audio_bps,decode_pos_ms); + mod.VSAAddPCMData(b,audio_nch,audio_bps,decode_pos_ms); + } + int l=min(s,len); + if (mod.dsp_isactive()) + { + char *sample_buffer = (char *)alloca(l*2); + memcpy(sample_buffer,b,l); + int l2=mod.dsp_dosamples((short *)sample_buffer,l/audio_nch/(audio_bps/8),audio_bps,audio_nch,audio_srate)*(audio_nch*(audio_bps/8)); + while (mod.outMod->CanWrite()<l2 && !g_quit) Sleep(10); + if (g_quit) + { + doingaudioshit=0; + return; + } + mod.outMod->Write(sample_buffer,l2); + } + else + { + while (mod.outMod->CanWrite()<l && !g_quit) Sleep(10); + if (g_quit) + { + doingaudioshit=0; + return; + } + mod.outMod->Write((char *)b,l); + } + //FUCKO:this is clearly having a precision problem + decode_pos_ms+=((l/audio_nch/(audio_bps/8))*1000)/audio_srate; + len-=l; + b+=l; + } + } + } + doingaudioshit=0; + } + void endofstream() + { + while (!g_quit && mod.outMod->IsPlaying()) Sleep(10); + PostMessage(mod.hMainWindow,WM_WA_MPEG_EOF,0,0); + } +private: +}; + +CWAAudioRenderer *nullfilter; +CWAVideoRenderer *nullfilter2; + +#include "DSTrackSelector.h" +//#define DEBUGVIDEO +#ifdef DEBUGVIDEO +void outputDebugStr(char *str) +{ + FILE *fh=fopen("c:\\dshow_dbg.log","at"); + fprintf(fh,"%s",str); + fclose(fh); +} +#endif + +class CVideoGrab : public CSampleCB +{ +public: + CVideoGrab() {} + void sample_cb(LONGLONG starttime, LONGLONG endtime, IMediaSample *pSample) + { + m_nbframes++; + if (!m_avgfps_start) m_avgfps_start=(DWORD)GetTickCount64(); + + if ((DWORD)GetTickCount64()-m_laststatus>500) + { + DWORD t= (DWORD)GetTickCount64()-m_avgfps_start; + if (t) + { + wchar_t text[512] = {0}; + StringCchPrintfW(text,512,L"%s %.02f%s",m_status,(double)m_nbframes*1000/t,WASABI_API_LNGSTRINGW(IDS_FPS)); + m_video_output->extended(VIDUSER_SET_INFOSTRINGW,(INT_PTR)text,0); + m_laststatus= (DWORD)GetTickCount64(); + } + } + + if (g_quit) return; + unsigned char *b=NULL; + pSample->GetPointer(&b); + //wait for the right time + int evtime=(int)(starttime/10000); + +#ifdef IN_DSHOW_CAPTURE_SUPPORT // disable capture, for now + if (m_is_capture) + { + //capture shit + + //check if frame to display + for (;g_video_refs.avail()>=64;) + { + int *ptr = (int *)g_video_refs.getcurbyteptr(); + if (getoutputtime()<ptr[0]) break; + //display it + void *b=(void*)ptr[1]; + if (video_mediatype==VIDEO_MAKETYPE('Y','V','1','2')) + { + // YV12 needs to pass a YV12_PLANES structure. + static YV12_PLANES yv12planes; + int s=video_h*video_w; + yv12planes.y.baseAddr=(unsigned char *)b; + yv12planes.y.rowBytes=video_w; + yv12planes.v.baseAddr=(unsigned char *)b+s; + yv12planes.v.rowBytes=video_w/2; + yv12planes.u.baseAddr=(unsigned char *)b+(s*5)/4; + yv12planes.u.rowBytes=video_w/2; + m_video_output->draw((void *)&yv12planes); + } + else + m_video_output->draw(b); + + //free frame + if (g_num_free_frames < G_MAX_FREE_FRAMES) + { + g_free_frames[g_num_free_frames++]=b; + } + else + { + free(b); + } + + g_video_refs.seek(64); + g_video_refs.compact(); + } + + //store the frame in buffer + + //alloc frame + int len=pSample->GetActualDataLength(); + if (!len) //very unlikely but oh well... + { + int s=4; + switch (video_mediatype) + { + case VIDEO_MAKETYPE('Y','U','Y','2'): + case VIDEO_MAKETYPE('Y','V','Y','U'): + case VIDEO_MAKETYPE('R','G','1','6'): + s=2; + break; + case VIDEO_MAKETYPE('R','G','2','4'): + s=3; + break; + } + len=video_w*video_h*s; + } + + void *t; + if (g_num_free_frames) + { + t=g_free_frames[--g_num_free_frames]; + g_free_frames[g_num_free_frames]=0; + } + else + t=malloc(len); + + memcpy(t,b,len); + + g_video_refs.add(&evtime,4); + g_video_refs.add(&t,4); + return; + } + +#endif + if (has_audio) + { + //sync based on audio + if (getoutputtime()>evtime) + { + //too late, zap it + return; + } + while (getoutputtime()<evtime && !g_quit) Sleep(1); + } + else + { + //sync based on time + while (paused && !g_quit) Sleep(1); + while ((GetTickCount64()-m_starttime)<(unsigned int)evtime && !g_quit) Sleep(1); + } + if (g_quit) return; + if (video_mediatype==VIDEO_MAKETYPE('Y','V','1','2')) + { + // YV12 needs to pass a YV12_PLANES structure. + static YV12_PLANES yv12planes; + int s=video_h*video_w; + yv12planes.y.baseAddr=(unsigned char *)b; + yv12planes.y.rowBytes=video_w; + yv12planes.v.baseAddr=(unsigned char *)b+s; + yv12planes.v.rowBytes=video_w/2; + yv12planes.u.baseAddr=(unsigned char *)b+(s*5)/4; + yv12planes.u.rowBytes=video_w/2; + m_video_output->draw((void *)&yv12planes); + } + else + m_video_output->draw(b); + } + void endofstream() + { + if (!has_audio) PostMessage(mod.hMainWindow,WM_WA_MPEG_EOF,0,0); + } +}; + +const wchar_t *extension(const wchar_t *fn) +{ + const wchar_t *x = PathFindExtensionW(fn); + + if (*x) + return CharNextW(x); + else + return x; +} + +DWORD WINAPI DecodeThread(LPVOID b); // the decode thread procedure + +void config(HWND hwndParent) +{ + doConfig(WASABI_API_LNG_HINST,hwndParent); + mod.FileExtensions=getfileextensions(); +} + +int DoAboutMessageBox(HWND parent, wchar_t* title, wchar_t* message) +{ + MSGBOXPARAMSW msgbx = {sizeof(MSGBOXPARAMSW),0}; + msgbx.lpszText = message; + msgbx.lpszCaption = title; + msgbx.lpszIcon = MAKEINTRESOURCEW(102); + msgbx.hInstance = GetModuleHandle(0); + msgbx.dwStyle = MB_USERICON; + msgbx.hwndOwner = parent; + return MessageBoxIndirectW(&msgbx); +} + +void about(HWND hwndParent) +{ + wchar_t message[1024] = {0}, text[1024] = {0}; + WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_DSHOW_PLUGIN_OLD,text,1024); + StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), + mod.description, __DATE__); + DoAboutMessageBox(hwndParent,text,message); +} + +int init() +{ + if (!IsWindow(mod.hMainWindow)) + return IN_INIT_FAILURE; + + INI_FILE = (const char *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETINIFILE); + + config_read(); + + waServiceFactory *sf = mod.service->service_getServiceByGuid(languageApiGUID); + if (sf) WASABI_API_LNG = reinterpret_cast<api_language*>(sf->getInterface()); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(mod.hDllInstance,IndshowLangGUID); + + static wchar_t szDescription[256]; + StringCchPrintfW(szDescription,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_DSHOW_PLUGIN),PLUGIN_VERSION); + mod.description = (char*)szDescription; + + mod.FileExtensions=getfileextensions(); + + pTrackSelector = new DSTrackSelector(); + return IN_INIT_SUCCESS; +} + +void quit() +{ + if (pTrackSelector) { delete pTrackSelector; pTrackSelector = NULL; } + if (infoFn) { free(infoFn); infoFn = 0; } + if (infoHeader) { delete infoHeader; infoHeader = 0; } +} + +int isourfile(const wchar_t *fn) +{ + // TODO: re-enable this, but only via an option +#if 0 + if (!strncmp(fn,"mms://",6) || !strncmp(fn,"mmst://",7) || !strncmp(fn,"mmsu://",7)) + { + if (!strstr(fn,".wma")) return 1; + } +#endif +#ifdef IN_DSHOW_CAPTURE_SUPPORT // disable capture, for now + return !strncmp(fn,"cap://",6); +#endif + return 0; +} + +#ifdef DEBUG +HRESULT AddToRot(IUnknown *pUnkGraph, DWORD *pdwRegister) +{ + IMoniker * pMoniker; + IRunningObjectTable *pROT; + if (FAILED(GetRunningObjectTable(0, &pROT))) + { + return E_FAIL; + } + WCHAR wsz[256] = {0}; + StringCchPrintfW(wsz, 256, L"FilterGraph %08x pid %08x", (DWORD_PTR)pUnkGraph, GetCurrentProcessId()); + HRESULT hr = CreateItemMoniker(L"!", wsz, &pMoniker); + if (SUCCEEDED(hr)) + { + hr = pROT->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, pUnkGraph, + pMoniker, pdwRegister); + pMoniker->Release(); + } + pROT->Release(); + return hr; +} +#endif + +IBaseFilter* GetCaptureDevice(ICreateDevEnum *pDevEnum, const GUID dwCLSID, int nDeviceSelected) +{ + IBaseFilter *pSrc = NULL; + + IEnumMoniker *pClassEnum = NULL; + pDevEnum->CreateClassEnumerator(dwCLSID, &pClassEnum, 0); + + ULONG cFetched; + IMoniker *pMoniker = NULL; + int nEnumPos = 0; + while (pClassEnum->Next(1, &pMoniker, &cFetched) == S_OK) + { + if (nEnumPos == nDeviceSelected) + { + pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pSrc); + pMoniker->Release(); + pClassEnum->Release(); + return pSrc; + } + pMoniker->Release(); + nEnumPos++; + } + pClassEnum->Release(); + return pSrc; +} + +void stop(); + +void releaseObjects() +{ + if (m_notif_hwnd) + { + KillTimer(m_notif_hwnd,111); + DestroyWindow(m_notif_hwnd); + } + m_notif_hwnd=NULL; + + if (pGraphBuilder) + pGraphBuilder->Release(); + pGraphBuilder=0; + + if (pCapture) + pCapture->Release(); + pCapture=0; + + + if (pMediaEventEx) + pMediaEventEx->Release(); + pMediaEventEx=0; + + if (pMediaControl) + pMediaControl->Release(); + pMediaControl=0; + + if (pMediaSeeking) + pMediaSeeking->Release(); + pMediaSeeking=0; + + if (pCapVidSrcFilter) + pCapVidSrcFilter->Release(); + pCapVidSrcFilter=0; + + if (pCapAudSrcFilter) + pCapAudSrcFilter->Release(); + pCapAudSrcFilter=0; + + /* + if (nullfilter) + { + ((CBaseFilter *)nullfilter)->Release(); + } + */ + nullfilter=0; + + /* + if (nullfilter2) + { + nullfilter2->Release(); + //TODO: why does this still have 3 refcounts? + } + */ + nullfilter2=0; + +} + +#define BeginEnumFilters(pFilterGraph, pEnumFilters, pBaseFilter) \ +{CComPtr<IEnumFilters> pEnumFilters; \ + if(pFilterGraph && SUCCEEDED(pFilterGraph->EnumFilters(&pEnumFilters))) \ +{ \ + for(CComPtr<IBaseFilter> pBaseFilter; S_OK == pEnumFilters->Next(1, &pBaseFilter, 0); pBaseFilter = NULL) \ +{ \ + +#define EndEnumFilters }}} + +void handleNotifyEvents() +{ + for (;;) + { + long evCode; + LONG_PTR param1, param2; + HRESULT h = pMediaEventEx->GetEvent(&evCode, ¶m1, ¶m2, 0); + if (FAILED(h)) break; + switch (evCode) + { + case EC_OLE_EVENT: + { + char str[MAX_PATH] = {0}; + WideCharToMultiByteSZ(CP_ACP,0,(BSTR)param1,-1,str,MAX_PATH,NULL,NULL); + if (!lstrcmpiA(str,"URLAndExit")) + { + //FUCKO + /* + WCHAR str[16384],*str2; + WCHAR m_filename[MAX_PATH] = {0}; + MultiByteToWideChar( CP_ACP, 0, infos->getFilename(), lstrlen( infos->getFilename() ) + 1, m_filename, sizeof( m_filename ) ); + wcscpy(str,(BSTR)param2); + wcscat(str,L"&filename="); + MakeEscapedURL(m_filename,&str2); + wcscat(str,str2); + delete str2; + LaunchURL(str); + */ + } + break; + } + case EC_BUFFERING_DATA: + { + m_buffering=param1; + if (!m_buffering) + { + lastfn_status[0]=0; + g_bufferstat=0; + PostMessage(mod.hMainWindow,WM_USER,0,243); + break; + } + } + break; + } + pMediaEventEx->FreeEventParams(evCode, param1, param2); + } +} + +LRESULT CALLBACK notif_wndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (uMsg==WM_TIMER && wParam==111 && m_notif_hwnd) + { + handleNotifyEvents(); + if (m_buffering) + { + BeginEnumFilters(pGraphBuilder, pEF, pBF) + { + if (CComQIPtr<IAMNetworkStatus, &IID_IAMNetworkStatus> pAMNS = pBF) + { + long BufferingProgress = 0; + if (SUCCEEDED(pAMNS->get_BufferingProgress(&BufferingProgress)) && BufferingProgress > 0) + { + StringCchPrintfW(lastfn_status, 256,WASABI_API_LNGSTRINGW(IDS_BUFFERING),BufferingProgress); + if (m_video_output) m_video_output->extended(VIDUSER_SET_INFOSTRINGW,(INT_PTR)lastfn_status,0); + + int bpos=BufferingProgress; + int csa = mod.SAGetMode(); + char tempdata[75*2]={0,}; + int x; + if (csa&1) + { + for (x = 0; x < bpos*75/100; x ++) + { + tempdata[x]=x*16/75; + } + } + if (csa&2) + { + int offs=(csa&1) ? 75 : 0; + x=0; + while (x < bpos*75/100) + { + tempdata[offs + x++]=-6+x*14/75; + } + while (x < 75) + { + tempdata[offs + x++]=0; + } + } + if (csa==4) + { + tempdata[0]=tempdata[1]=(bpos*127/100); + } + if (csa) mod.SAAdd(tempdata,++g_bufferstat,(csa==3)?0x80000003:csa); + + PostMessage(mod.hMainWindow,WM_USER,0,243); + break; + } + } + } + EndEnumFilters + } + } + return (DefWindowProc(hwnd, uMsg, wParam, lParam)); +} + +int play(const wchar_t *fn) +{ + paused=0; + decode_pos_ms=0; + seek_needed=-1; + m_length=-1; + g_quit=0; + lstrcpyn(m_lastfn,fn, 2048); + m_avgfps_start=0; + m_nbframes=0; + m_float=0; + m_notif_hwnd=NULL; + lastfn_status[0]=0; + m_buffering=0; + g_bufferstat=0; + + double aspect=1.0; + + HRESULT hr; + + assert(pGraphBuilder==0); + hr = ::CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,IID_IGraphBuilder,(void**)&pGraphBuilder); + if (FAILED(hr)) + return 1; + +#ifdef DEBUG + DWORD dwRegister; + AddToRot(pGraphBuilder, &dwRegister); +#endif +#ifdef IN_DSHOW_CAPTURE_SUPPORT // disable capture, for now + m_is_capture=!wcsncmp(fn, L"cap://",6); +#endif + // CWAAudioRenderer *nullfilter; + // CWAVideoRenderer *nullfilter2; + + has_audio=true; has_video=true; has_palette=false; + + // insert audio renderer + nullfilter=new CWAAudioRenderer(); + pGraphBuilder->AddFilter(nullfilter,L"Null Audio"); + nullfilter->SetCallback(new CAudioGrab()); + + // insert video renderer + + nullfilter2=new CWAVideoRenderer(); + pGraphBuilder->AddFilter(nullfilter2,L"Null Video"); + nullfilter2->SetCallback(new CVideoGrab()); + + assert(pMediaEventEx==0); + pGraphBuilder->QueryInterface(IID_IMediaEvent, (void **)&pMediaEventEx); + if (!pMediaEventEx) + { + releaseObjects(); + return 1; + } + + m_video_output=(IVideoOutput *)SendMessage(mod.hMainWindow,WM_USER,0,IPC_GET_IVIDEOOUTPUT); + if (!m_video_output) + { + releaseObjects(); + return 1; + } + + m_video_output->extended(VIDUSER_SET_TRACKSELINTERFACE, (INT_PTR)pTrackSelector, 0); + + //create window that will receive filter notifications + static int classReg=0; + if (!classReg) + { + WNDCLASS wc={0,}; + wc.style = CS_DBLCLKS; + wc.lpfnWndProc = notif_wndProc; + wc.hInstance = mod.hDllInstance; + wc.hIcon = NULL; + wc.hCursor = NULL; + wc.lpszClassName = L"in_dshowClass"; + if (!RegisterClassW(&wc)) + return 1; + classReg=1; + } + m_notif_hwnd=CreateWindow(L"in_dshowClass",L"dshow_notif",NULL,0,0,1,1,NULL,NULL,mod.hDllInstance,NULL); + SetTimer(m_notif_hwnd,111,500,0); + +#ifdef IN_DSHOW_CAPTURE_SUPPORT // disable capture, for now + if (m_is_capture) + { + //build capture graph + assert(pCapture==0); + HRESULT hr = CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC, (REFIID)IID_ICaptureGraphBuilder2, (void **)&pCapture); + pCapture->SetFiltergraph(pGraphBuilder); + + CComPtr<ICreateDevEnum> pDevEnum = NULL; + if (CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pDevEnum) != S_OK) + { + releaseObjects(); + return 1; + } + + int vcapdev=_wtoi(fn+6); + int acapdev=_wtoi(fn+8); //FUCKO + + assert(pCapVidSrcFilter==0); + pCapVidSrcFilter = GetCaptureDevice(pDevEnum, CLSID_VideoInputDeviceCategory, vcapdev); + if (!pCapVidSrcFilter || pGraphBuilder->AddFilter(pCapVidSrcFilter, L"Video Capture") != S_OK) + { + releaseObjects(); + return 1; + } + + //if (g_config_vidcap) { + if (1) + { + IAMStreamConfig *pSC; + hr = pCapture->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, pCapVidSrcFilter, IID_IAMStreamConfig, (void **)&pSC); + + if (hr != NOERROR) + pCapture->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pCapVidSrcFilter, IID_IAMStreamConfig, (void **)&pSC); + + ISpecifyPropertyPages *pSpec=NULL; + if (pSC) + { + pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); + if (pSpec) + { + CAUUID cauuid; + pSpec->GetPages(&cauuid); + OleCreatePropertyFrame(NULL, 0, 0, L"Poopie", 1, (IUnknown **)&pSC, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); + CoTaskMemFree(cauuid.pElems); + pSpec->Release(); + } + pSC->Release(); + } + } + + if (pCapVidSrcFilter && pCapture->RenderStream(0,&MEDIATYPE_Video, pCapVidSrcFilter, 0, nullfilter2) != S_OK) + { + releaseObjects(); + return 1; + } + + assert(pCapAudSrcFilter==0); + pCapAudSrcFilter = GetCaptureDevice(pDevEnum, CLSID_AudioInputDeviceCategory, acapdev); + if (!pCapVidSrcFilter || pGraphBuilder->AddFilter(pCapAudSrcFilter, L"Audio Capture") != S_OK) + { + releaseObjects(); + return 1; + } + if (pCapVidSrcFilter && pCapture->RenderStream(0,&MEDIATYPE_Audio, pCapAudSrcFilter, 0, nullfilter) != S_OK) + { + releaseObjects(); + return 1; + } + } + else +#endif + { + try + { + hr = pGraphBuilder->RenderFile(fn,NULL); + } + catch (...) + { + releaseObjects(); + return 1; + } + + if (FAILED(hr)) + { + // check for URL launch (WMA/ASF/...) + handleNotifyEvents(); + releaseObjects(); +#ifdef WINAMPX + if ((hr == CLASS_E_CLASSNOTAVAILABLE) || (hr == VFW_E_UNSUPPORTED_VIDEO) || (hr == VFW_E_NO_DECOMPRESSOR)) + { + if (ReportMissingCodec(fn)) // returns true if we sent a message + return -500; // Unsupported format + return -200; // Can't play file + } +#endif // WINAMPX + return 1; + } + +#ifdef WINAMPX + // Check if it's a partial playing of the file (likely video missing) + if ((hr == VFW_S_PARTIAL_RENDER) || (hr == VFW_S_VIDEO_NOT_RENDERED)) + { + if (!ReportMissingCodec(fn)) // Report the missing codec if we can determine it + mod.fire_winampstatus(WINAMPX_STATUS_MISSING_AVI_CODEC, 0); // If we can't report a null codec missing + } +#endif // WINAMPX + } + + // check if video has been negociated + { + CMediaType *mt=nullfilter2->GetAcceptedType(); + GUID t=mt->subtype; + if (t==MEDIASUBTYPE_YUY2) video_mediatype=VIDEO_MAKETYPE('Y','U','Y','2'); + else if (t==MEDIASUBTYPE_YV12) video_mediatype=VIDEO_MAKETYPE('Y','V','1','2'); + else if (t==MEDIASUBTYPE_RGB32) video_mediatype=VIDEO_MAKETYPE('R','G','3','2'); + else if (t==MEDIASUBTYPE_RGB24) video_mediatype=VIDEO_MAKETYPE('R','G','2','4'); + else if (t==MEDIASUBTYPE_RGB8) video_mediatype=VIDEO_MAKETYPE('R','G','B','8'); + else if (t==MEDIASUBTYPE_YVYU) video_mediatype=VIDEO_MAKETYPE('Y','V','Y','U'); + else + { + has_video=NULL; + } + if (has_video) + { + +#ifdef DEBUGVIDEO + char tmp[512] = {0}; + int a=video_mediatype; + wsprintf(tmp,"file: %s %c%c%c%c\n",fn,(char)(a&0xff),(char)((a>>8)&0xff),(char)((a>>16)&0xff),(char)((a>>24)&0xff)); + outputDebugStr(tmp); +#endif + + GUID format=mt->formattype; + int pw,ph; + if (format==FORMAT_VideoInfo) + { + VIDEOINFOHEADER *pHeader=(VIDEOINFOHEADER*)mt->pbFormat; + pw=pHeader->bmiHeader.biWidth; + ph=abs(pHeader->bmiHeader.biHeight); + if (pHeader->bmiHeader.biBitCount==8) + { + VIDEOINFO *pHeader=(VIDEOINFO*)mt->pbFormat; + memcpy(palette,&pHeader->bmiColors,sizeof(RGBQUAD)*0x100); + has_palette = true; + } + +#ifdef DEBUGVIDEO + RECT r=pHeader->rcSource; + RECT r2=pHeader->rcTarget; + char tmp[512] = {0}; + wsprintf(tmp,"init videoheader1: %i %i %i %i, %i %i %i %i\n",r.left,r.right,r.top,r.bottom,r2.left,r2.right,r2.top,r2.bottom); + outputDebugStr(tmp); +#endif + + } + else + { + VIDEOINFOHEADER2 *pHeader=(VIDEOINFOHEADER2*)mt->pbFormat; + pw=pHeader->bmiHeader.biWidth; + ph=abs(pHeader->bmiHeader.biHeight); + if (pHeader->dwPictAspectRatioX) aspect *= (double)pHeader->dwPictAspectRatioY * (double)pw / ((double)ph * (double)pHeader->dwPictAspectRatioX); + +#ifdef DEBUGVIDEO + RECT r=pHeader->rcSource; + RECT r2=pHeader->rcTarget; + char tmp[512] = {0}; + wsprintf(tmp,"init videoheader2: %i %i %i %i, %i %i %i %i\n",r.left,r.right,r.top,r.bottom,r2.left,r2.right,r2.top,r2.bottom); + outputDebugStr(tmp); +#endif + } + video_w=pw; video_h=ph; + video_len=(video_w*video_h*4)+sizeof(double); //CT> might be wrong for YUY2, etc... + } + else + { + pGraphBuilder->RemoveFilter(nullfilter2); + // TODO: release? + nullfilter2=0; + } + } + + // check if audio has been negociated + { + CMediaType *mt=nullfilter->GetAcceptedType(); + if (mt->subtype!=MEDIASUBTYPE_PCM && mt->subtype!=MEDIASUBTYPE_IEEE_FLOAT) + has_audio=NULL; + else + { + WAVEFORMATEX *pHeader = (WAVEFORMATEX*)mt->pbFormat; + // reget this cause this is the real UNCOMPRESSED format + audio_bps = pHeader->wBitsPerSample; + audio_srate = pHeader->nSamplesPerSec; + audio_nch = pHeader->nChannels; + + if (mt->subtype == MEDIASUBTYPE_IEEE_FLOAT/*WAVE_FORMAT_IEEE_FLOAT*//*audio_bps==32 || audio_bps==64*/) + { + m_float = 1; + m_src_bps = audio_bps; + //audio_bps = 16; //TODO: read bits from AGAVE_API_CONFIG :) + } + } + } + + // if none has been negociated, fuck off + if (!has_video && !has_audio) + { + releaseObjects(); + return 1; + } + + if (!has_audio) + { + pGraphBuilder->RemoveFilter(nullfilter); + // TODO: release? + nullfilter=0; + } + // QueryInterface for some basic interfaces + assert(pMediaControl==0); + pGraphBuilder->QueryInterface(IID_IMediaControl, (void **)&pMediaControl); + assert(pMediaSeeking==0); + pGraphBuilder->QueryInterface(IID_IMediaSeeking, (void **)&pMediaSeeking); + if (pMediaControl == NULL || pMediaEventEx == NULL) + { + releaseObjects(); + return 1; + } + + CComPtr<IVideoWindow> pVideoWindow; + pGraphBuilder->QueryInterface(IID_IVideoWindow, (void**)&pVideoWindow); + pVideoWindow->put_AutoShow(OAFALSE); + + CComPtr<IMediaFilter> pMediaFilter; + pGraphBuilder->QueryInterface(IID_IMediaFilter, (void**)&pMediaFilter); + + //FUCKO: verify if setsyncsource is really necessary (might be useful for sync under + // heavy cpu load) + /*if(!STRICMP(ext,"wma") || !STRICMP(ext,"asf") || !STRICMP(ext,"wmv")) { } + else*/ +#if IN_DSHOW_CAPTURE_SUPPORT // disable + if (!m_is_capture) + pMediaFilter->SetSyncSource(NULL); +#endif + + // retrieves length + { + CComPtr<IMediaPosition> pMediaPosition=NULL; + pGraphBuilder->QueryInterface(IID_IMediaPosition, (void **)&pMediaPosition); + if (pMediaPosition) + { + REFTIME length; + pMediaPosition->get_Duration(&length); + m_length=(int)(length*1000); + } + } + + getInfo(fn, m_status, 512, NULL,0, &m_bitrate, &audio_nch); + + if (has_audio) + { + //open output plugin + int maxlat=mod.outMod->Open(audio_srate,audio_nch,audio_bps,-1,-1); + if (maxlat<0) + { + releaseObjects(); + return 1; + } + +// if (has_video) + mod.SetInfo(m_bitrate,audio_srate/1000,audio_nch,1); + //else +// mod.SetInfo(audioBitrate,audio_srate/1000,audio_nch,1); + mod.SAVSAInit(maxlat,audio_srate); + mod.VSASetInfo(audio_srate,audio_nch); + mod.outMod->SetVolume(-666); + } + + if (has_video) + { + //open video stuff + m_video_output->extended(VIDUSER_SET_THREAD_SAFE, 0, 0); // we are NOT thread safe - we call draw() than a different thread than open() + m_video_output->open(video_w,video_h,0,aspect,video_mediatype); +#ifdef WINAMPX + if (has_palette) + { + m_video_output->extended(VIDUSER_SET_PALETTE, (int)palette, 0); + } + HWND hVideoWnd = (HWND)m_video_output->extended(VIDUSER_GET_VIDEOHWND, 0, 0); + + InvalidateRect(hVideoWnd, NULL, TRUE); +#endif + } + + m_video_output->extended(VIDUSER_SET_INFOSTRINGW,(INT_PTR)m_status,0); + m_laststatus=GetTickCount(); + + m_starttime=GetTickCount(); //used for non-audio videos + hr = pMediaControl->Run(); + if (FAILED(hr)) + { + stop(); + releaseObjects(); + return 1; + } + + lstrcpynW(lastfn,fn, MAX_PATH); + return 0; +} + +// standard pause implementation +void pause() +{ + paused=1; + m_time_paused=GetTickCount()-m_starttime; + if (has_audio) mod.outMod->Pause(1); +} + +void unpause() +{ + paused=0; + if (has_audio) + mod.outMod->Pause(0); + m_starttime=GetTickCount()-m_time_paused; + m_nbframes=m_avgfps_start=0; + m_laststatus=GetTickCount(); +} + +int ispaused() +{ + return paused; +} // Note: Shared with the dsr routines + +// stop playing. +void stop() +{ + g_quit=1; + + while (doingaudioshit) Sleep(10); + + if (pMediaControl) + pMediaControl->Stop(); + + if (m_video_output) + m_video_output->close(); + + mod.outMod->Close(); + mod.SAVSADeInit(); + + releaseObjects(); + + m_length=-1; + + m_lastfn[0]=0; +} + +int getlength() +{ + return m_length; +} + +int getoutputtime() +{ + + if (g_bufferstat) return g_bufferstat; + + if (has_audio) + { + return decode_pos_ms+ + (mod.outMod->GetOutputTime()-mod.outMod->GetWrittenTime()); + } + else + { + if (paused) + return m_time_paused; + return GetTickCount()-m_starttime; + } +} + +void setoutputtime(int time_in_ms) +{ + if (pMediaSeeking) + { + DWORD dwCaps = AM_SEEKING_CanSeekAbsolute; + if (pMediaSeeking->CheckCapabilities(&dwCaps) == S_OK) + { + int oldpause=paused; + if (oldpause) unpause(); + pMediaSeeking->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME); + LONGLONG l=((LONGLONG)time_in_ms)*10000; + pMediaSeeking->SetPositions(&l,AM_SEEKING_AbsolutePositioning|AM_SEEKING_SeekToKeyFrame|AM_SEEKING_ReturnTime ,NULL,AM_SEEKING_NoPositioning); + l/=10000; + time_in_ms=(int)l; + mod.outMod->Flush(time_in_ms); + decode_pos_ms=time_in_ms; + m_starttime=GetTickCount()-time_in_ms; //non-audio videos + m_nbframes=m_avgfps_start=0; + m_laststatus=GetTickCount(); + if (oldpause) pause(); + } + } +} + +void setvolume(int volume) +{ + { + mod.outMod->SetVolume(volume); + } +} +void setpan(int pan) +{ + mod.outMod->SetPan(pan); +} + +int infoDlg(const wchar_t *fn, HWND hwnd) +{ + doInfo(WASABI_API_LNG_HINST,hwnd, fn); + return INFOBOX_UNCHANGED; +} + +// this is an odd function. it is used to get the title and/or +// length of a track. +// if filename is either NULL or of length 0, it means you should +// return the info of lastfn. Otherwise, return the information +// for the file in filename. +// if title is NULL, no title is copied into it. +// if length_in_ms is NULL, no length is copied into it. +void getfileinfo(const wchar_t *filename, wchar_t *title, int *length_in_ms) +{ + if (!filename || !*filename) // currently playing file + { + if (length_in_ms) *length_in_ms=getlength(); + if (title) // get non-path portion.of filename + { + wchar_t *p = PathFindFileNameW(lastfn); + + if (lastfn_status[0]) + { + StringCchPrintfW(title, GETFILEINFO_TITLE_LENGTH, L"[%s] %s",lastfn_status,p); + } + else + { + lstrcpynW(title,p, GETFILEINFO_TITLE_LENGTH); + } + } + } + else // some other file + { + if (length_in_ms) // calculate length + { + *length_in_ms = GetFileLength(filename); + } + if (title) // get non path portion of filename + { + lstrcpynW(title, filename, GETFILEINFO_TITLE_LENGTH); + PathStripPathW(title); + PathRemoveExtensionW(title); + } + } +} + +void eq_set(int on, char data[10], int preamp) +{ +} + + +// module definition. + +In_Module mod = +{ + IN_VER_RET, // defined in IN2.H + "nullsoft(in_dshow.dll)", + 0, // hMainWindow (filled in by winamp) + 0, // hDllInstance (filled in by winamp) + /*"MPG;MPEG;M2V\0MPG File (*.MPG;*.MPEG;*.M2V)\0" + "AVI\0AVI File (*.AVI)\0" + "ASF;WMV\0ASF/WMV File (*.ASF;*.WMV)\0"*/ + 0, // this is a double-null limited list. "EXT\0Description\0EXT\0Description\0" etc. + 1, // is_seekable + 1, // uses output plug-in system + config, + about, + init, + quit, + getfileinfo, + infoDlg, + isourfile, + play, + pause, + unpause, + ispaused, + stop, + getlength, + getoutputtime, + setoutputtime, + setvolume, + setpan, + 0,0,0,0,0,0,0,0,0, // visualization calls filled in by winamp + 0,0, // dsp calls filled in by winamp + eq_set, + NULL, // setinfo call filled in by winamp + 0 // out_mod filled in by winamp +}; + +static char default_extlist[]="MPG;MPEG;M2V"; + +static const wchar_t *pExtList[]={L"MPG",L"MPEG",L"M2V",L"AVI",L"MOV",L"FLV",L"FLV1",L"OGV",L"OGA",L"OGM",L"RMVB",L"RM",L"VOB",L"AC3",L"MKV",L"MP4",L"M4V",L"3GP"}; +static const int pExtDescIdList[] = {0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, 13, 14,}; +static const int pExtDescList[] = +{ + IDS_FAMILY_STRING_MPEG, + IDS_FAMILY_STRING_MPEG2, + IDS_FAMILY_STRING_AVI, + IDS_FAMILY_STRING_MOV, + IDS_FAMILY_STRING_FLV, + IDS_FAMILY_STRING_OGV, + IDS_FAMILY_STRING_OGA, + IDS_FAMILY_STRING_OGM, + IDS_FAMILY_STRING_RM, + IDS_FAMILY_STRING_VOB, + IDS_FAMILY_STRING_AC3, + IDS_FAMILY_STRING_MKV, + IDS_FAMILY_STRING_MP4, + IDS_FAMILY_STRING_M4V, + IDS_FAMILY_STRING_3GPP, +}; +static FILETIME ftLastWriteTime; + +// is used to determine if the last write time of the file has changed when +// asked to get the metadata for the same cached file so we can update things +BOOL HasFileTimeChanged(const wchar_t *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData = {0}; + if (GetFileAttributesExW(fn, GetFileExInfoStandard, &fileData) == TRUE) + { + if(CompareFileTime(&ftLastWriteTime, &fileData.ftLastWriteTime)) + { + ftLastWriteTime = fileData.ftLastWriteTime; + return TRUE; + } + } + return FALSE; +} + +extern "C" +{ + + __declspec(dllexport) In_Module * winampGetInModule2() + { + return &mod; + } + + _declspec(dllexport) int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, int destlen) + { + if (!fn || !*fn) + { + if (!lstrcmpiA(data,"type")) + { + lstrcpyn(dest,L"1", destlen); //video + return 1; + } + + return 0; + } + if (!infoFn || !infoHeader || lstrcmpiW(fn, infoFn) || HasFileTimeChanged(fn)) + { + free(infoFn); + infoFn = _wcsdup(fn); + delete infoHeader; + infoHeader = MakeHeader(fn, true); + } + + if (!lstrcmpiA(data,"type")) + { + if (infoHeader) + { + if (infoHeader->has_video) + lstrcpyn(dest,L"1", destlen); //video + else + lstrcpyn(dest,L"0", destlen); // no video + } + else // assume video + { + lstrcpyn(dest,L"1", destlen); //video + } + return 1; + } + + if (!lstrcmpiA(data, "family")) + { + INT index; + LPCWSTR e; + DWORD lcid; + e = PathFindExtension(fn); + if (L'.' != *e || 0x00 == *(++e)) return 0; + + lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); + for (index = sizeof(pExtList)/sizeof(wchar_t*) - 1; index >= 0 && CSTR_EQUAL != CompareStringW(lcid, NORM_IGNORECASE, e, -1, pExtList[index], -1); index--); + if (index >= 0 && S_OK == StringCchCopyW(dest, destlen, WASABI_API_LNGSTRINGW(pExtDescList[pExtDescIdList[index]]))) return 1; + return 0; + } + + if (!lstrcmpiA(data,"length")) + { + int len = GetFileLength(fn); + if (len == -1000) + dest[0]=0; + else + StringCchPrintf(dest, destlen, L"%d", len); + return 1; + } + else if (!lstrcmpiA(data, "bitrate")) + { + int bitrate; + getInfo(fn, NULL, 0, NULL,0, &bitrate, NULL); + StringCchPrintf(dest, destlen, L"%d", bitrate); + return 1; + } + else if (!lstrcmpiA(data,"title")) + { + if (infoHeader && infoHeader->title) + lstrcpyn(dest,infoHeader->title, destlen); + else + dest[0]=0; + return 1; + } + else if (!lstrcmpiA(data,"artist")) + { + if (infoHeader && infoHeader->artist) + lstrcpyn(dest,infoHeader->artist, destlen); + else + dest[0]=0; + return 1; + } + else if (!lstrcmpiA(data,"comment")) + { + if (infoHeader && infoHeader->comment) + lstrcpyn(dest,infoHeader->comment, destlen); + else + dest[0]=0; + return 1; + } + else if (!lstrcmpiA(data,"genre")) + { + if (infoHeader && infoHeader->genre) + lstrcpyn(dest,infoHeader->genre, destlen); + else + dest[0]=0; + return 1; + } + else if (!lstrcmpiA(data,"album")) + { + if (infoHeader && infoHeader->album) + lstrcpyn(dest,infoHeader->album, destlen); + else + dest[0]=0; + return 1; + } + else if (!lstrcmpiA(data,"composer")) + { + if (infoHeader && infoHeader->composer) + lstrcpyn(dest,infoHeader->composer, destlen); + else + dest[0]=0; + return 1; + } + else if (!lstrcmpiA(data,"publisher")) + { + if (infoHeader && infoHeader->publisher) + lstrcpyn(dest,infoHeader->publisher, destlen); + else + dest[0]=0; + return 1; + } + return 0; + } +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_dshow/Main.h b/Src/Plugins/Input/in_dshow/Main.h new file mode 100644 index 00000000..7bc6c8d6 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/Main.h @@ -0,0 +1,37 @@ +#pragma once + +extern class CWAAudioRenderer *nullfilter; + +#include <windows.h> +#include <streams.h> +#include <strsafe.h> + +typedef struct tagVIDEOINFOHEADER2 { + RECT rcSource; + RECT rcTarget; + DWORD dwBitRate; + DWORD dwBitErrorRate; + REFERENCE_TIME AvgTimePerFrame; + DWORD dwInterlaceFlags; + DWORD dwCopyProtectFlags; + DWORD dwPictAspectRatioX; + DWORD dwPictAspectRatioY; + DWORD dwReserved1; + DWORD dwReserved2; + BITMAPINFOHEADER bmiHeader; +} VIDEOINFOHEADER2; +#include <AtlBase.h> + +extern IGraphBuilder *pGraphBuilder; +extern IMediaControl *pMediaControl; + +extern bool has_audio; +extern int audio_bps, audio_srate, audio_nch; +extern int m_float, m_src_bps; +extern int m_is_capture; +extern HWND m_notif_hwnd; +extern int m_bitrate; +#include "../Winamp/in2.h" +extern In_Module mod; // the output module (filled in near the bottom of this file) + +void releaseObjects(); diff --git a/Src/Plugins/Input/in_dshow/audioswitch.cpp b/Src/Plugins/Input/in_dshow/audioswitch.cpp new file mode 100644 index 00000000..032fbd71 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/audioswitch.cpp @@ -0,0 +1,1791 @@ +#include <windows.h> +#include <AtlBase.h> +#include <streams.h> +#include <strsafe.h> + +#include <qnetwork.h> +#include <initguid.h> // declares DEFINE_GUID to declare an EXTERN_C const. +#include "audioswitch.h" + +// Implements the CAudioSwitchRenderer class + +CAudioSwitchRenderer::CAudioSwitchRenderer(REFCLSID RenderClass, // CLSID for this renderer + TCHAR *pName, // Debug ONLY description + LPUNKNOWN pUnk, // Aggregated owner object + HRESULT *phr) : // General OLE return code + + CBaseFilter(pName, pUnk, &m_InterfaceLock, RenderClass), + m_evComplete(TRUE), + m_bAbort(FALSE), + m_pPosition(NULL), + m_ThreadSignal(TRUE), + m_bStreaming(FALSE), + m_bEOS(FALSE), + m_bEOSDelivered(FALSE), + m_dwAdvise(0), + m_pQSink(NULL), + m_bRepaintStatus(TRUE), + m_SignalTime(0), + m_bInReceive(FALSE), + m_EndOfStreamTimer(0), + m_inputSelected(0) +{ + for (int i = 0;i < 16;i++) m_pInputPin[i] = NULL; + for (int i = 0;i < 16;i++) m_pMediaSample[i] = NULL; + Ready(); +#ifdef PERF + m_idBaseStamp = MSR_REGISTER("BaseRenderer: sample time stamp"); + m_idBaseRenderTime = MSR_REGISTER("BaseRenderer: draw time (msec)"); + m_idBaseAccuracy = MSR_REGISTER("BaseRenderer: Accuracy (msec)"); +#endif +} + + +// Delete the dynamically allocated IMediaPosition and IMediaSeeking helper +// object. The object is created when somebody queries us. These are standard +// control interfaces for seeking and setting start/stop positions and rates. +// We will probably also have made an input pin based on CAudioSwitchRendererInputPin +// that has to be deleted, it's created when an enumerator calls our GetPin + +CAudioSwitchRenderer::~CAudioSwitchRenderer() +{ + ASSERT(m_bStreaming == FALSE); + ASSERT(m_EndOfStreamTimer == 0); + StopStreaming(); + ClearPendingSample(); + + // Delete any IMediaPosition implementation + + if (m_pPosition) + { + delete m_pPosition; + m_pPosition = NULL; + } + + // Delete any input pin created + + for (int i = 0;i < 16;i++) + { + if (m_pInputPin[i]) + { + delete m_pInputPin[i]; + m_pInputPin[i] = NULL; + } + } + + // Release any Quality sink + + ASSERT(m_pQSink == NULL); +} + + +// This returns the IMediaPosition and IMediaSeeking interfaces + +HRESULT CAudioSwitchRenderer::GetMediaPositionInterface(REFIID riid, void **ppv) +{ + CAutoLock cRendererLock(&m_InterfaceLock); + if (m_pPosition) + { + return m_pPosition->NonDelegatingQueryInterface(riid, ppv); + } + + HRESULT hr = NOERROR; + + // Create implementation of this dynamically since sometimes we may + // never try and do a seek. The helper object implements a position + // control interface (IMediaPosition) which in fact simply takes the + // calls normally from the filter graph and passes them upstream + + m_pPosition = new CRendererPosPassThru(NAME("Renderer CPosPassThru"), + CBaseFilter::GetOwner(), + (HRESULT *) & hr, + GetPin(m_inputSelected)); + if (m_pPosition == NULL) + { + return E_OUTOFMEMORY; + } + + if (FAILED(hr)) + { + delete m_pPosition; + m_pPosition = NULL; + return E_NOINTERFACE; + } + return GetMediaPositionInterface(riid, ppv); +} + + +// Overriden to say what interfaces we support and where + +STDMETHODIMP CAudioSwitchRenderer::NonDelegatingQueryInterface(REFIID riid, void **ppv) +{ + // Do we have this interface + + if (riid == IID_IMediaPosition || riid == IID_IMediaSeeking) + { + return GetMediaPositionInterface(riid, ppv); + } + else + { + return CBaseFilter::NonDelegatingQueryInterface(riid, ppv); + } +} + + +// This is called whenever we change states, we have a manual reset event that +// is signalled whenever we don't won't the source filter thread to wait in us +// (such as in a stopped state) and likewise is not signalled whenever it can +// wait (during paused and running) this function sets or resets the thread +// event. The event is used to stop source filter threads waiting in Receive + +HRESULT CAudioSwitchRenderer::SourceThreadCanWait(BOOL bCanWait) +{ + if (bCanWait == TRUE) + { + m_ThreadSignal.Reset(); + } + else + { + m_ThreadSignal.Set(); + } + return NOERROR; +} + + +#ifdef DEBUG +// Dump the current renderer state to the debug terminal. The hardest part of +// the renderer is the window where we unlock everything to wait for a clock +// to signal it is time to draw or for the application to cancel everything +// by stopping the filter. If we get things wrong we can leave the thread in +// WaitForRenderTime with no way for it to ever get out and we will deadlock + +void CAudioSwitchRenderer::DisplayRendererState() +{ + DbgLog((LOG_TIMING, 1, TEXT("\nTimed out in WaitForRenderTime"))); + + // No way should this be signalled at this point + + BOOL bSignalled = m_ThreadSignal.Check(); + DbgLog((LOG_TIMING, 1, TEXT("Signal sanity check %d"), bSignalled)); + + // Now output the current renderer state variables + + DbgLog((LOG_TIMING, 1, TEXT("Filter state %d"), m_State)); + + DbgLog((LOG_TIMING, 1, TEXT("Abort flag %d"), m_bAbort)); + + DbgLog((LOG_TIMING, 1, TEXT("Streaming flag %d"), m_bStreaming)); + + DbgLog((LOG_TIMING, 1, TEXT("Clock advise link %d"), m_dwAdvise)); + + DbgLog((LOG_TIMING, 1, TEXT("Current media sample %x"), m_pMediaSample[m_inputSelected])); + + DbgLog((LOG_TIMING, 1, TEXT("EOS signalled %d"), m_bEOS)); + + DbgLog((LOG_TIMING, 1, TEXT("EOS delivered %d"), m_bEOSDelivered)); + + DbgLog((LOG_TIMING, 1, TEXT("Repaint status %d"), m_bRepaintStatus)); + + + // Output the delayed end of stream timer information + + DbgLog((LOG_TIMING, 1, TEXT("End of stream timer %x"), m_EndOfStreamTimer)); + + DbgLog((LOG_TIMING, 1, TEXT("Deliver time %s"), CDisp((LONGLONG)m_SignalTime))); + + + // Should never timeout during a flushing state + + BOOL bFlushing = m_pInputPin[m_inputSelected]->IsFlushing(); + DbgLog((LOG_TIMING, 1, TEXT("Flushing sanity check %d"), bFlushing)); + + // Display the time we were told to start at + DbgLog((LOG_TIMING, 1, TEXT("Last run time %s"), CDisp((LONGLONG)m_tStart.m_time))); + + // Have we got a reference clock + if (m_pClock == NULL) return ; + + // Get the current time from the wall clock + + CRefTime CurrentTime, StartTime, EndTime; + m_pClock->GetTime((REFERENCE_TIME*) &CurrentTime); + CRefTime Offset = CurrentTime - m_tStart; + + // Display the current time from the clock + + DbgLog((LOG_TIMING, 1, TEXT("Clock time %s"), CDisp((LONGLONG)CurrentTime.m_time))); + + DbgLog((LOG_TIMING, 1, TEXT("Time difference %dms"), Offset.Millisecs())); + + + // Do we have a sample ready to render + if (m_pMediaSample[m_inputSelected] == NULL) return ; + + m_pMediaSample[m_inputSelected]->GetTime((REFERENCE_TIME*)&StartTime, (REFERENCE_TIME*)&EndTime); + DbgLog((LOG_TIMING, 1, TEXT("Next sample stream times (Start %d End %d ms)"), + StartTime.Millisecs(), EndTime.Millisecs())); + + // Calculate how long it is until it is due for rendering + CRefTime Wait = (m_tStart + StartTime) - CurrentTime; + DbgLog((LOG_TIMING, 1, TEXT("Wait required %d ms"), Wait.Millisecs())); +} +#endif + + +// Wait until the clock sets the timer event or we're otherwise signalled. We +// set an arbitrary timeout for this wait and if it fires then we display the +// current renderer state on the debugger. It will often fire if the filter's +// left paused in an application however it may also fire during stress tests +// if the synchronisation with application seeks and state changes is faulty + +#define RENDER_TIMEOUT 10000 + +HRESULT CAudioSwitchRenderer::WaitForRenderTime() +{ + HANDLE WaitObjects[] = { m_ThreadSignal, m_RenderEvent }; + DWORD Result = WAIT_TIMEOUT; + + // Wait for either the time to arrive or for us to be stopped + + OnWaitStart(); + while (Result == WAIT_TIMEOUT) + { + Result = WaitForMultipleObjects(2, WaitObjects, FALSE, RENDER_TIMEOUT); + +#ifdef DEBUG + if (Result == WAIT_TIMEOUT) DisplayRendererState(); +#endif + + } + OnWaitEnd(); + + // We may have been awoken without the timer firing + + if (Result == WAIT_OBJECT_0) + { + return VFW_E_STATE_CHANGED; + } + + SignalTimerFired(); + return NOERROR; +} + + +// Poll waiting for Receive to complete. This really matters when +// Receive may set the palette and cause window messages +// The problem is that if we don't really wait for a renderer to +// stop processing we can deadlock waiting for a transform which +// is calling the renderer's Receive() method because the transform's +// Stop method doesn't know to process window messages to unblock +// the renderer's Receive processing +void CAudioSwitchRenderer::WaitForReceiveToComplete() +{ + for (;;) + { + if (!m_bInReceive) + { + break; + } + + MSG msg; + // Receive all interthread snedmessages + PeekMessage(&msg, NULL, WM_NULL, WM_NULL, PM_NOREMOVE); + + Sleep(1); + } + + // If the wakebit for QS_POSTMESSAGE is set, the PeekMessage call + // above just cleared the changebit which will cause some messaging + // calls to block (waitMessage, MsgWaitFor...) now. + // Post a dummy message to set the QS_POSTMESSAGE bit again + if (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) & QS_POSTMESSAGE) + { + // Send dummy message + PostThreadMessage(GetCurrentThreadId(), WM_NULL, 0, 0); + } +} + +// A filter can have four discrete states, namely Stopped, Running, Paused, +// Intermediate. We are in an intermediate state if we are currently trying +// to pause but haven't yet got the first sample (or if we have been flushed +// in paused state and therefore still have to wait for a sample to arrive) + +// This class contains an event called m_evComplete which is signalled when +// the current state is completed and is not signalled when we are waiting to +// complete the last state transition. As mentioned above the only time we +// use this at the moment is when we wait for a media sample in paused state +// If while we are waiting we receive an end of stream notification from the +// source filter then we know no data is imminent so we can reset the event +// This means that when we transition to paused the source filter must call +// end of stream on us or send us an image otherwise we'll hang indefinately + + +// Simple internal way of getting the real state + +FILTER_STATE CAudioSwitchRenderer::GetRealState() +{ + return m_State; +} + + +// The renderer doesn't complete the full transition to paused states until +// it has got one media sample to render. If you ask it for its state while +// it's waiting it will return the state along with VFW_S_STATE_INTERMEDIATE + +STDMETHODIMP CAudioSwitchRenderer::GetState(DWORD dwMSecs, FILTER_STATE *State) +{ + CheckPointer(State, E_POINTER); + + if (WaitDispatchingMessages(m_evComplete, dwMSecs) == WAIT_TIMEOUT) + { + *State = m_State; + return VFW_S_STATE_INTERMEDIATE; + } + *State = m_State; + return NOERROR; +} + + +// If we're pausing and we have no samples we don't complete the transition +// to State_Paused and we return S_FALSE. However if the m_bAbort flag has +// been set then all samples are rejected so there is no point waiting for +// one. If we do have a sample then return NOERROR. We will only ever return +// VFW_S_STATE_INTERMEDIATE from GetState after being paused with no sample +// (calling GetState after either being stopped or Run will NOT return this) + +HRESULT CAudioSwitchRenderer::CompleteStateChange(FILTER_STATE OldState) +{ + // Allow us to be paused when disconnected + + if (m_pInputPin[m_inputSelected]->IsConnected() == FALSE) + { + Ready(); + return S_OK; + } + + // Have we run off the end of stream + + if (IsEndOfStream() == TRUE) + { + Ready(); + return S_OK; + } + + // Make sure we get fresh data after being stopped + + if (HaveCurrentSample() == TRUE) + { + if (OldState != State_Stopped) + { + Ready(); + return S_OK; + } + } + NotReady(); + return S_FALSE; +} + + +// When we stop the filter the things we do are:- + +// Decommit the allocator being used in the connection +// Release the source filter if it's waiting in Receive +// Cancel any advise link we set up with the clock +// Any end of stream signalled is now obsolete so reset +// Allow us to be stopped when we are not connected + +STDMETHODIMP CAudioSwitchRenderer::Stop() +{ + CAutoLock cRendererLock(&m_InterfaceLock); + + // Make sure there really is a state change + + if (m_State == State_Stopped) + { + return NOERROR; + } + + // Is our input pin connected + + if (m_pInputPin[m_inputSelected]->IsConnected() == FALSE) + { + NOTE("Input pin is not connected"); + m_State = State_Stopped; + return NOERROR; + } + + CBaseFilter::Stop(); + + // If we are going into a stopped state then we must decommit whatever + // allocator we are using it so that any source filter waiting in the + // GetBuffer can be released and unlock themselves for a state change + + if (m_pInputPin[m_inputSelected]->Allocator()) + { + m_pInputPin[m_inputSelected]->Allocator()->Decommit(); + } + + // Cancel any scheduled rendering + + SetRepaintStatus(TRUE); + StopStreaming(); + SourceThreadCanWait(FALSE); + ResetEndOfStream(); + CancelNotification(); + + // There should be no outstanding clock advise + ASSERT(CancelNotification() == S_FALSE); + ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent, 0)); + ASSERT(m_EndOfStreamTimer == 0); + + Ready(); + WaitForReceiveToComplete(); + m_bAbort = FALSE; + return NOERROR; +} + + +// When we pause the filter the things we do are:- + +// Commit the allocator being used in the connection +// Allow a source filter thread to wait in Receive +// Cancel any clock advise link (we may be running) +// Possibly complete the state change if we have data +// Allow us to be paused when we are not connected + +STDMETHODIMP CAudioSwitchRenderer::Pause() +{ + CAutoLock cRendererLock(&m_InterfaceLock); + FILTER_STATE OldState = m_State; + ASSERT(m_pInputPin[m_inputSelected]->IsFlushing() == FALSE); + + // Make sure there really is a state change + + if (m_State == State_Paused) + { + return CompleteStateChange(State_Paused); + } + + // Has our input pin been connected + + if (m_pInputPin[m_inputSelected]->IsConnected() == FALSE) + { + NOTE("Input pin is not connected"); + m_State = State_Paused; + return CompleteStateChange(State_Paused); + } + + // Pause the base filter class + + HRESULT hr = CBaseFilter::Pause(); + if (FAILED(hr)) + { + NOTE("Pause failed"); + return hr; + } + + // Enable EC_REPAINT events again + + SetRepaintStatus(TRUE); + StopStreaming(); + SourceThreadCanWait(TRUE); + CancelNotification(); + ResetEndOfStreamTimer(); + + // If we are going into a paused state then we must commit whatever + // allocator we are using it so that any source filter can call the + // GetBuffer and expect to get a buffer without returning an error + + if (m_pInputPin[m_inputSelected]->Allocator()) + { + m_pInputPin[m_inputSelected]->Allocator()->Commit(); + } + + // There should be no outstanding advise + ASSERT(CancelNotification() == S_FALSE); + ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent, 0)); + ASSERT(m_EndOfStreamTimer == 0); + ASSERT(m_pInputPin[m_inputSelected]->IsFlushing() == FALSE); + + // When we come out of a stopped state we must clear any image we were + // holding onto for frame refreshing. Since renderers see state changes + // first we can reset ourselves ready to accept the source thread data + // Paused or running after being stopped causes the current position to + // be reset so we're not interested in passing end of stream signals + + if (OldState == State_Stopped) + { + m_bAbort = FALSE; + ClearPendingSample(); + } + return CompleteStateChange(OldState); +} + + +// When we run the filter the things we do are:- + +// Commit the allocator being used in the connection +// Allow a source filter thread to wait in Receive +// Signal the render event just to get us going +// Start the base class by calling StartStreaming +// Allow us to be run when we are not connected +// Signal EC_COMPLETE if we are not connected + +STDMETHODIMP CAudioSwitchRenderer::Run(REFERENCE_TIME StartTime) +{ + CAutoLock cRendererLock(&m_InterfaceLock); + FILTER_STATE OldState = m_State; + + // Make sure there really is a state change + + if (m_State == State_Running) + { + return NOERROR; + } + + // Send EC_COMPLETE if we're not connected + + if (m_pInputPin[m_inputSelected]->IsConnected() == FALSE) + { + NotifyEvent(EC_COMPLETE, S_OK, (LONG_PTR)(IBaseFilter *)this); + m_State = State_Running; + return NOERROR; + } + + Ready(); + + // Pause the base filter class + + HRESULT hr = CBaseFilter::Run(StartTime); + if (FAILED(hr)) + { + NOTE("Run failed"); + return hr; + } + + // Allow the source thread to wait + ASSERT(m_pInputPin[m_inputSelected]->IsFlushing() == FALSE); + SourceThreadCanWait(TRUE); + SetRepaintStatus(FALSE); + + // There should be no outstanding advise + ASSERT(CancelNotification() == S_FALSE); + ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent, 0)); + ASSERT(m_EndOfStreamTimer == 0); + ASSERT(m_pInputPin[m_inputSelected]->IsFlushing() == FALSE); + + // If we are going into a running state then we must commit whatever + // allocator we are using it so that any source filter can call the + // GetBuffer and expect to get a buffer without returning an error + + if (m_pInputPin[m_inputSelected]->Allocator()) + { + m_pInputPin[m_inputSelected]->Allocator()->Commit(); + } + + // When we come out of a stopped state we must clear any image we were + // holding onto for frame refreshing. Since renderers see state changes + // first we can reset ourselves ready to accept the source thread data + // Paused or running after being stopped causes the current position to + // be reset so we're not interested in passing end of stream signals + + if (OldState == State_Stopped) + { + m_bAbort = FALSE; + ClearPendingSample(); + } + return StartStreaming(); +} + + +// Return the number of input pins we support + +int CAudioSwitchRenderer::GetPinCount() +{ + return 16; +} + + +// We only support one input pin and it is numbered zero + +CBasePin *CAudioSwitchRenderer::GetPin(int n) +{ + CAutoLock cRendererLock(&m_InterfaceLock); + HRESULT hr = NOERROR; + ASSERT(n < 16 && n >= 0); + + // Should only ever be called with zero + + if (n > 16) + { + return NULL; + } + + // Create the input pin if not already done so + + if (m_pInputPin[n] == NULL) + { + WCHAR t[256] = {0}; + StringCchPrintfW(t, 256, L"In%d", n); + m_pInputPin[n] = new CAudioSwitchRendererInputPin(this, &hr, t); + } + return m_pInputPin[n]; +} + + +// If "In" then return the IPin for our input pin, otherwise NULL and error + +STDMETHODIMP CAudioSwitchRenderer::FindPin(LPCWSTR Id, IPin **ppPin) +{ + CheckPointer(ppPin, E_POINTER); + + int gotit = 0; + for (int i = 0;i < 16;i++) + { + WCHAR t[256] = {0}; + StringCchPrintfW(t, 256, L"In%d", i); + if (0 == lstrcmpW(Id, t)) + { + gotit = 1; + *ppPin = GetPin(i); + ASSERT(*ppPin); + (*ppPin)->AddRef(); + } + } + if (!gotit) + { + *ppPin = NULL; + return VFW_E_NOT_FOUND; + } + return NOERROR; +} + + +// Called when the input pin receives an EndOfStream notification. If we have +// not got a sample, then notify EC_COMPLETE now. If we have samples, then set +// m_bEOS and check for this on completing samples. If we're waiting to pause +// then complete the transition to paused state by setting the state event + +HRESULT CAudioSwitchRenderer::EndOfStream() +{ + // Ignore these calls if we are stopped + + if (m_State == State_Stopped) + { + return NOERROR; + } + + // If we have a sample then wait for it to be rendered + + m_bEOS = TRUE; + if (m_pMediaSample[m_inputSelected]) + { + return NOERROR; + } + + // If we are waiting for pause then we are now ready since we cannot now + // carry on waiting for a sample to arrive since we are being told there + // won't be any. This sets an event that the GetState function picks up + + Ready(); + + // Only signal completion now if we are running otherwise queue it until + // we do run in StartStreaming. This is used when we seek because a seek + // causes a pause where early notification of completion is misleading + + if (m_bStreaming) + { + SendEndOfStream(); + } + return NOERROR; +} + + +// When we are told to flush we should release the source thread + +HRESULT CAudioSwitchRenderer::BeginFlush() +{ + // If paused then report state intermediate until we get some data + + if (m_State == State_Paused) + { + NotReady(); + } + + SourceThreadCanWait(FALSE); + CancelNotification(); + ClearPendingSample(); + // Wait for Receive to complete + WaitForReceiveToComplete(); + return NOERROR; +} + + +// After flushing the source thread can wait in Receive again + +HRESULT CAudioSwitchRenderer::EndFlush() +{ + // Reset the current sample media time + if (m_pPosition) m_pPosition->ResetMediaTime(); + + // There should be no outstanding advise + + ASSERT(CancelNotification() == S_FALSE); + SourceThreadCanWait(TRUE); + return NOERROR; +} + + +// We can now send EC_REPAINTs if so required + +HRESULT CAudioSwitchRenderer::CompleteConnect(IPin *pReceivePin) +{ + SetRepaintStatus(TRUE); + m_bAbort = FALSE; + return NOERROR; +} + + +// Called when we go paused or running + +HRESULT CAudioSwitchRenderer::Active() +{ + return NOERROR; +} + + +// Called when we go into a stopped state + +HRESULT CAudioSwitchRenderer::Inactive() +{ + if (m_pPosition) + { + m_pPosition->ResetMediaTime(); + } + // People who derive from this may want to override this behaviour + // to keep hold of the sample in some circumstances + ClearPendingSample(); + return NOERROR; +} + + +// Tell derived classes about the media type agreed + +HRESULT CAudioSwitchRenderer::SetMediaType(const CMediaType *pmt) +{ + return NOERROR; +} + + +// When we break the input pin connection we should reset the EOS flags. When +// we are asked for either IMediaPosition or IMediaSeeking we will create a +// CPosPassThru object to handles media time pass through. When we're handed +// samples we store (by calling CPosPassThru::RegisterMediaTime) their media +// times so we can then return a real current position of data being rendered + +HRESULT CAudioSwitchRenderer::BreakConnect() +{ + // Do we have a quality management sink + + if (m_pQSink) + { + m_pQSink->Release(); + m_pQSink = NULL; + } + + // Check we have a valid connection + + int n = 0; + for (int i = 0;i < 16;i++) + { + if (!m_pInputPin[i] || m_pInputPin[i]->IsConnected() == FALSE) { n++; continue; } + + // Check we are stopped before disconnecting + if (m_State != State_Stopped && !m_pInputPin[i]->CanReconnectWhenActive()) + { + return VFW_E_NOT_STOPPED; + } + } + + if (n == 16) return S_FALSE; + + SetRepaintStatus(FALSE); + ResetEndOfStream(); + ClearPendingSample(); + m_bAbort = FALSE; + return NOERROR; +} + + +// Retrieves the sample times for this samples (note the sample times are +// passed in by reference not value). We return S_FALSE to say schedule this +// sample according to the times on the sample. We also return S_OK in +// which case the object should simply render the sample data immediately + +HRESULT CAudioSwitchRenderer::GetSampleTimes(IMediaSample *pMediaSample, + REFERENCE_TIME *pStartTime, + REFERENCE_TIME *pEndTime) +{ + ASSERT(m_dwAdvise == 0); + ASSERT(pMediaSample); + + // If the stop time for this sample is before or the same as start time, + // then just ignore it (release it) and schedule the next one in line + // Source filters should always fill in the start and end times properly! + + if (SUCCEEDED(pMediaSample->GetTime(pStartTime, pEndTime))) + { + if (*pEndTime < *pStartTime) + { + return VFW_E_START_TIME_AFTER_END; + } + } + else + { + // no time set in the sample... draw it now? + return S_OK; + } + + // Can't synchronise without a clock so we return S_OK which tells the + // caller that the sample should be rendered immediately without going + // through the overhead of setting a timer advise link with the clock + + if (m_pClock == NULL) + { + return S_OK; + } + return ShouldDrawSampleNow(pMediaSample, pStartTime, pEndTime); +} + + +// By default all samples are drawn according to their time stamps so we +// return S_FALSE. Returning S_OK means draw immediately, this is used +// by the derived video renderer class in its quality management. + +HRESULT CAudioSwitchRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample, + REFERENCE_TIME *ptrStart, + REFERENCE_TIME *ptrEnd) +{ + return S_FALSE; +} + + +// We must always reset the current advise time to zero after a timer fires +// because there are several possible ways which lead us not to do any more +// scheduling such as the pending image being cleared after state changes + +void CAudioSwitchRenderer::SignalTimerFired() +{ + m_dwAdvise = 0; +} + + +// Cancel any notification currently scheduled. This is called by the owning +// window object when it is told to stop streaming. If there is no timer link +// outstanding then calling this is benign otherwise we go ahead and cancel +// We must always reset the render event as the quality management code can +// signal immediate rendering by setting the event without setting an advise +// link. If we're subsequently stopped and run the first attempt to setup an +// advise link with the reference clock will find the event still signalled + +HRESULT CAudioSwitchRenderer::CancelNotification() +{ + ASSERT(m_dwAdvise == 0 || m_pClock); + DWORD_PTR dwAdvise = m_dwAdvise; + + // Have we a live advise link + + if (m_dwAdvise) + { + m_pClock->Unadvise(m_dwAdvise); + SignalTimerFired(); + ASSERT(m_dwAdvise == 0); + } + + // Clear the event and return our status + + m_RenderEvent.Reset(); + return (dwAdvise ? S_OK : S_FALSE); +} + + +// Responsible for setting up one shot advise links with the clock +// Return FALSE if the sample is to be dropped (not drawn at all) +// Return TRUE if the sample is to be drawn and in this case also +// arrange for m_RenderEvent to be set at the appropriate time + +BOOL CAudioSwitchRenderer::ScheduleSample(IMediaSample *pMediaSample) +{ + REFERENCE_TIME StartSample, EndSample; + + // Is someone pulling our leg + + if (pMediaSample == NULL) + { + return FALSE; + } + + // Get the next sample due up for rendering. If there aren't any ready + // then GetNextSampleTimes returns an error. If there is one to be done + // then it succeeds and yields the sample times. If it is due now then + // it returns S_OK other if it's to be done when due it returns S_FALSE + + HRESULT hr = GetSampleTimes(pMediaSample, &StartSample, &EndSample); + if (FAILED(hr)) + { + return FALSE; + } + + // If we don't have a reference clock then we cannot set up the advise + // time so we simply set the event indicating an image to render. This + // will cause us to run flat out without any timing or synchronisation + + if (hr == S_OK) + { + EXECUTE_ASSERT(SetEvent((HANDLE) m_RenderEvent)); + return TRUE; + } + + ASSERT(m_dwAdvise == 0); + ASSERT(m_pClock); + ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent, 0)); + + // We do have a valid reference clock interface so we can ask it to + // set an event when the image comes due for rendering. We pass in + // the reference time we were told to start at and also the current + // stream time which is the offset from the start reference time + + hr = m_pClock->AdviseTime( + (REFERENCE_TIME) m_tStart, // Start run time + StartSample, // Stream time + (HEVENT)(HANDLE) m_RenderEvent, // Render notification + &m_dwAdvise); // Advise cookie + + if (SUCCEEDED(hr)) + { + return TRUE; + } + + // We could not schedule the next sample for rendering despite the fact + // we have a valid sample here. This is a fair indication that either + // the system clock is wrong or the time stamp for the sample is duff + + ASSERT(m_dwAdvise == 0); + return FALSE; +} + + +// This is called when a sample comes due for rendering. We pass the sample +// on to the derived class. After rendering we will initialise the timer for +// the next sample, NOTE signal that the last one fired first, if we don't +// do this it thinks there is still one outstanding that hasn't completed + +HRESULT CAudioSwitchRenderer::Render(IMediaSample *pMediaSample) +{ + // If the media sample is NULL then we will have been notified by the + // clock that another sample is ready but in the mean time someone has + // stopped us streaming which causes the next sample to be released + + if (pMediaSample == NULL) + { + return S_FALSE; + } + + // If we have stopped streaming then don't render any more samples, the + // thread that got in and locked us and then reset this flag does not + // clear the pending sample as we can use it to refresh any output device + + if (m_bStreaming == FALSE) + { + return S_FALSE; + } + + // Time how long the rendering takes + + OnRenderStart(pMediaSample); + DoRenderSample(pMediaSample); + OnRenderEnd(pMediaSample); + + return NOERROR; +} + + +// Checks if there is a sample waiting at the renderer + +BOOL CAudioSwitchRenderer::HaveCurrentSample() +{ + CAutoLock cRendererLock(&m_RendererLock); + return (m_pMediaSample[m_inputSelected] == NULL ? FALSE : TRUE); +} + + +// Returns the current sample waiting at the video renderer. We AddRef the +// sample before returning so that should it come due for rendering the +// person who called this method will hold the remaining reference count +// that will stop the sample being added back onto the allocator free list + +IMediaSample *CAudioSwitchRenderer::GetCurrentSample() +{ + CAutoLock cRendererLock(&m_RendererLock); + if (m_pMediaSample[m_inputSelected]) + { + m_pMediaSample[m_inputSelected]->AddRef(); + } + return m_pMediaSample[m_inputSelected]; +} + + +// Called when the source delivers us a sample. We go through a few checks to +// make sure the sample can be rendered. If we are running (streaming) then we +// have the sample scheduled with the reference clock, if we are not streaming +// then we have received an sample in paused mode so we can complete any state +// transition. On leaving this function everything will be unlocked so an app +// thread may get in and change our state to stopped (for example) in which +// case it will also signal the thread event so that our wait call is stopped + +HRESULT CAudioSwitchRenderer::PrepareReceive(IMediaSample *pMediaSample) +{ + CAutoLock cRendererLock(&m_InterfaceLock); + m_bInReceive = TRUE; + + // Check our flushing and filter state + + HRESULT hr = m_pInputPin[m_inputSelected]->CBaseInputPin::Receive(pMediaSample); + + if (hr != NOERROR) + { + m_bInReceive = FALSE; + return E_FAIL; + } + + // Has the type changed on a media sample. We do all rendering + // synchronously on the source thread, which has a side effect + // that only one buffer is ever outstanding. Therefore when we + // have Receive called we can go ahead and change the format + // Since the format change can cause a SendMessage we just don't + // lock + if (m_pInputPin[m_inputSelected]->SampleProps()->pMediaType) + { + m_pInputPin[m_inputSelected]->SetMediaType((CMediaType *)m_pInputPin[m_inputSelected]->SampleProps()->pMediaType); + } + + + CAutoLock cSampleLock(&m_RendererLock); + + ASSERT(IsActive() == TRUE); + ASSERT(m_pInputPin[m_inputSelected]->IsFlushing() == FALSE); + ASSERT(m_pInputPin[m_inputSelected]->IsConnected() == TRUE); + ASSERT(m_pMediaSample[m_inputSelected] == NULL); + + // Return an error if we already have a sample waiting for rendering + // source pins must serialise the Receive calls - we also check that + // no data is being sent after the source signalled an end of stream + + if (m_pMediaSample[m_inputSelected] || m_bEOS || m_bAbort) + { + Ready(); + m_bInReceive = FALSE; + return E_UNEXPECTED; + } + + // Store the media times from this sample + if (m_pPosition) m_pPosition->RegisterMediaTime(pMediaSample); + + // Schedule the next sample if we are streaming + + if ((m_bStreaming == TRUE) && (ScheduleSample(pMediaSample) == FALSE)) + { + ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent, 0)); + ASSERT(CancelNotification() == S_FALSE); + m_bInReceive = FALSE; + return VFW_E_SAMPLE_REJECTED; + } + + // Store the sample end time for EC_COMPLETE handling + m_SignalTime = m_pInputPin[m_inputSelected]->SampleProps()->tStop; + + // BEWARE we sometimes keep the sample even after returning the thread to + // the source filter such as when we go into a stopped state (we keep it + // to refresh the device with) so we must AddRef it to keep it safely. If + // we start flushing the source thread is released and any sample waiting + // will be released otherwise GetBuffer may never return (see BeginFlush) + + m_pMediaSample[m_inputSelected] = pMediaSample; + m_pMediaSample[m_inputSelected]->AddRef(); + + if (m_bStreaming == FALSE) + { + SetRepaintStatus(TRUE); + } + return NOERROR; +} + + +// Called by the source filter when we have a sample to render. Under normal +// circumstances we set an advise link with the clock, wait for the time to +// arrive and then render the data using the PURE virtual DoRenderSample that +// the derived class will have overriden. After rendering the sample we may +// also signal EOS if it was the last one sent before EndOfStream was called + +HRESULT CAudioSwitchRenderer::Receive(IMediaSample *pSample) +{ + ASSERT(pSample); + + // It may return VFW_E_SAMPLE_REJECTED code to say don't bother + + HRESULT hr = PrepareReceive(pSample); + ASSERT(m_bInReceive == SUCCEEDED(hr)); + if (FAILED(hr)) + { + if (hr == VFW_E_SAMPLE_REJECTED) + { + return NOERROR; + } + return hr; + } + + // We realize the palette in "PrepareRender()" so we have to give away the + // filter lock here. + if (m_State == State_Paused) + { + PrepareRender(); + // no need to use InterlockedExchange + m_bInReceive = FALSE; + { + // We must hold both these locks + CAutoLock cRendererLock(&m_InterfaceLock); + if (m_State == State_Stopped) + return NOERROR; + m_bInReceive = TRUE; + } + Ready(); + } + // Having set an advise link with the clock we sit and wait. We may be + // awoken by the clock firing or by a state change. The rendering call + // will lock the critical section and check we can still render the data + + hr = WaitForRenderTime(); + if (FAILED(hr)) + { + m_bInReceive = FALSE; + return NOERROR; + } + + PrepareRender(); + + // Set this here and poll it until we work out the locking correctly + // It can't be right that the streaming stuff grabs the interface + // lock - after all we want to be able to wait for this stuff + // to complete + m_bInReceive = FALSE; + + // We must hold both these locks + CAutoLock cRendererLock(&m_InterfaceLock); + + // since we gave away the filter wide lock, the sate of the filter could + // have chnaged to Stopped + if (m_State == State_Stopped) + return NOERROR; + + CAutoLock cSampleLock(&m_RendererLock); + + // Deal with this sample + + Render(m_pMediaSample[m_inputSelected]); + ClearPendingSample(); + SendEndOfStream(); + CancelNotification(); + return NOERROR; +} + + +// This is called when we stop or are inactivated to clear the pending sample +// We release the media sample interface so that they can be allocated to the +// source filter again, unless of course we are changing state to inactive in +// which case GetBuffer will return an error. We must also reset the current +// media sample to NULL so that we know we do not currently have an image + +HRESULT CAudioSwitchRenderer::ClearPendingSample() +{ + CAutoLock cRendererLock(&m_RendererLock); + for (int i = 0;i < 16;i++) + { + if (m_pMediaSample[i]) + { + m_pMediaSample[i]->Release(); + m_pMediaSample[i] = NULL; + } + } + return NOERROR; +} + +// Do the timer callback work +void CAudioSwitchRenderer::TimerCallback() +{ + // Lock for synchronization (but don't hold this lock when calling + // timeKillEvent) + CAutoLock cRendererLock(&m_RendererLock); + + // See if we should signal end of stream now + + if (m_EndOfStreamTimer) + { + m_EndOfStreamTimer = 0; + SendEndOfStream(); + } +} + + +// If we are at the end of the stream signal the filter graph but do not set +// the state flag back to FALSE. Once we drop off the end of the stream we +// leave the flag set (until a subsequent ResetEndOfStream). Each sample we +// get delivered will update m_SignalTime to be the last sample's end time. +// We must wait this long before signalling end of stream to the filtergraph + +#define TIMEOUT_DELIVERYWAIT 50 +#define TIMEOUT_RESOLUTION 10 + +HRESULT CAudioSwitchRenderer::SendEndOfStream() +{ + ASSERT(CritCheckIn(&m_RendererLock)); + if (m_bEOS == FALSE || m_bEOSDelivered || m_EndOfStreamTimer) + { + return NOERROR; + } + + // If there is no clock then signal immediately + if (m_pClock == NULL) + { + return NotifyEndOfStream(); + } + + // How long into the future is the delivery time + + REFERENCE_TIME Signal = m_tStart + m_SignalTime; + REFERENCE_TIME CurrentTime; + m_pClock->GetTime(&CurrentTime); + LONG Delay = LONG((Signal - CurrentTime) / 10000); + + // Dump the timing information to the debugger + + NOTE1("Delay until end of stream delivery %d", Delay); + NOTE1("Current %s", (LPCTSTR)CDisp((LONGLONG)CurrentTime)); + NOTE1("Signal %s", (LPCTSTR)CDisp((LONGLONG)Signal)); + + // Wait for the delivery time to arrive + + if (Delay < TIMEOUT_DELIVERYWAIT) + { + return NotifyEndOfStream(); + } + + // Signal a timer callback on another worker thread + + m_EndOfStreamTimer = timeSetEvent((UINT) Delay, // Period of timer + TIMEOUT_RESOLUTION, // Timer resolution + EndOfStreamTimer, // Callback function + DWORD_PTR(this), // Used information + TIME_ONESHOT); // Type of callback + if (m_EndOfStreamTimer == 0) + { + return NotifyEndOfStream(); + } + return NOERROR; +} + + +// Signals EC_COMPLETE to the filtergraph manager + +HRESULT CAudioSwitchRenderer::NotifyEndOfStream() +{ + CAutoLock cRendererLock(&m_RendererLock); + ASSERT(m_bEOS == TRUE); + ASSERT(m_bEOSDelivered == FALSE); + ASSERT(m_EndOfStreamTimer == 0); + + // Has the filter changed state + + if (m_bStreaming == FALSE) + { + ASSERT(m_EndOfStreamTimer == 0); + return NOERROR; + } + + // Reset the end of stream timer + m_EndOfStreamTimer = 0; + + // If we've been using the IMediaPosition interface, set it's start + // and end media "times" to the stop position by hand. This ensures + // that we actually get to the end, even if the MPEG guestimate has + // been bad or if the quality management dropped the last few frames + + if (m_pPosition) m_pPosition->EOS(); + m_bEOSDelivered = TRUE; + NOTE("Sending EC_COMPLETE..."); + return NotifyEvent(EC_COMPLETE, S_OK, (LONG_PTR)(IBaseFilter *)this); +} + + +// Reset the end of stream flag, this is typically called when we transfer to +// stopped states since that resets the current position back to the start so +// we will receive more samples or another EndOfStream if there aren't any. We +// keep two separate flags one to say we have run off the end of the stream +// (this is the m_bEOS flag) and another to say we have delivered EC_COMPLETE +// to the filter graph. We need the latter otherwise we can end up sending an +// EC_COMPLETE every time the source changes state and calls our EndOfStream + +HRESULT CAudioSwitchRenderer::ResetEndOfStream() +{ + ResetEndOfStreamTimer(); + CAutoLock cRendererLock(&m_RendererLock); + + m_bEOS = FALSE; + m_bEOSDelivered = FALSE; + m_SignalTime = 0; + + return NOERROR; +} + + +// Kills any outstanding end of stream timer + +void CAudioSwitchRenderer::ResetEndOfStreamTimer() +{ + ASSERT(CritCheckOut(&m_RendererLock)); + if (m_EndOfStreamTimer) + { + timeKillEvent(m_EndOfStreamTimer); + m_EndOfStreamTimer = 0; + } +} + + +// This is called when we start running so that we can schedule any pending +// image we have with the clock and display any timing information. If we +// don't have any sample but we have queued an EOS flag then we send it. If +// we do have a sample then we wait until that has been rendered before we +// signal the filter graph otherwise we may change state before it's done + +HRESULT CAudioSwitchRenderer::StartStreaming() +{ + CAutoLock cRendererLock(&m_RendererLock); + if (m_bStreaming == TRUE) + { + return NOERROR; + } + + // Reset the streaming times ready for running + + m_bStreaming = TRUE; + timeBeginPeriod(1); + OnStartStreaming(); + + // There should be no outstanding advise + ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent, 0)); + ASSERT(CancelNotification() == S_FALSE); + + // If we have an EOS and no data then deliver it now + + if (m_pMediaSample[m_inputSelected] == NULL) + { + return SendEndOfStream(); + } + + // Have the data rendered + + ASSERT(m_pMediaSample[m_inputSelected]); + if (!ScheduleSample(m_pMediaSample[m_inputSelected])) + m_RenderEvent.Set(); + + return NOERROR; +} + + +// This is called when we stop streaming so that we can set our internal flag +// indicating we are not now to schedule any more samples arriving. The state +// change methods in the filter implementation take care of cancelling any +// clock advise link we have set up and clearing any pending sample we have + +HRESULT CAudioSwitchRenderer::StopStreaming() +{ + CAutoLock cRendererLock(&m_RendererLock); + m_bEOSDelivered = FALSE; + + if (m_bStreaming == TRUE) + { + m_bStreaming = FALSE; + OnStopStreaming(); + timeEndPeriod(1); + } + return NOERROR; +} + + +// We have a boolean flag that is reset when we have signalled EC_REPAINT to +// the filter graph. We set this when we receive an image so that should any +// conditions arise again we can send another one. By having a flag we ensure +// we don't flood the filter graph with redundant calls. We do not set the +// event when we receive an EndOfStream call since there is no point in us +// sending further EC_REPAINTs. In particular the AutoShowWindow method and +// the DirectDraw object use this method to control the window repainting + +void CAudioSwitchRenderer::SetRepaintStatus(BOOL bRepaint) +{ + CAutoLock cSampleLock(&m_RendererLock); + m_bRepaintStatus = bRepaint; +} + + +// Pass the window handle to the upstream filter + +void CAudioSwitchRenderer::SendNotifyWindow(IPin *pPin, HWND hwnd) +{ + IMediaEventSink *pSink; + + // Does the pin support IMediaEventSink + HRESULT hr = pPin->QueryInterface(IID_IMediaEventSink, (void **) & pSink); + if (SUCCEEDED(hr)) + { + pSink->Notify(EC_NOTIFY_WINDOW, LONG_PTR(hwnd), 0); + pSink->Release(); + } + NotifyEvent(EC_NOTIFY_WINDOW, LONG_PTR(hwnd), 0); +} + + +// Signal an EC_REPAINT to the filter graph. This can be used to have data +// sent to us. For example when a video window is first displayed it may +// not have an image to display, at which point it signals EC_REPAINT. The +// filtergraph will either pause the graph if stopped or if already paused +// it will call put_CurrentPosition of the current position. Setting the +// current position to itself has the stream flushed and the image resent + +#define RLOG(_x_) DbgLog((LOG_TRACE,1,TEXT(_x_))); + +void CAudioSwitchRenderer::SendRepaint() +{ + CAutoLock cSampleLock(&m_RendererLock); + ASSERT(m_pInputPin[m_inputSelected]); + + // We should not send repaint notifications when... + // - An end of stream has been notified + // - Our input pin is being flushed + // - The input pin is not connected + // - We have aborted a video playback + // - There is a repaint already sent + + if (m_bAbort == FALSE) + { + if (m_pInputPin[m_inputSelected]->IsConnected() == TRUE) + { + if (m_pInputPin[m_inputSelected]->IsFlushing() == FALSE) + { + if (IsEndOfStream() == FALSE) + { + if (m_bRepaintStatus == TRUE) + { + for (int i = 0;i < 16;i++) + { + IPin *pPin = (IPin *) m_pInputPin[i]; + if (!pPin) continue; + NotifyEvent(EC_REPAINT, (LONG_PTR) pPin, 0); + SetRepaintStatus(FALSE); + RLOG("Sending repaint"); + } + } + } + } + } + } +} + + +// When a video window detects a display change (WM_DISPLAYCHANGE message) it +// can send an EC_DISPLAY_CHANGED event code along with the renderer pin. The +// filtergraph will stop everyone and reconnect our input pin. As we're then +// reconnected we can accept the media type that matches the new display mode +// since we may no longer be able to draw the current image type efficiently + +BOOL CAudioSwitchRenderer::OnDisplayChange() +{ + // Ignore if we are not connected yet + + CAutoLock cSampleLock(&m_RendererLock); + int n = 0; + for (int i = 0;i < 16;i++) + if (!m_pInputPin[i] || m_pInputPin[i]->IsConnected() == FALSE) n++; + if (n == 16) + return FALSE; + + RLOG("Notification of EC_DISPLAY_CHANGE"); + + // Pass our input pin as parameter on the event + + for (int i = 0;i < 16;i++) + if (m_pInputPin[i] && m_pInputPin[i]->IsConnected()) + { + IPin *pPin = (IPin *) m_pInputPin[i]; + m_pInputPin[i]->AddRef(); + NotifyEvent(EC_DISPLAY_CHANGED, (LONG_PTR) pPin, 0); + SetAbortSignal(TRUE); + ClearPendingSample(); + m_pInputPin[i]->Release(); + } + + return TRUE; +} + + +// Called just before we start drawing. +// Store the current time in m_trRenderStart to allow the rendering time to be +// logged. Log the time stamp of the sample and how late it is (neg is early) + +void CAudioSwitchRenderer::OnRenderStart(IMediaSample *pMediaSample) +{ +#ifdef PERF + REFERENCE_TIME trStart, trEnd; + pMediaSample->GetTime(&trStart, &trEnd); + + MSR_INTEGER(m_idBaseStamp, (int)trStart); // dump low order 32 bits + + m_pClock->GetTime(&m_trRenderStart); + MSR_INTEGER(0, (int)m_trRenderStart); + REFERENCE_TIME trStream; + trStream = m_trRenderStart - m_tStart; // convert reftime to stream time + MSR_INTEGER(0, (int)trStream); + + const int trLate = (int)(trStream - trStart); + MSR_INTEGER(m_idBaseAccuracy, trLate / 10000); // dump in mSec +#endif + +} // OnRenderStart + + +// Called directly after drawing an image. +// calculate the time spent drawing and log it. + +void CAudioSwitchRenderer::OnRenderEnd(IMediaSample *pMediaSample) +{ +#ifdef PERF + REFERENCE_TIME trNow; + m_pClock->GetTime(&trNow); + MSR_INTEGER(0, (int)trNow); + int t = (int)((trNow - m_trRenderStart) / 10000); // convert UNITS->msec + MSR_INTEGER(m_idBaseRenderTime, t); +#endif +} // OnRenderEnd + +void CAudioSwitchRenderer::SetSelectedInput(int n) +{ + if (m_inputSelected == n) return ; + if (n > 15 || n < 0) return ; + ClearPendingSample(); + m_inputSelected = n; + GetSelectedPin()->NotifyMediaType(); +} + +int CAudioSwitchRenderer::GetSelectedInput() +{ + return m_inputSelected; +} + +int CAudioSwitchRenderer::GetConnectedInputsCount() +{ + int n = 0; + for (int i = 0;i < 16;i++) + { + if (m_pInputPin[i] && m_pInputPin[i]->IsConnected()) n++; + } + return n; +} + +// Constructor must be passed the base renderer object + +CAudioSwitchRendererInputPin::CAudioSwitchRendererInputPin(CAudioSwitchRenderer *pRenderer, + HRESULT *phr, + LPCWSTR pPinName) : + CBaseInputPin(NAME("Renderer pin"), + pRenderer, + &pRenderer->m_InterfaceLock, + (HRESULT *) phr, + pPinName) +{ + m_pRenderer = pRenderer; + ASSERT(m_pRenderer); +} + + +// Signals end of data stream on the input pin + +STDMETHODIMP CAudioSwitchRendererInputPin::EndOfStream() +{ + HRESULT hr = NOERROR; + if (m_pRenderer->GetSelectedPin() == this) + { + CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock); + CAutoLock cSampleLock(&m_pRenderer->m_RendererLock); + + // Make sure we're streaming ok + + hr = CheckStreaming(); + if (hr != NOERROR) + { + return hr; + } + + // Pass it onto the renderer + + hr = m_pRenderer->EndOfStream(); + } + if (SUCCEEDED(hr)) + { + hr = CBaseInputPin::EndOfStream(); + } + return hr; +} + + +// Signals start of flushing on the input pin - we do the final reset end of +// stream with the renderer lock unlocked but with the interface lock locked +// We must do this because we call timeKillEvent, our timer callback method +// has to take the renderer lock to serialise our state. Therefore holding a +// renderer lock when calling timeKillEvent could cause a deadlock condition + +STDMETHODIMP CAudioSwitchRendererInputPin::BeginFlush() +{ + if (m_pRenderer->GetSelectedPin() == this) + { + CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock); + { + CAutoLock cSampleLock(&m_pRenderer->m_RendererLock); + CBaseInputPin::BeginFlush(); + m_pRenderer->BeginFlush(); + } + return m_pRenderer->ResetEndOfStream(); + } + else return CBaseInputPin::BeginFlush(); +} + + +// Signals end of flushing on the input pin + +STDMETHODIMP CAudioSwitchRendererInputPin::EndFlush() +{ + HRESULT hr = NOERROR; + if (m_pRenderer->GetSelectedPin() == this) + { + CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock); + CAutoLock cSampleLock(&m_pRenderer->m_RendererLock); + hr = m_pRenderer->EndFlush(); + } + if (SUCCEEDED(hr)) + { + hr = CBaseInputPin::EndFlush(); + } + return hr; +} + + +// Pass the sample straight through to the renderer object + +STDMETHODIMP CAudioSwitchRendererInputPin::Receive(IMediaSample *pSample) +{ + if (m_pRenderer->GetSelectedPin() != this) + return NOERROR; + return m_pRenderer->Receive(pSample); +} + + +// Called when the input pin is disconnected + +HRESULT CAudioSwitchRendererInputPin::BreakConnect() +{ + if (m_pRenderer->GetSelectedPin() == this) + { + HRESULT hr = m_pRenderer->BreakConnect(); + if (FAILED(hr)) + { + return hr; + } + } + return CBaseInputPin::BreakConnect(); +} + + +// Called when the input pin is connected + +HRESULT CAudioSwitchRendererInputPin::CompleteConnect(IPin *pReceivePin) +{ + if (m_pRenderer->GetSelectedPin() == this) + { + HRESULT hr = m_pRenderer->CompleteConnect(pReceivePin); + if (FAILED(hr)) + { + return hr; + } + } + return CBaseInputPin::CompleteConnect(pReceivePin); +} + + +// Give the pin id of our one and only pin + +STDMETHODIMP CAudioSwitchRendererInputPin::QueryId(LPWSTR *Id) +{ + CheckPointer(Id, E_POINTER); + + *Id = (LPWSTR)CoTaskMemAlloc(8); + if (*Id == NULL) + { + return E_OUTOFMEMORY; + } + StringCbCopyW(*Id, 8, m_pName); + return NOERROR; +} + + +// Will the filter accept this media type + +HRESULT CAudioSwitchRendererInputPin::CheckMediaType(const CMediaType *pmt) +{ + return m_pRenderer->CheckMediaType(pmt); +} + + +// Called when we go paused or running + +HRESULT CAudioSwitchRendererInputPin::Active() +{ + return m_pRenderer->Active(); +} + + +// Called when we go into a stopped state + +HRESULT CAudioSwitchRendererInputPin::Inactive() +{ + return m_pRenderer->Inactive(); +} + + +// Tell derived classes about the media type agreed + +HRESULT CAudioSwitchRendererInputPin::SetMediaType(const CMediaType *pmt) +{ + HRESULT hr = CBaseInputPin::SetMediaType(pmt); + if (FAILED(hr)) + { + return hr; + } + m_mt = *pmt; + if (m_pRenderer->GetSelectedPin() != this) + return NOERROR; + return m_pRenderer->SetMediaType(pmt); +} + +HRESULT CAudioSwitchRendererInputPin::NotifyMediaType() +{ + if (m_pRenderer->GetSelectedPin() != this) + return NOERROR; + return m_pRenderer->SetMediaType(&m_mt); +} diff --git a/Src/Plugins/Input/in_dshow/audioswitch.h b/Src/Plugins/Input/in_dshow/audioswitch.h new file mode 100644 index 00000000..0db6d929 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/audioswitch.h @@ -0,0 +1,216 @@ +#ifndef _AUDIOSWITCH_H +#define _AUDIOSWITCH_H + +#include <streams.h> + +class CAudioSwitchRenderer; +class CAudioSwitchRendererInputPin; + +class CAudioSwitchRendererInputPin : public CBaseInputPin +{ +protected: + CAudioSwitchRenderer *m_pRenderer; + CMediaType m_mt; + +public: + HRESULT NotifyMediaType(); + + CAudioSwitchRendererInputPin(CAudioSwitchRenderer *pRenderer, + HRESULT *phr, + LPCWSTR Name); + + // Overriden from the base pin classes + + HRESULT BreakConnect(); + HRESULT CompleteConnect(IPin *pReceivePin); + HRESULT SetMediaType(const CMediaType *pmt); + HRESULT CheckMediaType(const CMediaType *pmt); + HRESULT Active(); + HRESULT Inactive(); + + // Add rendering behaviour to interface functions + + STDMETHODIMP QueryId(LPWSTR *Id); + STDMETHODIMP EndOfStream(); + STDMETHODIMP BeginFlush(); + STDMETHODIMP EndFlush(); + STDMETHODIMP Receive(IMediaSample *pMediaSample); + + // Helper + IMemAllocator inline *Allocator() const + { + return m_pAllocator; + } +}; + + +class CAudioSwitchRenderer : public CBaseFilter +{ +protected: + + friend class CAudioSwitchRendererInputPin; + + friend void CALLBACK EndOfStreamTimer(UINT uID, // Timer identifier + UINT uMsg, // Not currently used + DWORD_PTR dwUser, // User information + DWORD_PTR dw1, // Windows reserved + DWORD_PTR dw2); // Is also reserved + + CRendererPosPassThru *m_pPosition; // Media seeking pass by object + CAMEvent m_RenderEvent; // Used to signal timer events + CAMEvent m_ThreadSignal; // Signalled to release worker thread + CAMEvent m_evComplete; // Signalled when state complete + BOOL m_bAbort; // Stop us from rendering more data + BOOL m_bStreaming; // Are we currently streaming + DWORD_PTR m_dwAdvise; // Timer advise cookie + IMediaSample *m_pMediaSample[16]; // Current image media samples + BOOL m_bEOS; // Any more samples in the stream + BOOL m_bEOSDelivered; // Have we delivered an EC_COMPLETE + CAudioSwitchRendererInputPin *m_pInputPin[16]; // Our renderer input pin objects + CCritSec m_InterfaceLock; // Critical section for interfaces + CCritSec m_RendererLock; // Controls access to internals + IQualityControl * m_pQSink; // QualityControl sink + BOOL m_bRepaintStatus; // Can we signal an EC_REPAINT + // Avoid some deadlocks by tracking filter during stop + volatile BOOL m_bInReceive; // Inside Receive between PrepareReceive + // And actually processing the sample + REFERENCE_TIME m_SignalTime; // Time when we signal EC_COMPLETE + UINT m_EndOfStreamTimer; // Used to signal end of stream + int m_inputSelected; // input selected for rendering (0 to 16) + +public: + + CAudioSwitchRenderer(REFCLSID RenderClass, // CLSID for this renderer + TCHAR *pName, // Debug ONLY description + LPUNKNOWN pUnk, // Aggregated owner object + HRESULT *phr); // General OLE return code + + virtual ~CAudioSwitchRenderer(); + + // Overriden to say what interfaces we support and where + + virtual HRESULT GetMediaPositionInterface(REFIID riid,void **ppv); + STDMETHODIMP NonDelegatingQueryInterface(REFIID, void **); + + virtual HRESULT SourceThreadCanWait(BOOL bCanWait); + +#ifdef DEBUG + // Debug only dump of the renderer state + void DisplayRendererState(); +#endif + virtual HRESULT WaitForRenderTime(); + virtual HRESULT CompleteStateChange(FILTER_STATE OldState); + + // Return internal information about this filter + + BOOL IsEndOfStream() { return m_bEOS; }; + BOOL IsEndOfStreamDelivered() { return m_bEOSDelivered; }; + BOOL IsStreaming() { return m_bStreaming; }; + void SetAbortSignal(BOOL bAbort) { m_bAbort = bAbort; }; + CAMEvent *GetRenderEvent() { return &m_RenderEvent; }; + + // Permit access to the transition state + + void Ready() { m_evComplete.Set(); }; + void NotReady() { m_evComplete.Reset(); }; + BOOL CheckReady() { return m_evComplete.Check(); }; + + virtual int GetPinCount(); + virtual CBasePin *GetPin(int n); + FILTER_STATE GetRealState(); + void SendRepaint(); + void SendNotifyWindow(IPin *pPin,HWND hwnd); + BOOL OnDisplayChange(); + void SetRepaintStatus(BOOL bRepaint); + + // Override the filter and pin interface functions + + STDMETHODIMP Stop(); + STDMETHODIMP Pause(); + STDMETHODIMP Run(REFERENCE_TIME StartTime); + STDMETHODIMP GetState(DWORD dwMSecs,FILTER_STATE *State); + STDMETHODIMP FindPin(LPCWSTR Id, IPin **ppPin); + + // These are available for a quality management implementation + + virtual void OnRenderStart(IMediaSample *pMediaSample); + virtual void OnRenderEnd(IMediaSample *pMediaSample); + virtual HRESULT OnStartStreaming() { return NOERROR; }; + virtual HRESULT OnStopStreaming() { return NOERROR; }; + virtual void OnWaitStart() { }; + virtual void OnWaitEnd() { }; + virtual void PrepareRender() { }; + +#ifdef PERF + REFERENCE_TIME m_trRenderStart; // Just before we started drawing + // Set in OnRenderStart, Used in OnRenderEnd + int m_idBaseStamp; // MSR_id for frame time stamp + int m_idBaseRenderTime; // MSR_id for true wait time + int m_idBaseAccuracy; // MSR_id for time frame is late (int) +#endif + + // Quality management implementation for scheduling rendering + + virtual BOOL ScheduleSample(IMediaSample *pMediaSample); + virtual HRESULT GetSampleTimes(IMediaSample *pMediaSample, + REFERENCE_TIME *pStartTime, + REFERENCE_TIME *pEndTime); + + virtual HRESULT ShouldDrawSampleNow(IMediaSample *pMediaSample, + REFERENCE_TIME *ptrStart, + REFERENCE_TIME *ptrEnd); + + // Lots of end of stream complexities + + void TimerCallback(); + void ResetEndOfStreamTimer(); + HRESULT NotifyEndOfStream(); + virtual HRESULT SendEndOfStream(); + virtual HRESULT ResetEndOfStream(); + virtual HRESULT EndOfStream(); + + // Rendering is based around the clock + + void SignalTimerFired(); + virtual HRESULT CancelNotification(); + virtual HRESULT ClearPendingSample(); + + // Called when the filter changes state + + virtual HRESULT Active(); + virtual HRESULT Inactive(); + virtual HRESULT StartStreaming(); + virtual HRESULT StopStreaming(); + virtual HRESULT BeginFlush(); + virtual HRESULT EndFlush(); + + // Deal with connections and type changes + + virtual HRESULT BreakConnect(); + virtual HRESULT SetMediaType(const CMediaType *pmt); + virtual HRESULT CompleteConnect(IPin *pReceivePin); + + // These look after the handling of data samples + + virtual HRESULT PrepareReceive(IMediaSample *pMediaSample); + virtual HRESULT Receive(IMediaSample *pMediaSample); + virtual BOOL HaveCurrentSample(); + virtual IMediaSample *GetCurrentSample(); + virtual HRESULT Render(IMediaSample *pMediaSample); + + // Derived classes MUST override these + virtual HRESULT DoRenderSample(IMediaSample *pMediaSample) PURE; + virtual HRESULT CheckMediaType(const CMediaType *) PURE; + + // Helper + void WaitForReceiveToComplete(); + + CAudioSwitchRendererInputPin *GetSelectedPin() { return m_pInputPin[m_inputSelected]; } + + int GetConnectedInputsCount(); + int GetSelectedInput(); + void SetSelectedInput(int n); +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_dshow/base/amextra.cpp b/Src/Plugins/Input/in_dshow/base/amextra.cpp new file mode 100644 index 00000000..47bfd088 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/amextra.cpp @@ -0,0 +1,111 @@ +//------------------------------------------------------------------------------ +// File: AMExtra.cpp +// +// Desc: DirectShow base classes - implements CRenderedInputPin class. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> // DirectShow base class definitions +#include <mmsystem.h> // Needed for definition of timeGetTime +#include <limits.h> // Standard data type limit definitions +#include <measure.h> // Used for time critical log functions + +#include "amextra.h" + +#pragma warning(disable:4355) + +// Implements CRenderedInputPin class + +CRenderedInputPin::CRenderedInputPin(__in_opt LPCTSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName) : + CBaseInputPin(pObjectName, pFilter, pLock, phr, pName), + m_bAtEndOfStream(FALSE), + m_bCompleteNotified(FALSE) +{ +} +#ifdef UNICODE +CRenderedInputPin::CRenderedInputPin(__in_opt LPCSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName) : + CBaseInputPin(pObjectName, pFilter, pLock, phr, pName), + m_bAtEndOfStream(FALSE), + m_bCompleteNotified(FALSE) +{ +} +#endif + +// Flush end of stream condition - caller should do any +// necessary stream level locking before calling this + +STDMETHODIMP CRenderedInputPin::EndOfStream() +{ + HRESULT hr = CheckStreaming(); + + // Do EC_COMPLETE handling for rendered pins + if (S_OK == hr && !m_bAtEndOfStream) { + m_bAtEndOfStream = TRUE; + FILTER_STATE fs; + EXECUTE_ASSERT(SUCCEEDED(m_pFilter->GetState(0, &fs))); + if (fs == State_Running) { + DoCompleteHandling(); + } + } + return hr; +} + + +// Called to complete the flush + +STDMETHODIMP CRenderedInputPin::EndFlush() +{ + CAutoLock lck(m_pLock); + + // Clean up renderer state + m_bAtEndOfStream = FALSE; + m_bCompleteNotified = FALSE; + + return CBaseInputPin::EndFlush(); +} + + +// Notify of Run() from filter + +HRESULT CRenderedInputPin::Run(REFERENCE_TIME tStart) +{ + UNREFERENCED_PARAMETER(tStart); + m_bCompleteNotified = FALSE; + if (m_bAtEndOfStream) { + DoCompleteHandling(); + } + return S_OK; +} + + +// Clear status on going into paused state + +HRESULT CRenderedInputPin::Active() +{ + m_bAtEndOfStream = FALSE; + m_bCompleteNotified = FALSE; + return CBaseInputPin::Active(); +} + + +// Do stuff to deliver end of stream + +void CRenderedInputPin::DoCompleteHandling() +{ + ASSERT(m_bAtEndOfStream); + if (!m_bCompleteNotified) { + m_bCompleteNotified = TRUE; + m_pFilter->NotifyEvent(EC_COMPLETE, S_OK, (LONG_PTR)(IBaseFilter *)m_pFilter); + } +} + diff --git a/Src/Plugins/Input/in_dshow/base/amextra.h b/Src/Plugins/Input/in_dshow/base/amextra.h new file mode 100644 index 00000000..3caf64ce --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/amextra.h @@ -0,0 +1,56 @@ +//------------------------------------------------------------------------------ +// File: AMExtra.h +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __AMEXTRA__ +#define __AMEXTRA__ + +// Simple rendered input pin +// +// NOTE if your filter queues stuff before rendering then it may not be +// appropriate to use this class +// +// In that case queue the end of stream condition until the last sample +// is actually rendered and flush the condition appropriately + +class CRenderedInputPin : public CBaseInputPin +{ +public: + + CRenderedInputPin(__in_opt LPCTSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName); +#ifdef UNICODE + CRenderedInputPin(__in_opt LPCSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName); +#endif + + // Override methods to track end of stream state + STDMETHODIMP EndOfStream(); + STDMETHODIMP EndFlush(); + + HRESULT Active(); + HRESULT Run(REFERENCE_TIME tStart); + +protected: + + // Member variables to track state + BOOL m_bAtEndOfStream; // Set by EndOfStream + BOOL m_bCompleteNotified; // Set when we notify for EC_COMPLETE + +private: + void DoCompleteHandling(); +}; + +#endif // __AMEXTRA__ + diff --git a/Src/Plugins/Input/in_dshow/base/amfilter.cpp b/Src/Plugins/Input/in_dshow/base/amfilter.cpp new file mode 100644 index 00000000..9027553f --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/amfilter.cpp @@ -0,0 +1,5358 @@ +//------------------------------------------------------------------------------ +// File: AMFilter.cpp +// +// Desc: DirectShow base classes - implements class hierarchy for streams +// architecture. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +//===================================================================== +//===================================================================== +// The following classes are declared in this header: +// +// +// CBaseMediaFilter Basic IMediaFilter support (abstract class) +// CBaseFilter Support for IBaseFilter (incl. IMediaFilter) +// CEnumPins Enumerate input and output pins +// CEnumMediaTypes Enumerate the preferred pin formats +// CBasePin Abstract base class for IPin interface +// CBaseOutputPin Adds data provider member functions +// CBaseInputPin Implements IMemInputPin interface +// CMediaSample Basic transport unit for IMemInputPin +// CBaseAllocator General list guff for most allocators +// CMemAllocator Implements memory buffer allocation +// +//===================================================================== +//===================================================================== + +#include <streams.h> +#include <strsafe.h> + +#ifdef DXMPERF +#include "dxmperf.h" +#endif // DXMPERF + + +//===================================================================== +// Helpers +//===================================================================== +STDAPI CreateMemoryAllocator(__deref_out IMemAllocator **ppAllocator) +{ + return CoCreateInstance(CLSID_MemoryAllocator, + 0, + CLSCTX_INPROC_SERVER, + IID_IMemAllocator, + (void **)ppAllocator); +} + +// Put this one here rather than in ctlutil.cpp to avoid linking +// anything brought in by ctlutil.cpp +STDAPI CreatePosPassThru( + __in_opt LPUNKNOWN pAgg, + BOOL bRenderer, + IPin *pPin, + __deref_out IUnknown **ppPassThru +) +{ + *ppPassThru = NULL; + IUnknown *pUnkSeek; + HRESULT hr = CoCreateInstance(CLSID_SeekingPassThru, + pAgg, + CLSCTX_INPROC_SERVER, + IID_IUnknown, + (void **)&pUnkSeek + ); + if (FAILED(hr)) { + return hr; + } + + ISeekingPassThru *pPassThru; + hr = pUnkSeek->QueryInterface(IID_ISeekingPassThru, (void**)&pPassThru); + if (FAILED(hr)) { + pUnkSeek->Release(); + return hr; + } + hr = pPassThru->Init(bRenderer, pPin); + pPassThru->Release(); + if (FAILED(hr)) { + pUnkSeek->Release(); + return hr; + } + *ppPassThru = pUnkSeek; + return S_OK; +} + + + +#define CONNECT_TRACE_LEVEL 3 + +//===================================================================== +//===================================================================== +// Implements CBaseMediaFilter +//===================================================================== +//===================================================================== + + +/* Constructor */ + +CBaseMediaFilter::CBaseMediaFilter(__in_opt LPCTSTR pName, + __inout_opt LPUNKNOWN pUnk, + __in CCritSec *pLock, + REFCLSID clsid) : + CUnknown(pName, pUnk), + m_pLock(pLock), + m_clsid(clsid), + m_State(State_Stopped), + m_pClock(NULL) +{ +} + + +/* Destructor */ + +CBaseMediaFilter::~CBaseMediaFilter() +{ + // must be stopped, but can't call Stop here since + // our critsec has been destroyed. + + /* Release any clock we were using */ + + if (m_pClock) { + m_pClock->Release(); + m_pClock = NULL; + } +} + + +/* Override this to say what interfaces we support and where */ + +STDMETHODIMP +CBaseMediaFilter::NonDelegatingQueryInterface( + REFIID riid, + __deref_out void ** ppv) +{ + if (riid == IID_IMediaFilter) { + return GetInterface((IMediaFilter *) this, ppv); + } else if (riid == IID_IPersist) { + return GetInterface((IPersist *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } +} + +/* Return the filter's clsid */ +STDMETHODIMP +CBaseMediaFilter::GetClassID(__out CLSID *pClsID) +{ + CheckPointer(pClsID,E_POINTER); + ValidateReadWritePtr(pClsID,sizeof(CLSID)); + *pClsID = m_clsid; + return NOERROR; +} + +/* Override this if your state changes are not done synchronously */ + +STDMETHODIMP +CBaseMediaFilter::GetState(DWORD dwMSecs, __out FILTER_STATE *State) +{ + UNREFERENCED_PARAMETER(dwMSecs); + CheckPointer(State,E_POINTER); + ValidateReadWritePtr(State,sizeof(FILTER_STATE)); + + *State = m_State; + return S_OK; +} + + +/* Set the clock we will use for synchronisation */ + +STDMETHODIMP +CBaseMediaFilter::SetSyncSource(__inout_opt IReferenceClock *pClock) +{ + CAutoLock cObjectLock(m_pLock); + + // Ensure the new one does not go away - even if the same as the old + if (pClock) { + pClock->AddRef(); + } + + // if we have a clock, release it + if (m_pClock) { + m_pClock->Release(); + } + + // Set the new reference clock (might be NULL) + // Should we query it to ensure it is a clock? Consider for a debug build. + m_pClock = pClock; + + return NOERROR; +} + +/* Return the clock we are using for synchronisation */ +STDMETHODIMP +CBaseMediaFilter::GetSyncSource(__deref_out_opt IReferenceClock **pClock) +{ + CheckPointer(pClock,E_POINTER); + ValidateReadWritePtr(pClock,sizeof(IReferenceClock *)); + CAutoLock cObjectLock(m_pLock); + + if (m_pClock) { + // returning an interface... addref it... + m_pClock->AddRef(); + } + *pClock = (IReferenceClock*)m_pClock; + return NOERROR; +} + + +/* Put the filter into a stopped state */ + +STDMETHODIMP +CBaseMediaFilter::Stop() +{ + CAutoLock cObjectLock(m_pLock); + + m_State = State_Stopped; + return S_OK; +} + + +/* Put the filter into a paused state */ + +STDMETHODIMP +CBaseMediaFilter::Pause() +{ + CAutoLock cObjectLock(m_pLock); + + m_State = State_Paused; + return S_OK; +} + + +// Put the filter into a running state. + +// The time parameter is the offset to be added to the samples' +// stream time to get the reference time at which they should be presented. +// +// you can either add these two and compare it against the reference clock, +// or you can call CBaseMediaFilter::StreamTime and compare that against +// the sample timestamp. + +STDMETHODIMP +CBaseMediaFilter::Run(REFERENCE_TIME tStart) +{ + CAutoLock cObjectLock(m_pLock); + + // remember the stream time offset + m_tStart = tStart; + + if (m_State == State_Stopped){ + HRESULT hr = Pause(); + + if (FAILED(hr)) { + return hr; + } + } + m_State = State_Running; + return S_OK; +} + + +// +// return the current stream time - samples with start timestamps of this +// time or before should be rendered by now +HRESULT +CBaseMediaFilter::StreamTime(CRefTime& rtStream) +{ + // Caller must lock for synchronization + // We can't grab the filter lock because we want to be able to call + // this from worker threads without deadlocking + + if (m_pClock == NULL) { + return VFW_E_NO_CLOCK; + } + + // get the current reference time + HRESULT hr = m_pClock->GetTime((REFERENCE_TIME*)&rtStream); + if (FAILED(hr)) { + return hr; + } + + // subtract the stream offset to get stream time + rtStream -= m_tStart; + + return S_OK; +} + + +//===================================================================== +//===================================================================== +// Implements CBaseFilter +//===================================================================== +//===================================================================== + + +/* Override this to say what interfaces we support and where */ + +STDMETHODIMP CBaseFilter::NonDelegatingQueryInterface(REFIID riid, + __deref_out void **ppv) +{ + /* Do we have this interface */ + + if (riid == IID_IBaseFilter) { + return GetInterface((IBaseFilter *) this, ppv); + } else if (riid == IID_IMediaFilter) { + return GetInterface((IMediaFilter *) this, ppv); + } else if (riid == IID_IPersist) { + return GetInterface((IPersist *) this, ppv); + } else if (riid == IID_IAMovieSetup) { + return GetInterface((IAMovieSetup *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } +} + +#ifdef DEBUG +STDMETHODIMP_(ULONG) CBaseFilter::NonDelegatingRelease() +{ + if (m_cRef == 1) { + KASSERT(m_pGraph == NULL); + } + return CUnknown::NonDelegatingRelease(); +} +#endif + + +/* Constructor */ + +CBaseFilter::CBaseFilter(__in_opt LPCTSTR pName, + __inout_opt LPUNKNOWN pUnk, + __in CCritSec *pLock, + REFCLSID clsid) : + CUnknown( pName, pUnk ), + m_pLock(pLock), + m_clsid(clsid), + m_State(State_Stopped), + m_pClock(NULL), + m_pGraph(NULL), + m_pSink(NULL), + m_pName(NULL), + m_PinVersion(1) +{ +#ifdef DXMPERF + PERFLOG_CTOR( pName ? pName : L"CBaseFilter", (IBaseFilter *) this ); +#endif // DXMPERF + + ASSERT(pLock != NULL); +} + +/* Passes in a redundant HRESULT argument */ + +CBaseFilter::CBaseFilter(__in_opt LPCTSTR pName, + __in_opt LPUNKNOWN pUnk, + __in CCritSec *pLock, + REFCLSID clsid, + __inout HRESULT *phr) : + CUnknown( pName, pUnk ), + m_pLock(pLock), + m_clsid(clsid), + m_State(State_Stopped), + m_pClock(NULL), + m_pGraph(NULL), + m_pSink(NULL), + m_pName(NULL), + m_PinVersion(1) +{ +#ifdef DXMPERF + PERFLOG_CTOR( pName ? pName : L"CBaseFilter", (IBaseFilter *) this ); +#endif // DXMPERF + + ASSERT(pLock != NULL); + UNREFERENCED_PARAMETER(phr); +} + +#ifdef UNICODE +CBaseFilter::CBaseFilter(__in_opt LPCSTR pName, + __in_opt LPUNKNOWN pUnk, + __in CCritSec *pLock, + REFCLSID clsid) : + CUnknown( pName, pUnk ), + m_pLock(pLock), + m_clsid(clsid), + m_State(State_Stopped), + m_pClock(NULL), + m_pGraph(NULL), + m_pSink(NULL), + m_pName(NULL), + m_PinVersion(1) +{ +#ifdef DXMPERF + PERFLOG_CTOR( L"CBaseFilter", (IBaseFilter *) this ); +#endif // DXMPERF + + ASSERT(pLock != NULL); +} +CBaseFilter::CBaseFilter(__in_opt LPCSTR pName, + __in_opt LPUNKNOWN pUnk, + __in CCritSec *pLock, + REFCLSID clsid, + __inout HRESULT *phr) : + CUnknown( pName, pUnk ), + m_pLock(pLock), + m_clsid(clsid), + m_State(State_Stopped), + m_pClock(NULL), + m_pGraph(NULL), + m_pSink(NULL), + m_pName(NULL), + m_PinVersion(1) +{ +#ifdef DXMPERF + PERFLOG_CTOR( L"CBaseFilter", (IBaseFilter *) this ); +#endif // DXMPERF + + ASSERT(pLock != NULL); + UNREFERENCED_PARAMETER(phr); +} +#endif + +/* Destructor */ + +CBaseFilter::~CBaseFilter() +{ +#ifdef DXMPERF + PERFLOG_DTOR( L"CBaseFilter", (IBaseFilter *) this ); +#endif // DXMPERF + + // NOTE we do NOT hold references on the filtergraph for m_pGraph or m_pSink + // When we did we had the circular reference problem. Nothing would go away. + + delete[] m_pName; + + // must be stopped, but can't call Stop here since + // our critsec has been destroyed. + + /* Release any clock we were using */ + if (m_pClock) { + m_pClock->Release(); + m_pClock = NULL; + } +} + +/* Return the filter's clsid */ +STDMETHODIMP +CBaseFilter::GetClassID(__out CLSID *pClsID) +{ + CheckPointer(pClsID,E_POINTER); + ValidateReadWritePtr(pClsID,sizeof(CLSID)); + *pClsID = m_clsid; + return NOERROR; +} + +/* Override this if your state changes are not done synchronously */ +STDMETHODIMP +CBaseFilter::GetState(DWORD dwMSecs, __out FILTER_STATE *State) +{ + UNREFERENCED_PARAMETER(dwMSecs); + CheckPointer(State,E_POINTER); + ValidateReadWritePtr(State,sizeof(FILTER_STATE)); + + *State = m_State; + return S_OK; +} + + +/* Set the clock we will use for synchronisation */ + +STDMETHODIMP +CBaseFilter::SetSyncSource(__in_opt IReferenceClock *pClock) +{ + CAutoLock cObjectLock(m_pLock); + + // Ensure the new one does not go away - even if the same as the old + if (pClock) { + pClock->AddRef(); + } + + // if we have a clock, release it + if (m_pClock) { + m_pClock->Release(); + } + + // Set the new reference clock (might be NULL) + // Should we query it to ensure it is a clock? Consider for a debug build. + m_pClock = pClock; + + return NOERROR; +} + +/* Return the clock we are using for synchronisation */ +STDMETHODIMP +CBaseFilter::GetSyncSource(__deref_out_opt IReferenceClock **pClock) +{ + CheckPointer(pClock,E_POINTER); + ValidateReadWritePtr(pClock,sizeof(IReferenceClock *)); + CAutoLock cObjectLock(m_pLock); + + if (m_pClock) { + // returning an interface... addref it... + m_pClock->AddRef(); + } + *pClock = (IReferenceClock*)m_pClock; + return NOERROR; +} + + + +// override CBaseMediaFilter Stop method, to deactivate any pins this +// filter has. +STDMETHODIMP +CBaseFilter::Stop() +{ + CAutoLock cObjectLock(m_pLock); + HRESULT hr = NOERROR; + + // notify all pins of the state change + if (m_State != State_Stopped) { + int cPins = GetPinCount(); + for (int c = 0; c < cPins; c++) { + + CBasePin *pPin = GetPin(c); + if (NULL == pPin) { + break; + } + + // Disconnected pins are not activated - this saves pins worrying + // about this state themselves. We ignore the return code to make + // sure everyone is inactivated regardless. The base input pin + // class can return an error if it has no allocator but Stop can + // be used to resync the graph state after something has gone bad + + if (pPin->IsConnected()) { + HRESULT hrTmp = pPin->Inactive(); + if (FAILED(hrTmp) && SUCCEEDED(hr)) { + hr = hrTmp; + } + } + } + } + +#ifdef DXMPERF + PERFLOG_STOP( m_pName ? m_pName : L"CBaseFilter", (IBaseFilter *) this, m_State ); +#endif // DXMPERF + + m_State = State_Stopped; + return hr; +} + + +// override CBaseMediaFilter Pause method to activate any pins +// this filter has (also called from Run) + +STDMETHODIMP +CBaseFilter::Pause() +{ + CAutoLock cObjectLock(m_pLock); + + // notify all pins of the change to active state + if (m_State == State_Stopped) { + int cPins = GetPinCount(); + for (int c = 0; c < cPins; c++) { + + CBasePin *pPin = GetPin(c); + if (NULL == pPin) { + break; + } + + // Disconnected pins are not activated - this saves pins + // worrying about this state themselves + + if (pPin->IsConnected()) { + HRESULT hr = pPin->Active(); + if (FAILED(hr)) { + return hr; + } + } + } + } + + +#ifdef DXMPERF + PERFLOG_PAUSE( m_pName ? m_pName : L"CBaseFilter", (IBaseFilter *) this, m_State ); +#endif // DXMPERF + + m_State = State_Paused; + return S_OK; +} + +// Put the filter into a running state. + +// The time parameter is the offset to be added to the samples' +// stream time to get the reference time at which they should be presented. +// +// you can either add these two and compare it against the reference clock, +// or you can call CBaseFilter::StreamTime and compare that against +// the sample timestamp. + +STDMETHODIMP +CBaseFilter::Run(REFERENCE_TIME tStart) +{ + CAutoLock cObjectLock(m_pLock); + + // remember the stream time offset + m_tStart = tStart; + + if (m_State == State_Stopped){ + HRESULT hr = Pause(); + + if (FAILED(hr)) { + return hr; + } + } + // notify all pins of the change to active state + if (m_State != State_Running) { + int cPins = GetPinCount(); + for (int c = 0; c < cPins; c++) { + + CBasePin *pPin = GetPin(c); + if (NULL == pPin) { + break; + } + + // Disconnected pins are not activated - this saves pins + // worrying about this state themselves + + if (pPin->IsConnected()) { + HRESULT hr = pPin->Run(tStart); + if (FAILED(hr)) { + return hr; + } + } + } + } + +#ifdef DXMPERF + PERFLOG_RUN( m_pName ? m_pName : L"CBaseFilter", (IBaseFilter *) this, tStart, m_State ); +#endif // DXMPERF + + m_State = State_Running; + return S_OK; +} + +// +// return the current stream time - samples with start timestamps of this +// time or before should be rendered by now +HRESULT +CBaseFilter::StreamTime(CRefTime& rtStream) +{ + // Caller must lock for synchronization + // We can't grab the filter lock because we want to be able to call + // this from worker threads without deadlocking + + if (m_pClock == NULL) { + return VFW_E_NO_CLOCK; + } + + // get the current reference time + HRESULT hr = m_pClock->GetTime((REFERENCE_TIME*)&rtStream); + if (FAILED(hr)) { + return hr; + } + + // subtract the stream offset to get stream time + rtStream -= m_tStart; + + return S_OK; +} + + +/* Create an enumerator for the pins attached to this filter */ + +STDMETHODIMP +CBaseFilter::EnumPins(__deref_out IEnumPins **ppEnum) +{ + CheckPointer(ppEnum,E_POINTER); + ValidateReadWritePtr(ppEnum,sizeof(IEnumPins *)); + + /* Create a new ref counted enumerator */ + + *ppEnum = new CEnumPins(this, + NULL); + + return *ppEnum == NULL ? E_OUTOFMEMORY : NOERROR; +} + + +// default behaviour of FindPin is to assume pins are named +// by their pin names +STDMETHODIMP +CBaseFilter::FindPin( + LPCWSTR Id, + __deref_out IPin ** ppPin +) +{ + CheckPointer(ppPin,E_POINTER); + ValidateReadWritePtr(ppPin,sizeof(IPin *)); + + // We're going to search the pin list so maintain integrity + CAutoLock lck(m_pLock); + int iCount = GetPinCount(); + for (int i = 0; i < iCount; i++) { + CBasePin *pPin = GetPin(i); + if (NULL == pPin) { + break; + } + + if (0 == lstrcmpW(pPin->Name(), Id)) { + // Found one that matches + // + // AddRef() and return it + *ppPin = pPin; + pPin->AddRef(); + return S_OK; + } + } + *ppPin = NULL; + return VFW_E_NOT_FOUND; +} + +/* Return information about this filter */ + +STDMETHODIMP +CBaseFilter::QueryFilterInfo(__out FILTER_INFO * pInfo) +{ + CheckPointer(pInfo,E_POINTER); + ValidateReadWritePtr(pInfo,sizeof(FILTER_INFO)); + + if (m_pName) { + (void)StringCchCopyW(pInfo->achName, NUMELMS(pInfo->achName), m_pName); + } else { + pInfo->achName[0] = L'\0'; + } + pInfo->pGraph = m_pGraph; + if (m_pGraph) + m_pGraph->AddRef(); + return NOERROR; +} + + +/* Provide the filter with a filter graph */ + +STDMETHODIMP +CBaseFilter::JoinFilterGraph( + __inout_opt IFilterGraph * pGraph, + __in_opt LPCWSTR pName) +{ + CAutoLock cObjectLock(m_pLock); + + // NOTE: we no longer hold references on the graph (m_pGraph, m_pSink) + + m_pGraph = pGraph; + if (m_pGraph) { + HRESULT hr = m_pGraph->QueryInterface(IID_IMediaEventSink, + (void**) &m_pSink); + if (FAILED(hr)) { + ASSERT(m_pSink == NULL); + } + else m_pSink->Release(); // we do NOT keep a reference on it. + } else { + // if graph pointer is null, then we should + // also release the IMediaEventSink on the same object - we don't + // refcount it, so just set it to null + m_pSink = NULL; + } + + + if (m_pName) { + delete[] m_pName; + m_pName = NULL; + } + + if (pName) { + size_t namelen; + HRESULT hr = StringCchLengthW(pName, STRSAFE_MAX_CCH, &namelen); + if (FAILED(hr)) { + return hr; + } + m_pName = new WCHAR[namelen + 1]; + if (m_pName) { + (void)StringCchCopyW(m_pName, namelen + 1, pName); + } else { + return E_OUTOFMEMORY; + } + } + +#ifdef DXMPERF + PERFLOG_JOINGRAPH( m_pName ? m_pName : L"CBaseFilter",(IBaseFilter *) this, pGraph ); +#endif // DXMPERF + + return NOERROR; +} + + +// return a Vendor information string. Optional - may return E_NOTIMPL. +// memory returned should be freed using CoTaskMemFree +// default implementation returns E_NOTIMPL +STDMETHODIMP +CBaseFilter::QueryVendorInfo( + __deref_out LPWSTR* pVendorInfo) +{ + UNREFERENCED_PARAMETER(pVendorInfo); + return E_NOTIMPL; +} + + +// send an event notification to the filter graph if we know about it. +// returns S_OK if delivered, S_FALSE if the filter graph does not sink +// events, or an error otherwise. +HRESULT +CBaseFilter::NotifyEvent( + long EventCode, + LONG_PTR EventParam1, + LONG_PTR EventParam2) +{ + // Snapshot so we don't have to lock up + IMediaEventSink *pSink = m_pSink; + if (pSink) { + if (EC_COMPLETE == EventCode) { + EventParam2 = (LONG_PTR)(IBaseFilter*)this; + } + + return pSink->Notify(EventCode, EventParam1, EventParam2); + } else { + return E_NOTIMPL; + } +} + +// Request reconnect +// pPin is the pin to reconnect +// pmt is the type to reconnect with - can be NULL +// Calls ReconnectEx on the filter graph +HRESULT +CBaseFilter::ReconnectPin( + IPin *pPin, + __in_opt AM_MEDIA_TYPE const *pmt +) +{ + IFilterGraph2 *pGraph2; + if (m_pGraph != NULL) { + HRESULT hr = m_pGraph->QueryInterface(IID_IFilterGraph2, (void **)&pGraph2); + if (SUCCEEDED(hr)) { + hr = pGraph2->ReconnectEx(pPin, pmt); + pGraph2->Release(); + return hr; + } else { + return m_pGraph->Reconnect(pPin); + } + } else { + return E_NOINTERFACE; + } +} + + + +/* This is the same idea as the media type version does for type enumeration + on pins but for the list of pins available. So if the list of pins you + provide changes dynamically then either override this virtual function + to provide the version number, or more simply call IncrementPinVersion */ + +LONG CBaseFilter::GetPinVersion() +{ + return m_PinVersion; +} + + +/* Increment the current pin version cookie */ + +void CBaseFilter::IncrementPinVersion() +{ + InterlockedIncrement(&m_PinVersion); +} + +/* register filter */ + +STDMETHODIMP CBaseFilter::Register() +{ + // get setup data, if it exists + // + LPAMOVIESETUP_FILTER psetupdata = GetSetupData(); + + // check we've got data + // + if( NULL == psetupdata ) return S_FALSE; + + // init is ref counted so call just in case + // we're being called cold. + // + HRESULT hr = CoInitialize( (LPVOID)NULL ); + ASSERT( SUCCEEDED(hr) ); + + // get hold of IFilterMapper + // + IFilterMapper *pIFM; + hr = CoCreateInstance( CLSID_FilterMapper + , NULL + , CLSCTX_INPROC_SERVER + , IID_IFilterMapper + , (void **)&pIFM ); + if( SUCCEEDED(hr) ) + { + hr = AMovieSetupRegisterFilter( psetupdata, pIFM, TRUE ); + pIFM->Release(); + } + + // and clear up + // + CoFreeUnusedLibraries(); + CoUninitialize(); + + return NOERROR; +} + + +/* unregister filter */ + +STDMETHODIMP CBaseFilter::Unregister() +{ + // get setup data, if it exists + // + LPAMOVIESETUP_FILTER psetupdata = GetSetupData(); + + // check we've got data + // + if( NULL == psetupdata ) return S_FALSE; + + // OLE init is ref counted so call + // just in case we're being called cold. + // + HRESULT hr = CoInitialize( (LPVOID)NULL ); + ASSERT( SUCCEEDED(hr) ); + + // get hold of IFilterMapper + // + IFilterMapper *pIFM; + hr = CoCreateInstance( CLSID_FilterMapper + , NULL + , CLSCTX_INPROC_SERVER + , IID_IFilterMapper + , (void **)&pIFM ); + if( SUCCEEDED(hr) ) + { + hr = AMovieSetupRegisterFilter( psetupdata, pIFM, FALSE ); + + // release interface + // + pIFM->Release(); + } + + // clear up + // + CoFreeUnusedLibraries(); + CoUninitialize(); + + // handle one acceptable "error" - that + // of filter not being registered! + // (couldn't find a suitable #define'd + // name for the error!) + // + if( 0x80070002 == hr) + return NOERROR; + else + return hr; +} + + +//===================================================================== +//===================================================================== +// Implements CEnumPins +//===================================================================== +//===================================================================== + + +CEnumPins::CEnumPins(__in CBaseFilter *pFilter, + __in_opt CEnumPins *pEnumPins) : + m_Position(0), + m_PinCount(0), + m_pFilter(pFilter), + m_cRef(1), // Already ref counted + m_PinCache(NAME("Pin Cache")) +{ + +#ifdef DEBUG + m_dwCookie = DbgRegisterObjectCreation("CEnumPins", 0); +#endif + + /* We must be owned by a filter derived from CBaseFilter */ + + ASSERT(pFilter != NULL); + + /* Hold a reference count on our filter */ + m_pFilter->AddRef(); + + /* Are we creating a new enumerator */ + + if (pEnumPins == NULL) { + m_Version = m_pFilter->GetPinVersion(); + m_PinCount = m_pFilter->GetPinCount(); + } else { + ASSERT(m_Position <= m_PinCount); + m_Position = pEnumPins->m_Position; + m_PinCount = pEnumPins->m_PinCount; + m_Version = pEnumPins->m_Version; + m_PinCache.AddTail(&(pEnumPins->m_PinCache)); + } +} + + +/* Destructor releases the reference count on our filter NOTE since we hold + a reference count on the filter who created us we know it is safe to + release it, no access can be made to it afterwards though as we have just + caused the last reference count to go and the object to be deleted */ + +CEnumPins::~CEnumPins() +{ + m_pFilter->Release(); + +#ifdef DEBUG + DbgRegisterObjectDestruction(m_dwCookie); +#endif +} + + +/* Override this to say what interfaces we support where */ + +STDMETHODIMP +CEnumPins::QueryInterface(REFIID riid, __deref_out void **ppv) +{ + CheckPointer(ppv, E_POINTER); + + /* Do we have this interface */ + + if (riid == IID_IEnumPins || riid == IID_IUnknown) { + return GetInterface((IEnumPins *) this, ppv); + } else { + *ppv = NULL; + return E_NOINTERFACE; + } +} + +STDMETHODIMP_(ULONG) +CEnumPins::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) +CEnumPins::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) { + delete this; + } + return cRef; +} + +/* One of an enumerator's basic member functions allows us to create a cloned + interface that initially has the same state. Since we are taking a snapshot + of an object (current position and all) we must lock access at the start */ + +STDMETHODIMP +CEnumPins::Clone(__deref_out IEnumPins **ppEnum) +{ + CheckPointer(ppEnum,E_POINTER); + ValidateReadWritePtr(ppEnum,sizeof(IEnumPins *)); + HRESULT hr = NOERROR; + + /* Check we are still in sync with the filter */ + if (AreWeOutOfSync() == TRUE) { + *ppEnum = NULL; + hr = VFW_E_ENUM_OUT_OF_SYNC; + } else { + *ppEnum = new CEnumPins(m_pFilter, + this); + if (*ppEnum == NULL) { + hr = E_OUTOFMEMORY; + } + } + return hr; +} + + +/* Return the next pin after the current position */ + +STDMETHODIMP +CEnumPins::Next(ULONG cPins, // place this many pins... + __out_ecount(cPins) IPin **ppPins, // ...in this array + __out_opt ULONG *pcFetched) // actual count passed returned here +{ + CheckPointer(ppPins,E_POINTER); + ValidateReadWritePtr(ppPins,cPins * sizeof(IPin *)); + + ASSERT(ppPins); + + if (pcFetched!=NULL) { + ValidateWritePtr(pcFetched, sizeof(ULONG)); + *pcFetched = 0; // default unless we succeed + } + // now check that the parameter is valid + else if (cPins>1) { // pcFetched == NULL + return E_INVALIDARG; + } + ULONG cFetched = 0; // increment as we get each one. + + /* Check we are still in sync with the filter */ + if (AreWeOutOfSync() == TRUE) { + // If we are out of sync, we should refresh the enumerator. + // This will reset the position and update the other members, but + // will not clear cache of pins we have already returned. + Refresh(); + } + + /* Return each pin interface NOTE GetPin returns CBasePin * not addrefed + so we must QI for the IPin (which increments its reference count) + If while we are retrieving a pin from the filter an error occurs we + assume that our internal state is stale with respect to the filter + (for example someone has deleted a pin) so we + return VFW_E_ENUM_OUT_OF_SYNC */ + + while (cFetched < cPins && m_PinCount > m_Position) { + + /* Get the next pin object from the filter */ + + CBasePin *pPin = m_pFilter->GetPin(m_Position++); + if (pPin == NULL) { + // If this happend, and it's not the first time through, then we've got a problem, + // since we should really go back and release the iPins, which we have previously + // AddRef'ed. + ASSERT( cFetched==0 ); + return VFW_E_ENUM_OUT_OF_SYNC; + } + + /* We only want to return this pin, if it is not in our cache */ + if (0 == m_PinCache.Find(pPin)) + { + /* From the object get an IPin interface */ + + *ppPins = pPin; + pPin->AddRef(); + + cFetched++; + ppPins++; + + m_PinCache.AddTail(pPin); + } + } + + if (pcFetched!=NULL) { + *pcFetched = cFetched; + } + + return (cPins==cFetched ? NOERROR : S_FALSE); +} + + +/* Skip over one or more entries in the enumerator */ + +STDMETHODIMP +CEnumPins::Skip(ULONG cPins) +{ + /* Check we are still in sync with the filter */ + if (AreWeOutOfSync() == TRUE) { + return VFW_E_ENUM_OUT_OF_SYNC; + } + + /* Work out how many pins are left to skip over */ + /* We could position at the end if we are asked to skip too many... */ + /* ..which would match the base implementation for CEnumMediaTypes::Skip */ + + ULONG PinsLeft = m_PinCount - m_Position; + if (cPins > PinsLeft) { + return S_FALSE; + } + m_Position += cPins; + return NOERROR; +} + + +/* Set the current position back to the start */ +/* Reset has 4 simple steps: + * + * Set position to _head of list + * Sync enumerator with object being enumerated + * Clear the cache of pins already returned + * return S_OK + */ + +STDMETHODIMP +CEnumPins::Reset() +{ + m_Version = m_pFilter->GetPinVersion(); + m_PinCount = m_pFilter->GetPinCount(); + + m_Position = 0; + + // Clear the cache + m_PinCache.RemoveAll(); + + return S_OK; +} + + +/* Set the current position back to the start */ +/* Refresh has 3 simple steps: + * + * Set position to _head of list + * Sync enumerator with object being enumerated + * return S_OK + */ + +STDMETHODIMP +CEnumPins::Refresh() +{ + m_Version = m_pFilter->GetPinVersion(); + m_PinCount = m_pFilter->GetPinCount(); + + m_Position = 0; + return S_OK; +} + + +//===================================================================== +//===================================================================== +// Implements CEnumMediaTypes +//===================================================================== +//===================================================================== + + +CEnumMediaTypes::CEnumMediaTypes(__in CBasePin *pPin, + __in_opt CEnumMediaTypes *pEnumMediaTypes) : + m_Position(0), + m_pPin(pPin), + m_cRef(1) +{ + +#ifdef DEBUG + m_dwCookie = DbgRegisterObjectCreation("CEnumMediaTypes", 0); +#endif + + /* We must be owned by a pin derived from CBasePin */ + + ASSERT(pPin != NULL); + + /* Hold a reference count on our pin */ + m_pPin->AddRef(); + + /* Are we creating a new enumerator */ + + if (pEnumMediaTypes == NULL) { + m_Version = m_pPin->GetMediaTypeVersion(); + return; + } + + m_Position = pEnumMediaTypes->m_Position; + m_Version = pEnumMediaTypes->m_Version; +} + + +/* Destructor releases the reference count on our base pin. NOTE since we hold + a reference count on the pin who created us we know it is safe to release + it, no access can be made to it afterwards though as we might have just + caused the last reference count to go and the object to be deleted */ + +CEnumMediaTypes::~CEnumMediaTypes() +{ +#ifdef DEBUG + DbgRegisterObjectDestruction(m_dwCookie); +#endif + m_pPin->Release(); +} + + +/* Override this to say what interfaces we support where */ + +STDMETHODIMP +CEnumMediaTypes::QueryInterface(REFIID riid, __deref_out void **ppv) +{ + CheckPointer(ppv, E_POINTER); + + /* Do we have this interface */ + + if (riid == IID_IEnumMediaTypes || riid == IID_IUnknown) { + return GetInterface((IEnumMediaTypes *) this, ppv); + } else { + *ppv = NULL; + return E_NOINTERFACE; + } +} + +STDMETHODIMP_(ULONG) +CEnumMediaTypes::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) +CEnumMediaTypes::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) { + delete this; + } + return cRef; +} + +/* One of an enumerator's basic member functions allows us to create a cloned + interface that initially has the same state. Since we are taking a snapshot + of an object (current position and all) we must lock access at the start */ + +STDMETHODIMP +CEnumMediaTypes::Clone(__deref_out IEnumMediaTypes **ppEnum) +{ + CheckPointer(ppEnum,E_POINTER); + ValidateReadWritePtr(ppEnum,sizeof(IEnumMediaTypes *)); + HRESULT hr = NOERROR; + + /* Check we are still in sync with the pin */ + if (AreWeOutOfSync() == TRUE) { + *ppEnum = NULL; + hr = VFW_E_ENUM_OUT_OF_SYNC; + } else { + + *ppEnum = new CEnumMediaTypes(m_pPin, + this); + + if (*ppEnum == NULL) { + hr = E_OUTOFMEMORY; + } + } + return hr; +} + + +/* Enumerate the next pin(s) after the current position. The client using this + interface passes in a pointer to an array of pointers each of which will + be filled in with a pointer to a fully initialised media type format + Return NOERROR if it all works, + S_FALSE if fewer than cMediaTypes were enumerated. + VFW_E_ENUM_OUT_OF_SYNC if the enumerator has been broken by + state changes in the filter + The actual count always correctly reflects the number of types in the array. +*/ + +STDMETHODIMP +CEnumMediaTypes::Next(ULONG cMediaTypes, // place this many types... + __out_ecount(cMediaTypes) AM_MEDIA_TYPE **ppMediaTypes, // ...in this array + __out ULONG *pcFetched) // actual count passed +{ + CheckPointer(ppMediaTypes,E_POINTER); + ValidateReadWritePtr(ppMediaTypes,cMediaTypes * sizeof(AM_MEDIA_TYPE *)); + /* Check we are still in sync with the pin */ + if (AreWeOutOfSync() == TRUE) { + return VFW_E_ENUM_OUT_OF_SYNC; + } + + if (pcFetched!=NULL) { + ValidateWritePtr(pcFetched, sizeof(ULONG)); + *pcFetched = 0; // default unless we succeed + } + // now check that the parameter is valid + else if (cMediaTypes>1) { // pcFetched == NULL + return E_INVALIDARG; + } + ULONG cFetched = 0; // increment as we get each one. + + /* Return each media type by asking the filter for them in turn - If we + have an error code retured to us while we are retrieving a media type + we assume that our internal state is stale with respect to the filter + (for example the window size changing) so we return + VFW_E_ENUM_OUT_OF_SYNC */ + + while (cMediaTypes) { + + CMediaType cmt; + + HRESULT hr = m_pPin->GetMediaType(m_Position++, &cmt); + if (S_OK != hr) { + break; + } + + /* We now have a CMediaType object that contains the next media type + but when we assign it to the array position we CANNOT just assign + the AM_MEDIA_TYPE structure because as soon as the object goes out of + scope it will delete the memory we have just copied. The function + we use is CreateMediaType which allocates a task memory block */ + + /* Transfer across the format block manually to save an allocate + and free on the format block and generally go faster */ + + *ppMediaTypes = (AM_MEDIA_TYPE *)CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE)); + if (*ppMediaTypes == NULL) { + break; + } + + /* Do a regular copy */ + **ppMediaTypes = cmt; + + /* Make sure the destructor doesn't free these */ + cmt.pbFormat = NULL; + cmt.cbFormat = NULL; + cmt.pUnk = NULL; + + + ppMediaTypes++; + cFetched++; + cMediaTypes--; + } + + if (pcFetched!=NULL) { + *pcFetched = cFetched; + } + + return ( cMediaTypes==0 ? NOERROR : S_FALSE ); +} + + +/* Skip over one or more entries in the enumerator */ + +STDMETHODIMP +CEnumMediaTypes::Skip(ULONG cMediaTypes) +{ + // If we're skipping 0 elements we're guaranteed to skip the + // correct number of elements + if (cMediaTypes == 0) { + return S_OK; + } + + /* Check we are still in sync with the pin */ + if (AreWeOutOfSync() == TRUE) { + return VFW_E_ENUM_OUT_OF_SYNC; + } + + m_Position += cMediaTypes; + + /* See if we're over the end */ + CMediaType cmt; + return S_OK == m_pPin->GetMediaType(m_Position - 1, &cmt) ? S_OK : S_FALSE; +} + + +/* Set the current position back to the start */ +/* Reset has 3 simple steps: + * + * set position to _head of list + * sync enumerator with object being enumerated + * return S_OK + */ + +STDMETHODIMP +CEnumMediaTypes::Reset() + +{ + m_Position = 0; + + // Bring the enumerator back into step with the current state. This + // may be a noop but ensures that the enumerator will be valid on the + // next call. + m_Version = m_pPin->GetMediaTypeVersion(); + return NOERROR; +} + + +//===================================================================== +//===================================================================== +// Implements CBasePin +//===================================================================== +//===================================================================== + + +/* NOTE The implementation of this class calls the CUnknown constructor with + a NULL outer unknown pointer. This has the effect of making us a self + contained class, ie any QueryInterface, AddRef or Release calls will be + routed to the class's NonDelegatingUnknown methods. You will typically + find that the classes that do this then override one or more of these + virtual functions to provide more specialised behaviour. A good example + of this is where a class wants to keep the QueryInterface internal but + still wants its lifetime controlled by the external object */ + +/* Constructor */ + +CBasePin::CBasePin(__in_opt LPCTSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName, + PIN_DIRECTION dir) : + CUnknown( pObjectName, NULL ), + m_pFilter(pFilter), + m_pLock(pLock), + m_pName(NULL), + m_Connected(NULL), + m_dir(dir), + m_bRunTimeError(FALSE), + m_pQSink(NULL), + m_TypeVersion(1), + m_tStart(), + m_tStop(MAX_TIME), + m_bCanReconnectWhenActive(false), + m_bTryMyTypesFirst(false), + m_dRate(1.0) +{ + /* WARNING - pFilter is often not a properly constituted object at + this state (in particular QueryInterface may not work) - this + is because its owner is often its containing object and we + have been called from the containing object's constructor so + the filter's owner has not yet had its CUnknown constructor + called + */ +#ifdef DXMPERF + PERFLOG_CTOR( pName ? pName : L"CBasePin", (IPin *) this ); +#endif // DXMPERF + + ASSERT(pFilter != NULL); + ASSERT(pLock != NULL); + + if (pName) { + size_t cchName; + HRESULT hr = StringCchLengthW(pName, STRSAFE_MAX_CCH, &cchName); + if (SUCCEEDED(hr)) { + m_pName = new WCHAR[cchName + 1]; + if (m_pName) { + (void)StringCchCopyW(m_pName, cchName + 1, pName); + } + } + } + +#ifdef DEBUG + m_cRef = 0; +#endif +} + +#ifdef UNICODE +CBasePin::CBasePin(__in_opt LPCSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName, + PIN_DIRECTION dir) : + CUnknown( pObjectName, NULL ), + m_pFilter(pFilter), + m_pLock(pLock), + m_pName(NULL), + m_Connected(NULL), + m_dir(dir), + m_bRunTimeError(FALSE), + m_pQSink(NULL), + m_TypeVersion(1), + m_tStart(), + m_tStop(MAX_TIME), + m_bCanReconnectWhenActive(false), + m_bTryMyTypesFirst(false), + m_dRate(1.0) +{ + /* WARNING - pFilter is often not a properly constituted object at + this state (in particular QueryInterface may not work) - this + is because its owner is often its containing object and we + have been called from the containing object's constructor so + the filter's owner has not yet had its CUnknown constructor + called + */ +#ifdef DXMPERF + PERFLOG_CTOR( pName ? pName : L"CBasePin", (IPin *) this ); +#endif // DXMPERF + + ASSERT(pFilter != NULL); + ASSERT(pLock != NULL); + + if (pName) { + size_t cchName; + HRESULT hr = StringCchLengthW(pName, STRSAFE_MAX_CCH, &cchName); + if (SUCCEEDED(hr)) { + m_pName = new WCHAR[cchName + 1]; + if (m_pName) { + (void)StringCchCopyW(m_pName, cchName + 1, pName); + } + } + } + + +#ifdef DEBUG + m_cRef = 0; +#endif +} +#endif + +/* Destructor since a connected pin holds a reference count on us there is + no way that we can be deleted unless we are not currently connected */ + +CBasePin::~CBasePin() +{ +#ifdef DXMPERF + PERFLOG_DTOR( m_pName ? m_pName : L"CBasePin", (IPin *) this ); +#endif // DXMPERF + + // We don't call disconnect because if the filter is going away + // all the pins must have a reference count of zero so they must + // have been disconnected anyway - (but check the assumption) + ASSERT(m_Connected == FALSE); + + delete[] m_pName; + + // check the internal reference count is consistent + ASSERT(m_cRef == 0); +} + + +/* Override this to say what interfaces we support and where */ + +STDMETHODIMP +CBasePin::NonDelegatingQueryInterface(REFIID riid, __deref_out void ** ppv) +{ + /* Do we have this interface */ + + if (riid == IID_IPin) { + return GetInterface((IPin *) this, ppv); + } else if (riid == IID_IQualityControl) { + return GetInterface((IQualityControl *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } +} + + +/* Override to increment the owning filter's reference count */ + +STDMETHODIMP_(ULONG) +CBasePin::NonDelegatingAddRef() +{ + ASSERT(InterlockedIncrement(&m_cRef) > 0); + return m_pFilter->AddRef(); +} + + +/* Override to decrement the owning filter's reference count */ + +STDMETHODIMP_(ULONG) +CBasePin::NonDelegatingRelease() +{ + ASSERT(InterlockedDecrement(&m_cRef) >= 0); + return m_pFilter->Release(); +} + + +/* Displays pin connection information */ + +#ifdef DEBUG +void +CBasePin::DisplayPinInfo(IPin *pReceivePin) +{ + + if (DbgCheckModuleLevel(LOG_TRACE, CONNECT_TRACE_LEVEL)) { + PIN_INFO ConnectPinInfo; + PIN_INFO ReceivePinInfo; + + if (FAILED(QueryPinInfo(&ConnectPinInfo))) { + StringCchCopyW(ConnectPinInfo.achName, sizeof(ConnectPinInfo.achName)/sizeof(WCHAR), L"Bad Pin"); + } else { + QueryPinInfoReleaseFilter(ConnectPinInfo); + } + + if (FAILED(pReceivePin->QueryPinInfo(&ReceivePinInfo))) { + StringCchCopyW(ReceivePinInfo.achName, sizeof(ReceivePinInfo.achName)/sizeof(WCHAR), L"Bad Pin"); + } else { + QueryPinInfoReleaseFilter(ReceivePinInfo); + } + + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Trying to connect Pins :"))); + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT(" <%ls>"), ConnectPinInfo.achName)); + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT(" <%ls>"), ReceivePinInfo.achName)); + } +} +#endif + + +/* Displays general information on the pin media type */ + +#ifdef DEBUG +void CBasePin::DisplayTypeInfo(IPin *pPin, const CMediaType *pmt) +{ + UNREFERENCED_PARAMETER(pPin); + if (DbgCheckModuleLevel(LOG_TRACE, CONNECT_TRACE_LEVEL)) { + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Trying media type:"))); + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT(" major type: %hs"), + GuidNames[*pmt->Type()])); + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT(" sub type : %hs"), + GuidNames[*pmt->Subtype()])); + } +} +#endif + +/* Asked to connect to a pin. A pin is always attached to an owning filter + object so we always delegate our locking to that object. We first of all + retrieve a media type enumerator for the input pin and see if we accept + any of the formats that it would ideally like, failing that we retrieve + our enumerator and see if it will accept any of our preferred types */ + +STDMETHODIMP +CBasePin::Connect( + IPin * pReceivePin, + __in_opt const AM_MEDIA_TYPE *pmt // optional media type +) +{ + CheckPointer(pReceivePin,E_POINTER); + ValidateReadPtr(pReceivePin,sizeof(IPin)); + CAutoLock cObjectLock(m_pLock); + DisplayPinInfo(pReceivePin); + + /* See if we are already connected */ + + if (m_Connected) { + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Already connected"))); + return VFW_E_ALREADY_CONNECTED; + } + + /* See if the filter is active */ + if (!IsStopped() && !m_bCanReconnectWhenActive) { + return VFW_E_NOT_STOPPED; + } + + + // Find a mutually agreeable media type - + // Pass in the template media type. If this is partially specified, + // each of the enumerated media types will need to be checked against + // it. If it is non-null and fully specified, we will just try to connect + // with this. + + const CMediaType * ptype = (CMediaType*)pmt; + HRESULT hr = AgreeMediaType(pReceivePin, ptype); + if (FAILED(hr)) { + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Failed to agree type"))); + + // Since the procedure is already returning an error code, there + // is nothing else this function can do to report the error. + EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); + +#ifdef DXMPERF + PERFLOG_CONNECT( (IPin *) this, pReceivePin, hr, pmt ); +#endif // DXMPERF + + return hr; + } + + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Connection succeeded"))); + +#ifdef DXMPERF + PERFLOG_CONNECT( (IPin *) this, pReceivePin, NOERROR, pmt ); +#endif // DXMPERF + + return NOERROR; +} + +// given a specific media type, attempt a connection (includes +// checking that the type is acceptable to this pin) +HRESULT +CBasePin::AttemptConnection( + IPin* pReceivePin, // connect to this pin + const CMediaType* pmt // using this type +) +{ + // The caller should hold the filter lock becasue this function + // uses m_Connected. The caller should also hold the filter lock + // because this function calls SetMediaType(), IsStopped() and + // CompleteConnect(). + ASSERT(CritCheckIn(m_pLock)); + + // Check that the connection is valid -- need to do this for every + // connect attempt since BreakConnect will undo it. + HRESULT hr = CheckConnect(pReceivePin); + if (FAILED(hr)) { + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("CheckConnect failed"))); + + // Since the procedure is already returning an error code, there + // is nothing else this function can do to report the error. + EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); + + return hr; + } + + DisplayTypeInfo(pReceivePin, pmt); + + /* Check we will accept this media type */ + + hr = CheckMediaType(pmt); + if (hr == NOERROR) { + + /* Make ourselves look connected otherwise ReceiveConnection + may not be able to complete the connection + */ + m_Connected = pReceivePin; + m_Connected->AddRef(); + hr = SetMediaType(pmt); + if (SUCCEEDED(hr)) { + /* See if the other pin will accept this type */ + + hr = pReceivePin->ReceiveConnection((IPin *)this, pmt); + if (SUCCEEDED(hr)) { + /* Complete the connection */ + + hr = CompleteConnect(pReceivePin); + if (SUCCEEDED(hr)) { + return hr; + } else { + DbgLog((LOG_TRACE, + CONNECT_TRACE_LEVEL, + TEXT("Failed to complete connection"))); + pReceivePin->Disconnect(); + } + } + } + } else { + // we cannot use this media type + + // return a specific media type error if there is one + // or map a general failure code to something more helpful + // (in particular S_FALSE gets changed to an error code) + if (SUCCEEDED(hr) || + (hr == E_FAIL) || + (hr == E_INVALIDARG)) { + hr = VFW_E_TYPE_NOT_ACCEPTED; + } + } + + // BreakConnect and release any connection here in case CheckMediaType + // failed, or if we set anything up during a call back during + // ReceiveConnection. + + // Since the procedure is already returning an error code, there + // is nothing else this function can do to report the error. + EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); + + /* If failed then undo our state */ + if (m_Connected) { + m_Connected->Release(); + m_Connected = NULL; + } + + return hr; +} + +/* Given an enumerator we cycle through all the media types it proposes and + firstly suggest them to our derived pin class and if that succeeds try + them with the pin in a ReceiveConnection call. This means that if our pin + proposes a media type we still check in here that we can support it. This + is deliberate so that in simple cases the enumerator can hold all of the + media types even if some of them are not really currently available */ + +HRESULT CBasePin::TryMediaTypes( + IPin *pReceivePin, + __in_opt const CMediaType *pmt, + IEnumMediaTypes *pEnum) +{ + /* Reset the current enumerator position */ + + HRESULT hr = pEnum->Reset(); + if (FAILED(hr)) { + return hr; + } + + CMediaType *pMediaType = NULL; + ULONG ulMediaCount = 0; + + // attempt to remember a specific error code if there is one + HRESULT hrFailure = S_OK; + + for (;;) { + + /* Retrieve the next media type NOTE each time round the loop the + enumerator interface will allocate another AM_MEDIA_TYPE structure + If we are successful then we copy it into our output object, if + not then we must delete the memory allocated before returning */ + + hr = pEnum->Next(1, (AM_MEDIA_TYPE**)&pMediaType,&ulMediaCount); + if (hr != S_OK) { + if (S_OK == hrFailure) { + hrFailure = VFW_E_NO_ACCEPTABLE_TYPES; + } + return hrFailure; + } + + + ASSERT(ulMediaCount == 1); + ASSERT(pMediaType); + + // check that this matches the partial type (if any) + + if (pMediaType && + ((pmt == NULL) || + pMediaType->MatchesPartial(pmt))) { + + hr = AttemptConnection(pReceivePin, pMediaType); + + // attempt to remember a specific error code + if (FAILED(hr) && + SUCCEEDED(hrFailure) && + (hr != E_FAIL) && + (hr != E_INVALIDARG) && + (hr != VFW_E_TYPE_NOT_ACCEPTED)) { + hrFailure = hr; + } + } else { + hr = VFW_E_NO_ACCEPTABLE_TYPES; + } + + if(pMediaType) { + DeleteMediaType(pMediaType); + pMediaType = NULL; + } + + if (S_OK == hr) { + return hr; + } + } +} + + +/* This is called to make the connection, including the taask of finding + a media type for the pin connection. pmt is the proposed media type + from the Connect call: if this is fully specified, we will try that. + Otherwise we enumerate and try all the input pin's types first and + if that fails we then enumerate and try all our preferred media types. + For each media type we check it against pmt (if non-null and partially + specified) as well as checking that both pins will accept it. + */ + +HRESULT CBasePin::AgreeMediaType( + IPin *pReceivePin, + const CMediaType *pmt) +{ + ASSERT(pReceivePin); + IEnumMediaTypes *pEnumMediaTypes = NULL; + + // if the media type is fully specified then use that + if ( (pmt != NULL) && (!pmt->IsPartiallySpecified())) { + + // if this media type fails, then we must fail the connection + // since if pmt is nonnull we are only allowed to connect + // using a type that matches it. + + return AttemptConnection(pReceivePin, pmt); + } + + + /* Try the other pin's enumerator */ + + HRESULT hrFailure = VFW_E_NO_ACCEPTABLE_TYPES; + + for (int i = 0; i < 2; i++) { + HRESULT hr; + if (i == (int)m_bTryMyTypesFirst) { + hr = pReceivePin->EnumMediaTypes(&pEnumMediaTypes); + } else { + hr = EnumMediaTypes(&pEnumMediaTypes); + } + if (SUCCEEDED(hr)) { + ASSERT(pEnumMediaTypes); + hr = TryMediaTypes(pReceivePin,pmt,pEnumMediaTypes); + pEnumMediaTypes->Release(); + if (SUCCEEDED(hr)) { + return NOERROR; + } else { + // try to remember specific error codes if there are any + if ((hr != E_FAIL) && + (hr != E_INVALIDARG) && + (hr != VFW_E_TYPE_NOT_ACCEPTED)) { + hrFailure = hr; + } + } + } + } + + return hrFailure; +} + + +/* Called when we want to complete a connection to another filter. Failing + this will also fail the connection and disconnect the other pin as well */ + +HRESULT +CBasePin::CompleteConnect(IPin *pReceivePin) +{ + UNREFERENCED_PARAMETER(pReceivePin); + return NOERROR; +} + + +/* This is called to set the format for a pin connection - CheckMediaType + will have been called to check the connection format and if it didn't + return an error code then this (virtual) function will be invoked */ + +HRESULT +CBasePin::SetMediaType(const CMediaType *pmt) +{ + HRESULT hr = m_mt.Set(*pmt); + if (FAILED(hr)) { + return hr; + } + + return NOERROR; +} + + +/* This is called during Connect() to provide a virtual method that can do + any specific check needed for connection such as QueryInterface. This + base class method just checks that the pin directions don't match */ + +HRESULT +CBasePin::CheckConnect(IPin * pPin) +{ + /* Check that pin directions DONT match */ + + PIN_DIRECTION pd; + pPin->QueryDirection(&pd); + + ASSERT((pd == PINDIR_OUTPUT) || (pd == PINDIR_INPUT)); + ASSERT((m_dir == PINDIR_OUTPUT) || (m_dir == PINDIR_INPUT)); + + // we should allow for non-input and non-output connections? + if (pd == m_dir) { + return VFW_E_INVALID_DIRECTION; + } + return NOERROR; +} + + +/* This is called when we realise we can't make a connection to the pin and + must undo anything we did in CheckConnect - override to release QIs done */ + +HRESULT +CBasePin::BreakConnect() +{ + return NOERROR; +} + + +/* Called normally by an output pin on an input pin to try and establish a + connection. +*/ + +STDMETHODIMP +CBasePin::ReceiveConnection( + IPin * pConnector, // this is the pin who we will connect to + const AM_MEDIA_TYPE *pmt // this is the media type we will exchange +) +{ + CheckPointer(pConnector,E_POINTER); + CheckPointer(pmt,E_POINTER); + ValidateReadPtr(pConnector,sizeof(IPin)); + ValidateReadPtr(pmt,sizeof(AM_MEDIA_TYPE)); + CAutoLock cObjectLock(m_pLock); + + /* Are we already connected */ + if (m_Connected) { + return VFW_E_ALREADY_CONNECTED; + } + + /* See if the filter is active */ + if (!IsStopped() && !m_bCanReconnectWhenActive) { + return VFW_E_NOT_STOPPED; + } + + HRESULT hr = CheckConnect(pConnector); + if (FAILED(hr)) { + // Since the procedure is already returning an error code, there + // is nothing else this function can do to report the error. + EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); + +#ifdef DXMPERF + PERFLOG_RXCONNECT( pConnector, (IPin *) this, hr, pmt ); +#endif // DXMPERF + + return hr; + } + + /* Ask derived class if this media type is ok */ + + CMediaType * pcmt = (CMediaType*) pmt; + hr = CheckMediaType(pcmt); + if (hr != NOERROR) { + // no -we don't support this media type + + // Since the procedure is already returning an error code, there + // is nothing else this function can do to report the error. + EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); + + // return a specific media type error if there is one + // or map a general failure code to something more helpful + // (in particular S_FALSE gets changed to an error code) + if (SUCCEEDED(hr) || + (hr == E_FAIL) || + (hr == E_INVALIDARG)) { + hr = VFW_E_TYPE_NOT_ACCEPTED; + } + +#ifdef DXMPERF + PERFLOG_RXCONNECT( pConnector, (IPin *) this, hr, pmt ); +#endif // DXMPERF + + return hr; + } + + /* Complete the connection */ + + m_Connected = pConnector; + m_Connected->AddRef(); + hr = SetMediaType(pcmt); + if (SUCCEEDED(hr)) { + hr = CompleteConnect(pConnector); + if (SUCCEEDED(hr)) { + +#ifdef DXMPERF + PERFLOG_RXCONNECT( pConnector, (IPin *) this, NOERROR, pmt ); +#endif // DXMPERF + + return NOERROR; + } + } + + DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Failed to set the media type or failed to complete the connection."))); + m_Connected->Release(); + m_Connected = NULL; + + // Since the procedure is already returning an error code, there + // is nothing else this function can do to report the error. + EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); + +#ifdef DXMPERF + PERFLOG_RXCONNECT( pConnector, (IPin *) this, hr, pmt ); +#endif // DXMPERF + + return hr; +} + + +/* Called when we want to terminate a pin connection */ + +STDMETHODIMP +CBasePin::Disconnect() +{ + CAutoLock cObjectLock(m_pLock); + + /* See if the filter is active */ + if (!IsStopped()) { + return VFW_E_NOT_STOPPED; + } + + return DisconnectInternal(); +} + +STDMETHODIMP +CBasePin::DisconnectInternal() +{ + ASSERT(CritCheckIn(m_pLock)); + + if (m_Connected) { + HRESULT hr = BreakConnect(); + if( FAILED( hr ) ) { + +#ifdef DXMPERF + PERFLOG_DISCONNECT( (IPin *) this, m_Connected, hr ); +#endif // DXMPERF + + // There is usually a bug in the program if BreakConnect() fails. + DbgBreak( "WARNING: BreakConnect() failed in CBasePin::Disconnect()." ); + return hr; + } + + m_Connected->Release(); + m_Connected = NULL; + +#ifdef DXMPERF + PERFLOG_DISCONNECT( (IPin *) this, m_Connected, S_OK ); +#endif // DXMPERF + + return S_OK; + } else { + // no connection - not an error + +#ifdef DXMPERF + PERFLOG_DISCONNECT( (IPin *) this, m_Connected, S_FALSE ); +#endif // DXMPERF + + return S_FALSE; + } +} + + +/* Return an AddRef()'d pointer to the connected pin if there is one */ +STDMETHODIMP +CBasePin::ConnectedTo( + __deref_out IPin **ppPin +) +{ + CheckPointer(ppPin,E_POINTER); + ValidateReadWritePtr(ppPin,sizeof(IPin *)); + // + // It's pointless to lock here. + // The caller should ensure integrity. + // + + IPin *pPin = m_Connected; + *ppPin = pPin; + if (pPin != NULL) { + pPin->AddRef(); + return S_OK; + } else { + ASSERT(*ppPin == NULL); + return VFW_E_NOT_CONNECTED; + } +} + +/* Return the media type of the connection */ +STDMETHODIMP +CBasePin::ConnectionMediaType( + __out AM_MEDIA_TYPE *pmt +) +{ + CheckPointer(pmt,E_POINTER); + ValidateReadWritePtr(pmt,sizeof(AM_MEDIA_TYPE)); + CAutoLock cObjectLock(m_pLock); + + /* Copy constructor of m_mt allocates the memory */ + if (IsConnected()) { + CopyMediaType( pmt, &m_mt ); + return S_OK; + } else { + ((CMediaType *)pmt)->InitMediaType(); + return VFW_E_NOT_CONNECTED; + } +} + +/* Return information about the filter we are connect to */ + +STDMETHODIMP +CBasePin::QueryPinInfo( + __out PIN_INFO * pInfo +) +{ + CheckPointer(pInfo,E_POINTER); + ValidateReadWritePtr(pInfo,sizeof(PIN_INFO)); + + pInfo->pFilter = m_pFilter; + if (m_pFilter) { + m_pFilter->AddRef(); + } + + if (m_pName) { + (void)StringCchCopyW(pInfo->achName, NUMELMS(pInfo->achName), m_pName); + } else { + pInfo->achName[0] = L'\0'; + } + + pInfo->dir = m_dir; + + return NOERROR; +} + +STDMETHODIMP +CBasePin::QueryDirection( + __out PIN_DIRECTION * pPinDir +) +{ + CheckPointer(pPinDir,E_POINTER); + ValidateReadWritePtr(pPinDir,sizeof(PIN_DIRECTION)); + + *pPinDir = m_dir; + return NOERROR; +} + +// Default QueryId to return the pin's name +STDMETHODIMP +CBasePin::QueryId( + __deref_out LPWSTR * Id +) +{ + // We're not going away because someone's got a pointer to us + // so there's no need to lock + + return AMGetWideString(Name(), Id); +} + +/* Does this pin support this media type WARNING this interface function does + not lock the main object as it is meant to be asynchronous by nature - if + the media types you support depend on some internal state that is updated + dynamically then you will need to implement locking in a derived class */ + +STDMETHODIMP +CBasePin::QueryAccept( + const AM_MEDIA_TYPE *pmt +) +{ + CheckPointer(pmt,E_POINTER); + ValidateReadPtr(pmt,sizeof(AM_MEDIA_TYPE)); + + /* The CheckMediaType method is valid to return error codes if the media + type is horrible, an example might be E_INVALIDARG. What we do here + is map all the error codes into either S_OK or S_FALSE regardless */ + + HRESULT hr = CheckMediaType((CMediaType*)pmt); + if (FAILED(hr)) { + return S_FALSE; + } + // note that the only defined success codes should be S_OK and S_FALSE... + return hr; +} + + +/* This can be called to return an enumerator for the pin's list of preferred + media types. An input pin is not obliged to have any preferred formats + although it can do. For example, the window renderer has a preferred type + which describes a video image that matches the current window size. All + output pins should expose at least one preferred format otherwise it is + possible that neither pin has any types and so no connection is possible */ + +STDMETHODIMP +CBasePin::EnumMediaTypes( + __deref_out IEnumMediaTypes **ppEnum +) +{ + CheckPointer(ppEnum,E_POINTER); + ValidateReadWritePtr(ppEnum,sizeof(IEnumMediaTypes *)); + + /* Create a new ref counted enumerator */ + + *ppEnum = new CEnumMediaTypes(this, + NULL); + + if (*ppEnum == NULL) { + return E_OUTOFMEMORY; + } + + return NOERROR; +} + + + +/* This is a virtual function that returns a media type corresponding with + place iPosition in the list. This base class simply returns an error as + we support no media types by default but derived classes should override */ + +HRESULT CBasePin::GetMediaType(int iPosition, __inout CMediaType *pMediaType) +{ + UNREFERENCED_PARAMETER(iPosition); + UNREFERENCED_PARAMETER(pMediaType); + return E_UNEXPECTED; +} + + +/* This is a virtual function that returns the current media type version. + The base class initialises the media type enumerators with the value 1 + By default we always returns that same value. A Derived class may change + the list of media types available and after doing so it should increment + the version either in a method derived from this, or more simply by just + incrementing the m_TypeVersion base pin variable. The type enumerators + call this when they want to see if their enumerations are out of date */ + +LONG CBasePin::GetMediaTypeVersion() +{ + return m_TypeVersion; +} + + +/* Increment the cookie representing the current media type version */ + +void CBasePin::IncrementTypeVersion() +{ + InterlockedIncrement(&m_TypeVersion); +} + + +/* Called by IMediaFilter implementation when the state changes from Stopped + to either paused or running and in derived classes could do things like + commit memory and grab hardware resource (the default is to do nothing) */ + +HRESULT +CBasePin::Active(void) +{ + return NOERROR; +} + +/* Called by IMediaFilter implementation when the state changes from + to either paused to running and in derived classes could do things like + commit memory and grab hardware resource (the default is to do nothing) */ + +HRESULT +CBasePin::Run(REFERENCE_TIME tStart) +{ + UNREFERENCED_PARAMETER(tStart); + return NOERROR; +} + + +/* Also called by the IMediaFilter implementation when the state changes to + Stopped at which point you should decommit allocators and free hardware + resources you grabbed in the Active call (default is also to do nothing) */ + +HRESULT +CBasePin::Inactive(void) +{ + m_bRunTimeError = FALSE; + return NOERROR; +} + + +// Called when no more data will arrive +STDMETHODIMP +CBasePin::EndOfStream(void) +{ + return S_OK; +} + + +STDMETHODIMP +CBasePin::SetSink(IQualityControl * piqc) +{ + CAutoLock cObjectLock(m_pLock); + if (piqc) ValidateReadPtr(piqc,sizeof(IQualityControl)); + m_pQSink = piqc; + return NOERROR; +} // SetSink + + +STDMETHODIMP +CBasePin::Notify(IBaseFilter * pSender, Quality q) +{ + UNREFERENCED_PARAMETER(q); + UNREFERENCED_PARAMETER(pSender); + DbgBreak("IQualityControl::Notify not over-ridden from CBasePin. (IGNORE is OK)"); + return E_NOTIMPL; +} //Notify + + +// NewSegment notifies of the start/stop/rate applying to the data +// about to be received. Default implementation records data and +// returns S_OK. +// Override this to pass downstream. +STDMETHODIMP +CBasePin::NewSegment( + REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate) +{ + m_tStart = tStart; + m_tStop = tStop; + m_dRate = dRate; + + return S_OK; +} + + +//===================================================================== +//===================================================================== +// Implements CBaseOutputPin +//===================================================================== +//===================================================================== + + +CBaseOutputPin::CBaseOutputPin(__in_opt LPCTSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName) : + CBasePin(pObjectName, pFilter, pLock, phr, pName, PINDIR_OUTPUT), + m_pAllocator(NULL), + m_pInputPin(NULL) +{ + ASSERT(pFilter); +} + +#ifdef UNICODE +CBaseOutputPin::CBaseOutputPin(__in_opt LPCSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName) : + CBasePin(pObjectName, pFilter, pLock, phr, pName, PINDIR_OUTPUT), + m_pAllocator(NULL), + m_pInputPin(NULL) +{ + ASSERT(pFilter); +} +#endif + +/* This is called after a media type has been proposed + + Try to complete the connection by agreeing the allocator +*/ +HRESULT +CBaseOutputPin::CompleteConnect(IPin *pReceivePin) +{ + UNREFERENCED_PARAMETER(pReceivePin); + return DecideAllocator(m_pInputPin, &m_pAllocator); +} + + +/* This method is called when the output pin is about to try and connect to + an input pin. It is at this point that you should try and grab any extra + interfaces that you need, in this case IMemInputPin. Because this is + only called if we are not currently connected we do NOT need to call + BreakConnect. This also makes it easier to derive classes from us as + BreakConnect is only called when we actually have to break a connection + (or a partly made connection) and not when we are checking a connection */ + +/* Overriden from CBasePin */ + +HRESULT +CBaseOutputPin::CheckConnect(IPin * pPin) +{ + HRESULT hr = CBasePin::CheckConnect(pPin); + if (FAILED(hr)) { + return hr; + } + + // get an input pin and an allocator interface + hr = pPin->QueryInterface(IID_IMemInputPin, (void **) &m_pInputPin); + if (FAILED(hr)) { + return hr; + } + return NOERROR; +} + + +/* Overriden from CBasePin */ + +HRESULT +CBaseOutputPin::BreakConnect() +{ + /* Release any allocator we hold */ + + if (m_pAllocator) { + // Always decommit the allocator because a downstream filter may or + // may not decommit the connection's allocator. A memory leak could + // occur if the allocator is not decommited when a connection is broken. + HRESULT hr = m_pAllocator->Decommit(); + if( FAILED( hr ) ) { + return hr; + } + + m_pAllocator->Release(); + m_pAllocator = NULL; + } + + /* Release any input pin interface we hold */ + + if (m_pInputPin) { + m_pInputPin->Release(); + m_pInputPin = NULL; + } + return NOERROR; +} + + +/* This is called when the input pin didn't give us a valid allocator */ + +HRESULT +CBaseOutputPin::InitAllocator(__deref_out IMemAllocator **ppAlloc) +{ + return CreateMemoryAllocator(ppAlloc); +} + + +/* Decide on an allocator, override this if you want to use your own allocator + Override DecideBufferSize to call SetProperties. If the input pin fails + the GetAllocator call then this will construct a CMemAllocator and call + DecideBufferSize on that, and if that fails then we are completely hosed. + If the you succeed the DecideBufferSize call, we will notify the input + pin of the selected allocator. NOTE this is called during Connect() which + therefore looks after grabbing and locking the object's critical section */ + +// We query the input pin for its requested properties and pass this to +// DecideBufferSize to allow it to fulfill requests that it is happy +// with (eg most people don't care about alignment and are thus happy to +// use the downstream pin's alignment request). + +HRESULT +CBaseOutputPin::DecideAllocator(IMemInputPin *pPin, __deref_out IMemAllocator **ppAlloc) +{ + HRESULT hr = NOERROR; + *ppAlloc = NULL; + + // get downstream prop request + // the derived class may modify this in DecideBufferSize, but + // we assume that he will consistently modify it the same way, + // so we only get it once + ALLOCATOR_PROPERTIES prop; + ZeroMemory(&prop, sizeof(prop)); + + // whatever he returns, we assume prop is either all zeros + // or he has filled it out. + pPin->GetAllocatorRequirements(&prop); + + // if he doesn't care about alignment, then set it to 1 + if (prop.cbAlign == 0) { + prop.cbAlign = 1; + } + + /* Try the allocator provided by the input pin */ + + hr = pPin->GetAllocator(ppAlloc); + if (SUCCEEDED(hr)) { + + hr = DecideBufferSize(*ppAlloc, &prop); + if (SUCCEEDED(hr)) { + hr = pPin->NotifyAllocator(*ppAlloc, FALSE); + if (SUCCEEDED(hr)) { + return NOERROR; + } + } + } + + /* If the GetAllocator failed we may not have an interface */ + + if (*ppAlloc) { + (*ppAlloc)->Release(); + *ppAlloc = NULL; + } + + /* Try the output pin's allocator by the same method */ + + hr = InitAllocator(ppAlloc); + if (SUCCEEDED(hr)) { + + // note - the properties passed here are in the same + // structure as above and may have been modified by + // the previous call to DecideBufferSize + hr = DecideBufferSize(*ppAlloc, &prop); + if (SUCCEEDED(hr)) { + hr = pPin->NotifyAllocator(*ppAlloc, FALSE); + if (SUCCEEDED(hr)) { + return NOERROR; + } + } + } + + /* Likewise we may not have an interface to release */ + + if (*ppAlloc) { + (*ppAlloc)->Release(); + *ppAlloc = NULL; + } + return hr; +} + + +/* This returns an empty sample buffer from the allocator WARNING the same + dangers and restrictions apply here as described below for Deliver() */ + +HRESULT +CBaseOutputPin::GetDeliveryBuffer(__deref_out IMediaSample ** ppSample, + __in_opt REFERENCE_TIME * pStartTime, + __in_opt REFERENCE_TIME * pEndTime, + DWORD dwFlags) +{ + if (m_pAllocator != NULL) { + return m_pAllocator->GetBuffer(ppSample,pStartTime,pEndTime,dwFlags); + } else { + return E_NOINTERFACE; + } +} + + +/* Deliver a filled-in sample to the connected input pin. NOTE the object must + have locked itself before calling us otherwise we may get halfway through + executing this method only to find the filter graph has got in and + disconnected us from the input pin. If the filter has no worker threads + then the lock is best applied on Receive(), otherwise it should be done + when the worker thread is ready to deliver. There is a wee snag to worker + threads that this shows up. The worker thread must lock the object when + it is ready to deliver a sample, but it may have to wait until a state + change has completed, but that may never complete because the state change + is waiting for the worker thread to complete. The way to handle this is for + the state change code to grab the critical section, then set an abort event + for the worker thread, then release the critical section and wait for the + worker thread to see the event we set and then signal that it has finished + (with another event). At which point the state change code can complete */ + +// note (if you've still got any breath left after reading that) that you +// need to release the sample yourself after this call. if the connected +// input pin needs to hold onto the sample beyond the call, it will addref +// the sample itself. + +// of course you must release this one and call GetDeliveryBuffer for the +// next. You cannot reuse it directly. + +HRESULT +CBaseOutputPin::Deliver(IMediaSample * pSample) +{ + if (m_pInputPin == NULL) { + return VFW_E_NOT_CONNECTED; + } + +#ifdef DXMPERF + PERFLOG_DELIVER( m_pName ? m_pName : L"CBaseOutputPin", (IPin *) this, (IPin *) m_pInputPin, pSample, &m_mt ); +#endif // DXMPERF + + return m_pInputPin->Receive(pSample); +} + + +// called from elsewhere in our filter to pass EOS downstream to +// our connected input pin +HRESULT +CBaseOutputPin::DeliverEndOfStream(void) +{ + // remember this is on IPin not IMemInputPin + if (m_Connected == NULL) { + return VFW_E_NOT_CONNECTED; + } + return m_Connected->EndOfStream(); +} + + +/* Commit the allocator's memory, this is called through IMediaFilter + which is responsible for locking the object before calling us */ + +HRESULT +CBaseOutputPin::Active(void) +{ + if (m_pAllocator == NULL) { + return VFW_E_NO_ALLOCATOR; + } + return m_pAllocator->Commit(); +} + + +/* Free up or unprepare allocator's memory, this is called through + IMediaFilter which is responsible for locking the object first */ + +HRESULT +CBaseOutputPin::Inactive(void) +{ + m_bRunTimeError = FALSE; + if (m_pAllocator == NULL) { + return VFW_E_NO_ALLOCATOR; + } + return m_pAllocator->Decommit(); +} + +// we have a default handling of EndOfStream which is to return +// an error, since this should be called on input pins only +STDMETHODIMP +CBaseOutputPin::EndOfStream(void) +{ + return E_UNEXPECTED; +} + + +// BeginFlush should be called on input pins only +STDMETHODIMP +CBaseOutputPin::BeginFlush(void) +{ + return E_UNEXPECTED; +} + +// EndFlush should be called on input pins only +STDMETHODIMP +CBaseOutputPin::EndFlush(void) +{ + return E_UNEXPECTED; +} + +// call BeginFlush on the connected input pin +HRESULT +CBaseOutputPin::DeliverBeginFlush(void) +{ + // remember this is on IPin not IMemInputPin + if (m_Connected == NULL) { + return VFW_E_NOT_CONNECTED; + } + return m_Connected->BeginFlush(); +} + +// call EndFlush on the connected input pin +HRESULT +CBaseOutputPin::DeliverEndFlush(void) +{ + // remember this is on IPin not IMemInputPin + if (m_Connected == NULL) { + return VFW_E_NOT_CONNECTED; + } + return m_Connected->EndFlush(); +} +// deliver NewSegment to connected pin +HRESULT +CBaseOutputPin::DeliverNewSegment( + REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate) +{ + if (m_Connected == NULL) { + return VFW_E_NOT_CONNECTED; + } + return m_Connected->NewSegment(tStart, tStop, dRate); +} + + +//===================================================================== +//===================================================================== +// Implements CBaseInputPin +//===================================================================== +//===================================================================== + + +/* Constructor creates a default allocator object */ + +CBaseInputPin::CBaseInputPin(__in_opt LPCTSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pPinName) : + CBasePin(pObjectName, pFilter, pLock, phr, pPinName, PINDIR_INPUT), + m_pAllocator(NULL), + m_bReadOnly(FALSE), + m_bFlushing(FALSE) +{ + ZeroMemory(&m_SampleProps, sizeof(m_SampleProps)); +} + +#ifdef UNICODE +CBaseInputPin::CBaseInputPin(__in LPCSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pPinName) : + CBasePin(pObjectName, pFilter, pLock, phr, pPinName, PINDIR_INPUT), + m_pAllocator(NULL), + m_bReadOnly(FALSE), + m_bFlushing(FALSE) +{ + ZeroMemory(&m_SampleProps, sizeof(m_SampleProps)); +} +#endif + +/* Destructor releases it's reference count on the default allocator */ + +CBaseInputPin::~CBaseInputPin() +{ + if (m_pAllocator != NULL) { + m_pAllocator->Release(); + m_pAllocator = NULL; + } +} + + +// override this to publicise our interfaces +STDMETHODIMP +CBaseInputPin::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + /* Do we know about this interface */ + + if (riid == IID_IMemInputPin) { + return GetInterface((IMemInputPin *) this, ppv); + } else { + return CBasePin::NonDelegatingQueryInterface(riid, ppv); + } +} + + +/* Return the allocator interface that this input pin would like the output + pin to use. NOTE subsequent calls to GetAllocator should all return an + interface onto the SAME object so we create one object at the start + + Note: + The allocator is Release()'d on disconnect and replaced on + NotifyAllocator(). + + Override this to provide your own allocator. +*/ + +STDMETHODIMP +CBaseInputPin::GetAllocator( + __deref_out IMemAllocator **ppAllocator) +{ + CheckPointer(ppAllocator,E_POINTER); + ValidateReadWritePtr(ppAllocator,sizeof(IMemAllocator *)); + CAutoLock cObjectLock(m_pLock); + + if (m_pAllocator == NULL) { + HRESULT hr = CreateMemoryAllocator(&m_pAllocator); + if (FAILED(hr)) { + return hr; + } + } + ASSERT(m_pAllocator != NULL); + *ppAllocator = m_pAllocator; + m_pAllocator->AddRef(); + return NOERROR; +} + + +/* Tell the input pin which allocator the output pin is actually going to use + Override this if you care - NOTE the locking we do both here and also in + GetAllocator is unnecessary but derived classes that do something useful + will undoubtedly have to lock the object so this might help remind people */ + +STDMETHODIMP +CBaseInputPin::NotifyAllocator( + IMemAllocator * pAllocator, + BOOL bReadOnly) +{ + CheckPointer(pAllocator,E_POINTER); + ValidateReadPtr(pAllocator,sizeof(IMemAllocator)); + CAutoLock cObjectLock(m_pLock); + + IMemAllocator *pOldAllocator = m_pAllocator; + pAllocator->AddRef(); + m_pAllocator = pAllocator; + + if (pOldAllocator != NULL) { + pOldAllocator->Release(); + } + + // the readonly flag indicates whether samples from this allocator should + // be regarded as readonly - if true, then inplace transforms will not be + // allowed. + m_bReadOnly = (BYTE)bReadOnly; + return NOERROR; +} + + +HRESULT +CBaseInputPin::BreakConnect() +{ + /* We don't need our allocator any more */ + if (m_pAllocator) { + // Always decommit the allocator because a downstream filter may or + // may not decommit the connection's allocator. A memory leak could + // occur if the allocator is not decommited when a pin is disconnected. + HRESULT hr = m_pAllocator->Decommit(); + if( FAILED( hr ) ) { + return hr; + } + + m_pAllocator->Release(); + m_pAllocator = NULL; + } + + return S_OK; +} + + +/* Do something with this media sample - this base class checks to see if the + format has changed with this media sample and if so checks that the filter + will accept it, generating a run time error if not. Once we have raised a + run time error we set a flag so that no more samples will be accepted + + It is important that any filter should override this method and implement + synchronization so that samples are not processed when the pin is + disconnected etc +*/ + +STDMETHODIMP +CBaseInputPin::Receive(IMediaSample *pSample) +{ + CheckPointer(pSample,E_POINTER); + ValidateReadPtr(pSample,sizeof(IMediaSample)); + ASSERT(pSample); + + HRESULT hr = CheckStreaming(); + if (S_OK != hr) { + return hr; + } + +#ifdef DXMPERF + PERFLOG_RECEIVE( m_pName ? m_pName : L"CBaseInputPin", (IPin *) m_Connected, (IPin *) this, pSample, &m_mt ); +#endif // DXMPERF + + + /* Check for IMediaSample2 */ + IMediaSample2 *pSample2; + if (SUCCEEDED(pSample->QueryInterface(IID_IMediaSample2, (void **)&pSample2))) { + hr = pSample2->GetProperties(sizeof(m_SampleProps), (PBYTE)&m_SampleProps); + pSample2->Release(); + if (FAILED(hr)) { + return hr; + } + } else { + /* Get the properties the hard way */ + m_SampleProps.cbData = sizeof(m_SampleProps); + m_SampleProps.dwTypeSpecificFlags = 0; + m_SampleProps.dwStreamId = AM_STREAM_MEDIA; + m_SampleProps.dwSampleFlags = 0; + if (S_OK == pSample->IsDiscontinuity()) { + m_SampleProps.dwSampleFlags |= AM_SAMPLE_DATADISCONTINUITY; + } + if (S_OK == pSample->IsPreroll()) { + m_SampleProps.dwSampleFlags |= AM_SAMPLE_PREROLL; + } + if (S_OK == pSample->IsSyncPoint()) { + m_SampleProps.dwSampleFlags |= AM_SAMPLE_SPLICEPOINT; + } + if (SUCCEEDED(pSample->GetTime(&m_SampleProps.tStart, + &m_SampleProps.tStop))) { + m_SampleProps.dwSampleFlags |= AM_SAMPLE_TIMEVALID | + AM_SAMPLE_STOPVALID; + } + if (S_OK == pSample->GetMediaType(&m_SampleProps.pMediaType)) { + m_SampleProps.dwSampleFlags |= AM_SAMPLE_TYPECHANGED; + } + pSample->GetPointer(&m_SampleProps.pbBuffer); + m_SampleProps.lActual = pSample->GetActualDataLength(); + m_SampleProps.cbBuffer = pSample->GetSize(); + } + + /* Has the format changed in this sample */ + + if (!(m_SampleProps.dwSampleFlags & AM_SAMPLE_TYPECHANGED)) { + return NOERROR; + } + + /* Check the derived class accepts this format */ + /* This shouldn't fail as the source must call QueryAccept first */ + + hr = CheckMediaType((CMediaType *)m_SampleProps.pMediaType); + + if (hr == NOERROR) { + return NOERROR; + } + + /* Raise a runtime error if we fail the media type */ + + m_bRunTimeError = TRUE; + EndOfStream(); + m_pFilter->NotifyEvent(EC_ERRORABORT,VFW_E_TYPE_NOT_ACCEPTED,0); + return VFW_E_INVALIDMEDIATYPE; +} + + +/* Receive multiple samples */ +STDMETHODIMP +CBaseInputPin::ReceiveMultiple ( + __in_ecount(nSamples) IMediaSample **pSamples, + long nSamples, + __out long *nSamplesProcessed) +{ + CheckPointer(pSamples,E_POINTER); + ValidateReadPtr(pSamples,nSamples * sizeof(IMediaSample *)); + + HRESULT hr = S_OK; + *nSamplesProcessed = 0; + while (nSamples-- > 0) { + hr = Receive(pSamples[*nSamplesProcessed]); + + /* S_FALSE means don't send any more */ + if (hr != S_OK) { + break; + } + (*nSamplesProcessed)++; + } + return hr; +} + +/* See if Receive() might block */ +STDMETHODIMP +CBaseInputPin::ReceiveCanBlock() +{ + /* Ask all the output pins if they block + If there are no output pin assume we do block + */ + int cPins = m_pFilter->GetPinCount(); + int cOutputPins = 0; + for (int c = 0; c < cPins; c++) { + CBasePin *pPin = m_pFilter->GetPin(c); + if (NULL == pPin) { + break; + } + PIN_DIRECTION pd; + HRESULT hr = pPin->QueryDirection(&pd); + if (FAILED(hr)) { + return hr; + } + + if (pd == PINDIR_OUTPUT) { + + IPin *pConnected; + hr = pPin->ConnectedTo(&pConnected); + if (SUCCEEDED(hr)) { + ASSERT(pConnected != NULL); + cOutputPins++; + IMemInputPin *pInputPin; + hr = pConnected->QueryInterface( + IID_IMemInputPin, + (void **)&pInputPin); + pConnected->Release(); + if (SUCCEEDED(hr)) { + hr = pInputPin->ReceiveCanBlock(); + pInputPin->Release(); + if (hr != S_FALSE) { + return S_OK; + } + } else { + /* There's a transport we don't understand here */ + return S_OK; + } + } + } + } + return cOutputPins == 0 ? S_OK : S_FALSE; +} + +// Default handling for BeginFlush - call at the beginning +// of your implementation (makes sure that all Receive calls +// fail). After calling this, you need to free any queued data +// and then call downstream. +STDMETHODIMP +CBaseInputPin::BeginFlush(void) +{ + // BeginFlush is NOT synchronized with streaming but is part of + // a control action - hence we synchronize with the filter + CAutoLock lck(m_pLock); + + // if we are already in mid-flush, this is probably a mistake + // though not harmful - try to pick it up for now so I can think about it + ASSERT(!m_bFlushing); + + // first thing to do is ensure that no further Receive calls succeed + m_bFlushing = TRUE; + + // now discard any data and call downstream - must do that + // in derived classes + return S_OK; +} + +// default handling for EndFlush - call at end of your implementation +// - before calling this, ensure that there is no queued data and no thread +// pushing any more without a further receive, then call downstream, +// then call this method to clear the m_bFlushing flag and re-enable +// receives +STDMETHODIMP +CBaseInputPin::EndFlush(void) +{ + // Endlush is NOT synchronized with streaming but is part of + // a control action - hence we synchronize with the filter + CAutoLock lck(m_pLock); + + // almost certainly a mistake if we are not in mid-flush + ASSERT(m_bFlushing); + + // before calling, sync with pushing thread and ensure + // no more data is going downstream, then call EndFlush on + // downstream pins. + + // now re-enable Receives + m_bFlushing = FALSE; + + // No more errors + m_bRunTimeError = FALSE; + + return S_OK; +} + + +STDMETHODIMP +CBaseInputPin::Notify(IBaseFilter * pSender, Quality q) +{ + UNREFERENCED_PARAMETER(q); + CheckPointer(pSender,E_POINTER); + ValidateReadPtr(pSender,sizeof(IBaseFilter)); + DbgBreak("IQuality::Notify called on an input pin"); + return NOERROR; +} // Notify + +/* Free up or unprepare allocator's memory, this is called through + IMediaFilter which is responsible for locking the object first */ + +HRESULT +CBaseInputPin::Inactive(void) +{ + m_bRunTimeError = FALSE; + if (m_pAllocator == NULL) { + return VFW_E_NO_ALLOCATOR; + } + + m_bFlushing = FALSE; + + return m_pAllocator->Decommit(); +} + +// what requirements do we have of the allocator - override if you want +// to support other people's allocators but need a specific alignment +// or prefix. +STDMETHODIMP +CBaseInputPin::GetAllocatorRequirements(__out ALLOCATOR_PROPERTIES*pProps) +{ + UNREFERENCED_PARAMETER(pProps); + return E_NOTIMPL; +} + +// Check if it's OK to process data +// +HRESULT +CBaseInputPin::CheckStreaming() +{ + // Shouldn't be able to get any data if we're not connected! + ASSERT(IsConnected()); + + // Don't process stuff in Stopped state + if (IsStopped()) { + return VFW_E_WRONG_STATE; + } + if (m_bFlushing) { + return S_FALSE; + } + if (m_bRunTimeError) { + return VFW_E_RUNTIME_ERROR; + } + return S_OK; +} + +// Pass on the Quality notification q to +// a. Our QualityControl sink (if we have one) or else +// b. to our upstream filter +// and if that doesn't work, throw it away with a bad return code +HRESULT +CBaseInputPin::PassNotify(Quality& q) +{ + // We pass the message on, which means that we find the quality sink + // for our input pin and send it there + + DbgLog((LOG_TRACE,3,TEXT("Passing Quality notification through transform"))); + if (m_pQSink!=NULL) { + return m_pQSink->Notify(m_pFilter, q); + } else { + // no sink set, so pass it upstream + HRESULT hr; + IQualityControl * pIQC; + + hr = VFW_E_NOT_FOUND; // default + if (m_Connected) { + m_Connected->QueryInterface(IID_IQualityControl, (void**)&pIQC); + + if (pIQC!=NULL) { + hr = pIQC->Notify(m_pFilter, q); + pIQC->Release(); + } + } + return hr; + } + +} // PassNotify + +//===================================================================== +//===================================================================== +// Memory allocation class, implements CMediaSample +//===================================================================== +//===================================================================== + + +/* NOTE The implementation of this class calls the CUnknown constructor with + a NULL outer unknown pointer. This has the effect of making us a self + contained class, ie any QueryInterface, AddRef or Release calls will be + routed to the class's NonDelegatingUnknown methods. You will typically + find that the classes that do this then override one or more of these + virtual functions to provide more specialised behaviour. A good example + of this is where a class wants to keep the QueryInterface internal but + still wants it's lifetime controlled by the external object */ + +/* The last two parameters have default values of NULL and zero */ + +CMediaSample::CMediaSample(__in_opt LPCTSTR pName, + __in_opt CBaseAllocator *pAllocator, + __inout_opt HRESULT *phr, + __in_bcount_opt(length) LPBYTE pBuffer, + LONG length) : + m_pBuffer(pBuffer), // Initialise the buffer + m_cbBuffer(length), // And it's length + m_lActual(length), // By default, actual = length + m_pMediaType(NULL), // No media type change + m_dwFlags(0), // Nothing set + m_cRef(0), // 0 ref count + m_dwTypeSpecificFlags(0), // Type specific flags + m_dwStreamId(AM_STREAM_MEDIA), // Stream id + m_pAllocator(pAllocator) // Allocator +{ +#ifdef DXMPERF + PERFLOG_CTOR( pName ? pName : L"CMediaSample", (IMediaSample *) this ); +#endif // DXMPERF + + /* We must have an owner and it must also be derived from class + CBaseAllocator BUT we do not hold a reference count on it */ + + ASSERT(pAllocator); + + if (length < 0) { + *phr = VFW_E_BUFFER_OVERFLOW; + m_cbBuffer = 0; + } +} + +#ifdef UNICODE +CMediaSample::CMediaSample(__in_opt LPCSTR pName, + __in_opt CBaseAllocator *pAllocator, + __inout_opt HRESULT *phr, + __in_bcount_opt(length) LPBYTE pBuffer, + LONG length) : + m_pBuffer(pBuffer), // Initialise the buffer + m_cbBuffer(length), // And it's length + m_lActual(length), // By default, actual = length + m_pMediaType(NULL), // No media type change + m_dwFlags(0), // Nothing set + m_cRef(0), // 0 ref count + m_dwTypeSpecificFlags(0), // Type specific flags + m_dwStreamId(AM_STREAM_MEDIA), // Stream id + m_pAllocator(pAllocator) // Allocator +{ +#ifdef DXMPERF + PERFLOG_CTOR( L"CMediaSample", (IMediaSample *) this ); +#endif // DXMPERF + + /* We must have an owner and it must also be derived from class + CBaseAllocator BUT we do not hold a reference count on it */ + + ASSERT(pAllocator); +} +#endif + +/* Destructor deletes the media type memory */ + +CMediaSample::~CMediaSample() +{ +#ifdef DXMPERF + PERFLOG_DTOR( L"CMediaSample", (IMediaSample *) this ); +#endif // DXMPERF + + if (m_pMediaType) { + DeleteMediaType(m_pMediaType); + } +} + +/* Override this to publicise our interfaces */ + +STDMETHODIMP +CMediaSample::QueryInterface(REFIID riid, __deref_out void **ppv) +{ + if (riid == IID_IMediaSample || + riid == IID_IMediaSample2 || + riid == IID_IUnknown) { + return GetInterface((IMediaSample *) this, ppv); + } else { + *ppv = NULL; + return E_NOINTERFACE; + } +} + +STDMETHODIMP_(ULONG) +CMediaSample::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + + +// -- CMediaSample lifetimes -- +// +// On final release of this sample buffer it is not deleted but +// returned to the freelist of the owning memory allocator +// +// The allocator may be waiting for the last buffer to be placed on the free +// list in order to decommit all the memory, so the ReleaseBuffer() call may +// result in this sample being deleted. We also need to hold a refcount on +// the allocator to stop that going away until we have finished with this. +// However, we cannot release the allocator before the ReleaseBuffer, as the +// release may cause us to be deleted. Similarly we can't do it afterwards. +// +// Thus we must leave it to the allocator to hold an addref on our behalf. +// When he issues us in GetBuffer, he addref's himself. When ReleaseBuffer +// is called, he releases himself, possibly causing us and him to be deleted. + + +STDMETHODIMP_(ULONG) +CMediaSample::Release() +{ + /* Decrement our own private reference count */ + LONG lRef; + if (m_cRef == 1) { + lRef = 0; + m_cRef = 0; + } else { + lRef = InterlockedDecrement(&m_cRef); + } + ASSERT(lRef >= 0); + + DbgLog((LOG_MEMORY,3,TEXT(" Unknown %X ref-- = %d"), + this, m_cRef)); + + /* Did we release our final reference count */ + if (lRef == 0) { + /* Free all resources */ + if (m_dwFlags & Sample_TypeChanged) { + SetMediaType(NULL); + } + ASSERT(m_pMediaType == NULL); + m_dwFlags = 0; + m_dwTypeSpecificFlags = 0; + m_dwStreamId = AM_STREAM_MEDIA; + + /* This may cause us to be deleted */ + // Our refcount is reliably 0 thus no-one will mess with us + m_pAllocator->ReleaseBuffer(this); + } + return (ULONG)lRef; +} + + +// set the buffer pointer and length. Used by allocators that +// want variable sized pointers or pointers into already-read data. +// This is only available through a CMediaSample* not an IMediaSample* +// and so cannot be changed by clients. +HRESULT +CMediaSample::SetPointer(__in_bcount(cBytes) BYTE * ptr, LONG cBytes) +{ + if (cBytes < 0) { + return VFW_E_BUFFER_OVERFLOW; + } + m_pBuffer = ptr; // new buffer area (could be null) + m_cbBuffer = cBytes; // length of buffer + m_lActual = cBytes; // length of data in buffer (assume full) + + return S_OK; +} + + +// get me a read/write pointer to this buffer's memory. I will actually +// want to use sizeUsed bytes. +STDMETHODIMP +CMediaSample::GetPointer(__deref_out BYTE ** ppBuffer) +{ + ValidateReadWritePtr(ppBuffer,sizeof(BYTE *)); + + // creator must have set pointer either during + // constructor or by SetPointer + ASSERT(m_pBuffer); + + *ppBuffer = m_pBuffer; + return NOERROR; +} + + +// return the size in bytes of this buffer +STDMETHODIMP_(LONG) +CMediaSample::GetSize(void) +{ + return m_cbBuffer; +} + + +// get the stream time at which this sample should start and finish. +STDMETHODIMP +CMediaSample::GetTime( + __out REFERENCE_TIME * pTimeStart, // put time here + __out REFERENCE_TIME * pTimeEnd +) +{ + ValidateReadWritePtr(pTimeStart,sizeof(REFERENCE_TIME)); + ValidateReadWritePtr(pTimeEnd,sizeof(REFERENCE_TIME)); + + if (!(m_dwFlags & Sample_StopValid)) { + if (!(m_dwFlags & Sample_TimeValid)) { + return VFW_E_SAMPLE_TIME_NOT_SET; + } else { + *pTimeStart = m_Start; + + // Make sure old stuff works + *pTimeEnd = m_Start + 1; + return VFW_S_NO_STOP_TIME; + } + } + + *pTimeStart = m_Start; + *pTimeEnd = m_End; + return NOERROR; +} + + +// Set the stream time at which this sample should start and finish. +// NULL pointers means the time is reset +STDMETHODIMP +CMediaSample::SetTime( + __in_opt REFERENCE_TIME * pTimeStart, + __in_opt REFERENCE_TIME * pTimeEnd +) +{ + if (pTimeStart == NULL) { + ASSERT(pTimeEnd == NULL); + m_dwFlags &= ~(Sample_TimeValid | Sample_StopValid); + } else { + if (pTimeEnd == NULL) { + m_Start = *pTimeStart; + m_dwFlags |= Sample_TimeValid; + m_dwFlags &= ~Sample_StopValid; + } else { + ValidateReadPtr(pTimeStart,sizeof(REFERENCE_TIME)); + ValidateReadPtr(pTimeEnd,sizeof(REFERENCE_TIME)); + ASSERT(*pTimeEnd >= *pTimeStart); + + m_Start = *pTimeStart; + m_End = *pTimeEnd; + m_dwFlags |= Sample_TimeValid | Sample_StopValid; + } + } + return NOERROR; +} + + +// get the media times (eg bytes) for this sample +STDMETHODIMP +CMediaSample::GetMediaTime( + __out LONGLONG * pTimeStart, + __out LONGLONG * pTimeEnd +) +{ + ValidateReadWritePtr(pTimeStart,sizeof(LONGLONG)); + ValidateReadWritePtr(pTimeEnd,sizeof(LONGLONG)); + + if (!(m_dwFlags & Sample_MediaTimeValid)) { + return VFW_E_MEDIA_TIME_NOT_SET; + } + + *pTimeStart = m_MediaStart; + *pTimeEnd = (m_MediaStart + m_MediaEnd); + return NOERROR; +} + + +// Set the media times for this sample +STDMETHODIMP +CMediaSample::SetMediaTime( + __in_opt LONGLONG * pTimeStart, + __in_opt LONGLONG * pTimeEnd +) +{ + if (pTimeStart == NULL) { + ASSERT(pTimeEnd == NULL); + m_dwFlags &= ~Sample_MediaTimeValid; + } else { + if (NULL == pTimeEnd) { + return E_POINTER; + } + ValidateReadPtr(pTimeStart,sizeof(LONGLONG)); + ValidateReadPtr(pTimeEnd,sizeof(LONGLONG)); + ASSERT(*pTimeEnd >= *pTimeStart); + + m_MediaStart = *pTimeStart; + m_MediaEnd = (LONG)(*pTimeEnd - *pTimeStart); + m_dwFlags |= Sample_MediaTimeValid; + } + return NOERROR; +} + + +STDMETHODIMP +CMediaSample::IsSyncPoint(void) +{ + if (m_dwFlags & Sample_SyncPoint) { + return S_OK; + } else { + return S_FALSE; + } +} + + +STDMETHODIMP +CMediaSample::SetSyncPoint(BOOL bIsSyncPoint) +{ + if (bIsSyncPoint) { + m_dwFlags |= Sample_SyncPoint; + } else { + m_dwFlags &= ~Sample_SyncPoint; + } + return NOERROR; +} + +// returns S_OK if there is a discontinuity in the data (this same is +// not a continuation of the previous stream of data +// - there has been a seek). +STDMETHODIMP +CMediaSample::IsDiscontinuity(void) +{ + if (m_dwFlags & Sample_Discontinuity) { + return S_OK; + } else { + return S_FALSE; + } +} + +// set the discontinuity property - TRUE if this sample is not a +// continuation, but a new sample after a seek. +STDMETHODIMP +CMediaSample::SetDiscontinuity(BOOL bDiscont) +{ + // should be TRUE or FALSE + if (bDiscont) { + m_dwFlags |= Sample_Discontinuity; + } else { + m_dwFlags &= ~Sample_Discontinuity; + } + return S_OK; +} + +STDMETHODIMP +CMediaSample::IsPreroll(void) +{ + if (m_dwFlags & Sample_Preroll) { + return S_OK; + } else { + return S_FALSE; + } +} + + +STDMETHODIMP +CMediaSample::SetPreroll(BOOL bIsPreroll) +{ + if (bIsPreroll) { + m_dwFlags |= Sample_Preroll; + } else { + m_dwFlags &= ~Sample_Preroll; + } + return NOERROR; +} + +STDMETHODIMP_(LONG) +CMediaSample::GetActualDataLength(void) +{ + return m_lActual; +} + + +STDMETHODIMP +CMediaSample::SetActualDataLength(LONG lActual) +{ + if (lActual > m_cbBuffer || lActual < 0) { + ASSERT(lActual <= GetSize()); + return VFW_E_BUFFER_OVERFLOW; + } + m_lActual = lActual; + return NOERROR; +} + + +/* These allow for limited format changes in band */ + +STDMETHODIMP +CMediaSample::GetMediaType(__deref_out AM_MEDIA_TYPE **ppMediaType) +{ + ValidateReadWritePtr(ppMediaType,sizeof(AM_MEDIA_TYPE *)); + ASSERT(ppMediaType); + + /* Do we have a new media type for them */ + + if (!(m_dwFlags & Sample_TypeChanged)) { + ASSERT(m_pMediaType == NULL); + *ppMediaType = NULL; + return S_FALSE; + } + + ASSERT(m_pMediaType); + + /* Create a copy of our media type */ + + *ppMediaType = CreateMediaType(m_pMediaType); + if (*ppMediaType == NULL) { + return E_OUTOFMEMORY; + } + return NOERROR; +} + + +/* Mark this sample as having a different format type */ + +STDMETHODIMP +CMediaSample::SetMediaType(__in_opt AM_MEDIA_TYPE *pMediaType) +{ + /* Delete the current media type */ + + if (m_pMediaType) { + DeleteMediaType(m_pMediaType); + m_pMediaType = NULL; + } + + /* Mechanism for resetting the format type */ + + if (pMediaType == NULL) { + m_dwFlags &= ~Sample_TypeChanged; + return NOERROR; + } + + ASSERT(pMediaType); + ValidateReadPtr(pMediaType,sizeof(AM_MEDIA_TYPE)); + + /* Take a copy of the media type */ + + m_pMediaType = CreateMediaType(pMediaType); + if (m_pMediaType == NULL) { + m_dwFlags &= ~Sample_TypeChanged; + return E_OUTOFMEMORY; + } + + m_dwFlags |= Sample_TypeChanged; + return NOERROR; +} + +// Set and get properties (IMediaSample2) +STDMETHODIMP CMediaSample::GetProperties( + DWORD cbProperties, + __out_bcount(cbProperties) BYTE * pbProperties +) +{ + if (0 != cbProperties) { + CheckPointer(pbProperties, E_POINTER); + // Return generic stuff up to the length + AM_SAMPLE2_PROPERTIES Props; + Props.cbData = min(cbProperties, sizeof(Props)); + Props.dwSampleFlags = m_dwFlags & ~Sample_MediaTimeValid; + Props.dwTypeSpecificFlags = m_dwTypeSpecificFlags; + Props.pbBuffer = m_pBuffer; + Props.cbBuffer = m_cbBuffer; + Props.lActual = m_lActual; + Props.tStart = m_Start; + Props.tStop = m_End; + Props.dwStreamId = m_dwStreamId; + if (m_dwFlags & AM_SAMPLE_TYPECHANGED) { + Props.pMediaType = m_pMediaType; + } else { + Props.pMediaType = NULL; + } + CopyMemory(pbProperties, &Props, Props.cbData); + } + return S_OK; +} + +#define CONTAINS_FIELD(type, field, offset) \ + ((FIELD_OFFSET(type, field) + sizeof(((type *)0)->field)) <= offset) + +HRESULT CMediaSample::SetProperties( + DWORD cbProperties, + __in_bcount(cbProperties) const BYTE * pbProperties +) +{ + + /* Generic properties */ + AM_MEDIA_TYPE *pMediaType = NULL; + + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, cbData, cbProperties)) { + CheckPointer(pbProperties, E_POINTER); + AM_SAMPLE2_PROPERTIES *pProps = + (AM_SAMPLE2_PROPERTIES *)pbProperties; + + /* Don't use more data than is actually there */ + if (pProps->cbData < cbProperties) { + cbProperties = pProps->cbData; + } + /* We only handle IMediaSample2 */ + if (cbProperties > sizeof(*pProps) || + pProps->cbData > sizeof(*pProps)) { + return E_INVALIDARG; + } + /* Do checks first, the assignments (for backout) */ + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, dwSampleFlags, cbProperties)) { + /* Check the flags */ + if (pProps->dwSampleFlags & + (~Sample_ValidFlags | Sample_MediaTimeValid)) { + return E_INVALIDARG; + } + /* Check a flag isn't being set for a property + not being provided + */ + if ((pProps->dwSampleFlags & AM_SAMPLE_TIMEVALID) && + !(m_dwFlags & AM_SAMPLE_TIMEVALID) && + !CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, tStop, cbProperties)) { + return E_INVALIDARG; + } + } + /* NB - can't SET the pointer or size */ + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, pbBuffer, cbProperties)) { + + /* Check pbBuffer */ + if (pProps->pbBuffer != 0 && pProps->pbBuffer != m_pBuffer) { + return E_INVALIDARG; + } + } + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, cbBuffer, cbProperties)) { + + /* Check cbBuffer */ + if (pProps->cbBuffer != 0 && pProps->cbBuffer != m_cbBuffer) { + return E_INVALIDARG; + } + } + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, cbBuffer, cbProperties) && + CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, lActual, cbProperties)) { + + /* Check lActual */ + if (pProps->cbBuffer < pProps->lActual) { + return E_INVALIDARG; + } + } + + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, pMediaType, cbProperties)) { + + /* Check pMediaType */ + if (pProps->dwSampleFlags & AM_SAMPLE_TYPECHANGED) { + CheckPointer(pProps->pMediaType, E_POINTER); + pMediaType = CreateMediaType(pProps->pMediaType); + if (pMediaType == NULL) { + return E_OUTOFMEMORY; + } + } + } + + /* Now do the assignments */ + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, dwStreamId, cbProperties)) { + m_dwStreamId = pProps->dwStreamId; + } + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, dwSampleFlags, cbProperties)) { + /* Set the flags */ + m_dwFlags = pProps->dwSampleFlags | + (m_dwFlags & Sample_MediaTimeValid); + m_dwTypeSpecificFlags = pProps->dwTypeSpecificFlags; + } else { + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, dwTypeSpecificFlags, cbProperties)) { + m_dwTypeSpecificFlags = pProps->dwTypeSpecificFlags; + } + } + + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, lActual, cbProperties)) { + /* Set lActual */ + m_lActual = pProps->lActual; + } + + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, tStop, cbProperties)) { + + /* Set the times */ + m_End = pProps->tStop; + } + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, tStart, cbProperties)) { + + /* Set the times */ + m_Start = pProps->tStart; + } + + if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, pMediaType, cbProperties)) { + /* Set pMediaType */ + if (pProps->dwSampleFlags & AM_SAMPLE_TYPECHANGED) { + if (m_pMediaType != NULL) { + DeleteMediaType(m_pMediaType); + } + m_pMediaType = pMediaType; + } + } + + /* Fix up the type changed flag to correctly reflect the current state + If, for instance the input contained no type change but the + output does then if we don't do this we'd lose the + output media type. + */ + if (m_pMediaType) { + m_dwFlags |= Sample_TypeChanged; + } else { + m_dwFlags &= ~Sample_TypeChanged; + } + } + + return S_OK; +} + + +// +// The streaming thread calls IPin::NewSegment(), IPin::EndOfStream(), +// IMemInputPin::Receive() and IMemInputPin::ReceiveMultiple() on the +// connected input pin. The application thread calls Block(). The +// following class members can only be called by the streaming thread. +// +// Deliver() +// DeliverNewSegment() +// StartUsingOutputPin() +// StopUsingOutputPin() +// ChangeOutputFormat() +// ChangeMediaType() +// DynamicReconnect() +// +// The following class members can only be called by the application thread. +// +// Block() +// SynchronousBlockOutputPin() +// AsynchronousBlockOutputPin() +// + +CDynamicOutputPin::CDynamicOutputPin( + __in_opt LPCTSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName) : + CBaseOutputPin(pObjectName, pFilter, pLock, phr, pName), + m_hStopEvent(NULL), + m_pGraphConfig(NULL), + m_bPinUsesReadOnlyAllocator(FALSE), + m_BlockState(NOT_BLOCKED), + m_hUnblockOutputPinEvent(NULL), + m_hNotifyCallerPinBlockedEvent(NULL), + m_dwBlockCallerThreadID(0), + m_dwNumOutstandingOutputPinUsers(0) +{ + HRESULT hr = Initialize(); + if( FAILED( hr ) ) { + *phr = hr; + return; + } +} + +#ifdef UNICODE +CDynamicOutputPin::CDynamicOutputPin( + __in_opt LPCSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName) : + CBaseOutputPin(pObjectName, pFilter, pLock, phr, pName), + m_hStopEvent(NULL), + m_pGraphConfig(NULL), + m_bPinUsesReadOnlyAllocator(FALSE), + m_BlockState(NOT_BLOCKED), + m_hUnblockOutputPinEvent(NULL), + m_hNotifyCallerPinBlockedEvent(NULL), + m_dwBlockCallerThreadID(0), + m_dwNumOutstandingOutputPinUsers(0) +{ + HRESULT hr = Initialize(); + if( FAILED( hr ) ) { + *phr = hr; + return; + } +} +#endif + +CDynamicOutputPin::~CDynamicOutputPin() +{ + if(NULL != m_hUnblockOutputPinEvent) { + // This call should not fail because we have access to m_hUnblockOutputPinEvent + // and m_hUnblockOutputPinEvent is a valid event. + EXECUTE_ASSERT(::CloseHandle(m_hUnblockOutputPinEvent)); + } + + if(NULL != m_hNotifyCallerPinBlockedEvent) { + // This call should not fail because we have access to m_hNotifyCallerPinBlockedEvent + // and m_hNotifyCallerPinBlockedEvent is a valid event. + EXECUTE_ASSERT(::CloseHandle(m_hNotifyCallerPinBlockedEvent)); + } +} + +HRESULT CDynamicOutputPin::Initialize(void) +{ + m_hUnblockOutputPinEvent = ::CreateEvent( NULL, // The event will have the default security descriptor. + TRUE, // This is a manual reset event. + TRUE, // The event is initially signaled. + NULL ); // The event is not named. + + // CreateEvent() returns NULL if an error occurs. + if(NULL == m_hUnblockOutputPinEvent) { + return AmGetLastErrorToHResult(); + } + + // Set flag to say we can reconnect while streaming. + SetReconnectWhenActive(true); + + return S_OK; +} + +STDMETHODIMP CDynamicOutputPin::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + if(riid == IID_IPinFlowControl) { + return GetInterface(static_cast<IPinFlowControl*>(this), ppv); + } else { + return CBaseOutputPin::NonDelegatingQueryInterface(riid, ppv); + } +} + +STDMETHODIMP CDynamicOutputPin::Disconnect(void) +{ + CAutoLock cObjectLock(m_pLock); + return DisconnectInternal(); +} + +STDMETHODIMP CDynamicOutputPin::Block(DWORD dwBlockFlags, HANDLE hEvent) +{ + const DWORD VALID_FLAGS = AM_PIN_FLOW_CONTROL_BLOCK; + + // Check for illegal flags. + if(dwBlockFlags & ~VALID_FLAGS) { + return E_INVALIDARG; + } + + // Make sure the event is unsignaled. + if((dwBlockFlags & AM_PIN_FLOW_CONTROL_BLOCK) && (NULL != hEvent)) { + if( !::ResetEvent( hEvent ) ) { + return AmGetLastErrorToHResult(); + } + } + + // No flags are set if we are unblocking the output pin. + if(0 == dwBlockFlags) { + + // This parameter should be NULL because unblock operations are always synchronous. + // There is no need to notify the caller when the event is done. + if(NULL != hEvent) { + return E_INVALIDARG; + } + } + + #ifdef DEBUG + AssertValid(); + #endif // DEBUG + + HRESULT hr; + + if(dwBlockFlags & AM_PIN_FLOW_CONTROL_BLOCK) { + // IPinFlowControl::Block()'s hEvent parameter is NULL if the block is synchronous. + // If hEvent is not NULL, the block is asynchronous. + if(NULL == hEvent) { + hr = SynchronousBlockOutputPin(); + } else { + hr = AsynchronousBlockOutputPin(hEvent); + } + } else { + hr = UnblockOutputPin(); + } + + #ifdef DEBUG + AssertValid(); + #endif // DEBUG + + if(FAILED(hr)) { + return hr; + } + + return S_OK; +} + +HRESULT CDynamicOutputPin::SynchronousBlockOutputPin(void) +{ + HANDLE hNotifyCallerPinBlockedEvent = :: CreateEvent( NULL, // The event will have the default security attributes. + FALSE, // This is an automatic reset event. + FALSE, // The event is initially unsignaled. + NULL ); // The event is not named. + + // CreateEvent() returns NULL if an error occurs. + if(NULL == hNotifyCallerPinBlockedEvent) { + return AmGetLastErrorToHResult(); + } + + HRESULT hr = AsynchronousBlockOutputPin(hNotifyCallerPinBlockedEvent); + if(FAILED(hr)) { + // This call should not fail because we have access to hNotifyCallerPinBlockedEvent + // and hNotifyCallerPinBlockedEvent is a valid event. + EXECUTE_ASSERT(::CloseHandle(hNotifyCallerPinBlockedEvent)); + + return hr; + } + + hr = WaitEvent(hNotifyCallerPinBlockedEvent); + + // This call should not fail because we have access to hNotifyCallerPinBlockedEvent + // and hNotifyCallerPinBlockedEvent is a valid event. + EXECUTE_ASSERT(::CloseHandle(hNotifyCallerPinBlockedEvent)); + + if(FAILED(hr)) { + return hr; + } + + return S_OK; +} + +HRESULT CDynamicOutputPin::AsynchronousBlockOutputPin(HANDLE hNotifyCallerPinBlockedEvent) +{ + // This function holds the m_BlockStateLock because it uses + // m_dwBlockCallerThreadID, m_BlockState and + // m_hNotifyCallerPinBlockedEvent. + CAutoLock alBlockStateLock(&m_BlockStateLock); + + if(NOT_BLOCKED != m_BlockState) { + if(m_dwBlockCallerThreadID == ::GetCurrentThreadId()) { + return VFW_E_PIN_ALREADY_BLOCKED_ON_THIS_THREAD; + } else { + return VFW_E_PIN_ALREADY_BLOCKED; + } + } + + BOOL fSuccess = ::DuplicateHandle( ::GetCurrentProcess(), + hNotifyCallerPinBlockedEvent, + ::GetCurrentProcess(), + &m_hNotifyCallerPinBlockedEvent, + EVENT_MODIFY_STATE, + FALSE, + 0 ); + if( !fSuccess ) { + return AmGetLastErrorToHResult(); + } + + m_BlockState = PENDING; + m_dwBlockCallerThreadID = ::GetCurrentThreadId(); + + // The output pin cannot be blocked if the streaming thread is + // calling IPin::NewSegment(), IPin::EndOfStream(), IMemInputPin::Receive() + // or IMemInputPin::ReceiveMultiple() on the connected input pin. Also, it + // cannot be blocked if the streaming thread is calling DynamicReconnect(), + // ChangeMediaType() or ChangeOutputFormat(). + if(!StreamingThreadUsingOutputPin()) { + + // The output pin can be immediately blocked. + BlockOutputPin(); + } + + return S_OK; +} + +void CDynamicOutputPin::BlockOutputPin(void) +{ + // The caller should always hold the m_BlockStateLock because this function + // uses m_BlockState and m_hNotifyCallerPinBlockedEvent. + ASSERT(CritCheckIn(&m_BlockStateLock)); + + // This function should not be called if the streaming thread is modifying + // the connection state or it's passing data downstream. + ASSERT(!StreamingThreadUsingOutputPin()); + + // This should not fail because we successfully created the event + // and we have the security permissions to change it's state. + EXECUTE_ASSERT(::ResetEvent(m_hUnblockOutputPinEvent)); + + // This event should not fail because AsynchronousBlockOutputPin() successfully + // duplicated this handle and we have the appropriate security permissions. + EXECUTE_ASSERT(::SetEvent(m_hNotifyCallerPinBlockedEvent)); + EXECUTE_ASSERT(::CloseHandle(m_hNotifyCallerPinBlockedEvent)); + + m_BlockState = BLOCKED; + m_hNotifyCallerPinBlockedEvent = NULL; +} + +HRESULT CDynamicOutputPin::UnblockOutputPin(void) +{ + // UnblockOutputPin() holds the m_BlockStateLock because it + // uses m_BlockState, m_dwBlockCallerThreadID and + // m_hNotifyCallerPinBlockedEvent. + CAutoLock alBlockStateLock(&m_BlockStateLock); + + if(NOT_BLOCKED == m_BlockState) { + return S_FALSE; + } + + // This should not fail because we successfully created the event + // and we have the security permissions to change it's state. + EXECUTE_ASSERT(::SetEvent(m_hUnblockOutputPinEvent)); + + // Cancel the block operation if it's still pending. + if(NULL != m_hNotifyCallerPinBlockedEvent) { + // This event should not fail because AsynchronousBlockOutputPin() successfully + // duplicated this handle and we have the appropriate security permissions. + EXECUTE_ASSERT(::SetEvent(m_hNotifyCallerPinBlockedEvent)); + EXECUTE_ASSERT(::CloseHandle(m_hNotifyCallerPinBlockedEvent)); + } + + m_BlockState = NOT_BLOCKED; + m_dwBlockCallerThreadID = 0; + m_hNotifyCallerPinBlockedEvent = NULL; + + return S_OK; +} + +HRESULT CDynamicOutputPin::StartUsingOutputPin(void) +{ + // The caller should not hold m_BlockStateLock. If the caller does, + // a deadlock could occur. + ASSERT(CritCheckOut(&m_BlockStateLock)); + + CAutoLock alBlockStateLock(&m_BlockStateLock); + + #ifdef DEBUG + AssertValid(); + #endif // DEBUG + + // Are we in the middle of a block operation? + while(BLOCKED == m_BlockState) { + m_BlockStateLock.Unlock(); + + // If this ASSERT fires, a deadlock could occur. The caller should make sure + // that this thread never acquires the Block State lock more than once. + ASSERT(CritCheckOut( &m_BlockStateLock )); + + // WaitForMultipleObjects() returns WAIT_OBJECT_0 if the unblock event + // is fired. It returns WAIT_OBJECT_0 + 1 if the stop event if fired. + // See the Windows SDK documentation for more information on + // WaitForMultipleObjects(). + const DWORD UNBLOCK = WAIT_OBJECT_0; + const DWORD STOP = WAIT_OBJECT_0 + 1; + + HANDLE ahWaitEvents[] = { m_hUnblockOutputPinEvent, m_hStopEvent }; + DWORD dwNumWaitEvents = sizeof(ahWaitEvents)/sizeof(HANDLE); + + DWORD dwReturnValue = ::WaitForMultipleObjects( dwNumWaitEvents, ahWaitEvents, FALSE, INFINITE ); + + m_BlockStateLock.Lock(); + + #ifdef DEBUG + AssertValid(); + #endif // DEBUG + + switch( dwReturnValue ) { + case UNBLOCK: + break; + + case STOP: + return VFW_E_STATE_CHANGED; + + case WAIT_FAILED: + return AmGetLastErrorToHResult(); + + default: + DbgBreak( "An Unexpected case occured in CDynamicOutputPin::StartUsingOutputPin()." ); + return E_UNEXPECTED; + } + } + + m_dwNumOutstandingOutputPinUsers++; + + #ifdef DEBUG + AssertValid(); + #endif // DEBUG + + return S_OK; +} + +void CDynamicOutputPin::StopUsingOutputPin(void) +{ + CAutoLock alBlockStateLock(&m_BlockStateLock); + + #ifdef DEBUG + AssertValid(); + #endif // DEBUG + + m_dwNumOutstandingOutputPinUsers--; + + if((m_dwNumOutstandingOutputPinUsers == 0) && (NOT_BLOCKED != m_BlockState)) { + BlockOutputPin(); + } + + #ifdef DEBUG + AssertValid(); + #endif // DEBUG +} + +bool CDynamicOutputPin::StreamingThreadUsingOutputPin(void) +{ + CAutoLock alBlockStateLock(&m_BlockStateLock); + + return (m_dwNumOutstandingOutputPinUsers > 0); +} + +void CDynamicOutputPin::SetConfigInfo(IGraphConfig *pGraphConfig, HANDLE hStopEvent) +{ + // This pointer is not addrefed because filters are not allowed to + // hold references to the filter graph manager. See the documentation for + // IBaseFilter::JoinFilterGraph() in the Direct Show SDK for more information. + m_pGraphConfig = pGraphConfig; + + m_hStopEvent = hStopEvent; +} + +HRESULT CDynamicOutputPin::Active(void) +{ + // Make sure the user initialized the object by calling SetConfigInfo(). + if((NULL == m_hStopEvent) || (NULL == m_pGraphConfig)) { + DbgBreak( ERROR: CDynamicOutputPin::Active() failed because m_pGraphConfig and m_hStopEvent were not initialized. Call SetConfigInfo() to initialize them. ); + return E_FAIL; + } + + // If this ASSERT fires, the user may have passed an invalid event handle to SetConfigInfo(). + // The ASSERT can also fire if the event if destroyed and then Active() is called. An event + // handle is invalid if 1) the event does not exist or the user does not have the security + // permissions to use the event. + EXECUTE_ASSERT(ResetEvent(m_hStopEvent)); + + return CBaseOutputPin::Active(); +} + +HRESULT CDynamicOutputPin::Inactive(void) +{ + // If this ASSERT fires, the user may have passed an invalid event handle to SetConfigInfo(). + // The ASSERT can also fire if the event if destroyed and then Active() is called. An event + // handle is invalid if 1) the event does not exist or the user does not have the security + // permissions to use the event. + EXECUTE_ASSERT(SetEvent(m_hStopEvent)); + + return CBaseOutputPin::Inactive(); +} + +HRESULT CDynamicOutputPin::DeliverBeginFlush(void) +{ + // If this ASSERT fires, the user may have passed an invalid event handle to SetConfigInfo(). + // The ASSERT can also fire if the event if destroyed and then DeliverBeginFlush() is called. + // An event handle is invalid if 1) the event does not exist or the user does not have the security + // permissions to use the event. + EXECUTE_ASSERT(SetEvent(m_hStopEvent)); + + return CBaseOutputPin::DeliverBeginFlush(); +} + +HRESULT CDynamicOutputPin::DeliverEndFlush(void) +{ + // If this ASSERT fires, the user may have passed an invalid event handle to SetConfigInfo(). + // The ASSERT can also fire if the event if destroyed and then DeliverBeginFlush() is called. + // An event handle is invalid if 1) the event does not exist or the user does not have the security + // permissions to use the event. + EXECUTE_ASSERT(ResetEvent(m_hStopEvent)); + + return CBaseOutputPin::DeliverEndFlush(); +} + + +// ChangeOutputFormat() either dynamicly changes the connection's format type or it dynamicly +// reconnects the output pin. +HRESULT CDynamicOutputPin::ChangeOutputFormat + ( + const AM_MEDIA_TYPE *pmt, + REFERENCE_TIME tSegmentStart, + REFERENCE_TIME tSegmentStop, + double dSegmentRate + ) +{ + // The caller should call StartUsingOutputPin() before calling this + // method. + ASSERT(StreamingThreadUsingOutputPin()); + + // Callers should always pass a valid media type to ChangeOutputFormat() . + ASSERT(NULL != pmt); + + CMediaType cmt(*pmt); + HRESULT hr = ChangeMediaType(&cmt); + if (FAILED(hr)) { + return hr; + } + + hr = DeliverNewSegment(tSegmentStart, tSegmentStop, dSegmentRate); + if( FAILED( hr ) ) { + return hr; + } + + return S_OK; +} + +HRESULT CDynamicOutputPin::ChangeMediaType(const CMediaType *pmt) +{ + // The caller should call StartUsingOutputPin() before calling this + // method. + ASSERT(StreamingThreadUsingOutputPin()); + + // This function assumes the filter graph is running. + ASSERT(!IsStopped()); + + if(!IsConnected()) { + return VFW_E_NOT_CONNECTED; + } + + /* First check if the downstream pin will accept a dynamic + format change + */ + QzCComPtr<IPinConnection> pConnection; + + m_Connected->QueryInterface(IID_IPinConnection, (void **)&pConnection); + if(pConnection != NULL) { + + if(S_OK == pConnection->DynamicQueryAccept(pmt)) { + + HRESULT hr = ChangeMediaTypeHelper(pmt); + if(FAILED(hr)) { + return hr; + } + + return S_OK; + } + } + + /* Can't do the dynamic connection */ + return DynamicReconnect(pmt); +} + +HRESULT CDynamicOutputPin::ChangeMediaTypeHelper(const CMediaType *pmt) +{ + // The caller should call StartUsingOutputPin() before calling this + // method. + ASSERT(StreamingThreadUsingOutputPin()); + + HRESULT hr = m_Connected->ReceiveConnection(this, pmt); + if(FAILED(hr)) { + return hr; + } + + hr = SetMediaType(pmt); + if(FAILED(hr)) { + return hr; + } + + // Does this pin use the local memory transport? + if(NULL != m_pInputPin) { + // This function assumes that m_pInputPin and m_Connected are + // two different interfaces to the same object. + ASSERT(::IsEqualObject(m_Connected, m_pInputPin)); + + ALLOCATOR_PROPERTIES apInputPinRequirements; + apInputPinRequirements.cbAlign = 0; + apInputPinRequirements.cbBuffer = 0; + apInputPinRequirements.cbPrefix = 0; + apInputPinRequirements.cBuffers = 0; + + m_pInputPin->GetAllocatorRequirements(&apInputPinRequirements); + + // A zero allignment does not make any sense. + if(0 == apInputPinRequirements.cbAlign) { + apInputPinRequirements.cbAlign = 1; + } + + hr = m_pAllocator->Decommit(); + if(FAILED(hr)) { + return hr; + } + + hr = DecideBufferSize(m_pAllocator, &apInputPinRequirements); + if(FAILED(hr)) { + return hr; + } + + hr = m_pAllocator->Commit(); + if(FAILED(hr)) { + return hr; + } + + hr = m_pInputPin->NotifyAllocator(m_pAllocator, m_bPinUsesReadOnlyAllocator); + if(FAILED(hr)) { + return hr; + } + } + + return S_OK; +} + +// this method has to be called from the thread that is pushing data, +// and it's the caller's responsibility to make sure that the thread +// has no outstand samples because they cannot be delivered after a +// reconnect +// +HRESULT CDynamicOutputPin::DynamicReconnect( const CMediaType* pmt ) +{ + // The caller should call StartUsingOutputPin() before calling this + // method. + ASSERT(StreamingThreadUsingOutputPin()); + + if((m_pGraphConfig == NULL) || (NULL == m_hStopEvent)) { + return E_FAIL; + } + + HRESULT hr = m_pGraphConfig->Reconnect( + this, + NULL, + pmt, + NULL, + m_hStopEvent, + AM_GRAPH_CONFIG_RECONNECT_CACHE_REMOVED_FILTERS ); + + return hr; +} + +HRESULT CDynamicOutputPin::CompleteConnect(IPin *pReceivePin) +{ + HRESULT hr = CBaseOutputPin::CompleteConnect(pReceivePin); + if(SUCCEEDED(hr)) { + if(!IsStopped() && m_pAllocator) { + hr = m_pAllocator->Commit(); + ASSERT(hr != VFW_E_ALREADY_COMMITTED); + } + } + + return hr; +} + +#ifdef DEBUG +void CDynamicOutputPin::AssertValid(void) +{ + // Make sure the object was correctly initialized. + + // This ASSERT only fires if the object failed to initialize + // and the user ignored the constructor's return code (phr). + ASSERT(NULL != m_hUnblockOutputPinEvent); + + // If either of these ASSERTs fire, the user did not correctly call + // SetConfigInfo(). + ASSERT(NULL != m_hStopEvent); + ASSERT(NULL != m_pGraphConfig); + + // Make sure the block state is consistent. + + CAutoLock alBlockStateLock(&m_BlockStateLock); + + // BLOCK_STATE variables only have three legal values: PENDING, BLOCKED and NOT_BLOCKED. + ASSERT((NOT_BLOCKED == m_BlockState) || (PENDING == m_BlockState) || (BLOCKED == m_BlockState)); + + // m_hNotifyCallerPinBlockedEvent is only needed when a block operation cannot complete + // immediately. + ASSERT(((NULL == m_hNotifyCallerPinBlockedEvent) && (PENDING != m_BlockState)) || + ((NULL != m_hNotifyCallerPinBlockedEvent) && (PENDING == m_BlockState)) ); + + // m_dwBlockCallerThreadID should always be 0 if the pin is not blocked and + // the user is not trying to block the pin. + ASSERT((0 == m_dwBlockCallerThreadID) || (NOT_BLOCKED != m_BlockState)); + + // If this ASSERT fires, the streaming thread is using the output pin and the + // output pin is blocked. + ASSERT(((0 != m_dwNumOutstandingOutputPinUsers) && (BLOCKED != m_BlockState)) || + ((0 == m_dwNumOutstandingOutputPinUsers) && (NOT_BLOCKED != m_BlockState)) || + ((0 == m_dwNumOutstandingOutputPinUsers) && (NOT_BLOCKED == m_BlockState)) ); +} +#endif // DEBUG + +HRESULT CDynamicOutputPin::WaitEvent(HANDLE hEvent) +{ + const DWORD EVENT_SIGNALED = WAIT_OBJECT_0; + + DWORD dwReturnValue = ::WaitForSingleObject(hEvent, INFINITE); + + switch( dwReturnValue ) { + case EVENT_SIGNALED: + return S_OK; + + case WAIT_FAILED: + return AmGetLastErrorToHResult(); + + default: + DbgBreak( "An Unexpected case occured in CDynamicOutputPin::WaitEvent()." ); + return E_UNEXPECTED; + } +} + +//===================================================================== +//===================================================================== +// Implements CBaseAllocator +//===================================================================== +//===================================================================== + + +/* Constructor overrides the default settings for the free list to request + that it be alertable (ie the list can be cast to a handle which can be + passed to WaitForSingleObject). Both of the allocator lists also ask for + object locking, the all list matches the object default settings but I + have included them here just so it is obvious what kind of list it is */ + +CBaseAllocator::CBaseAllocator(__in_opt LPCTSTR pName, + __inout_opt LPUNKNOWN pUnk, + __inout HRESULT *phr, + BOOL bEvent, + BOOL fEnableReleaseCallback + ) : + CUnknown(pName, pUnk), + m_lAllocated(0), + m_bChanged(FALSE), + m_bCommitted(FALSE), + m_bDecommitInProgress(FALSE), + m_lSize(0), + m_lCount(0), + m_lAlignment(0), + m_lPrefix(0), + m_hSem(NULL), + m_lWaiting(0), + m_fEnableReleaseCallback(fEnableReleaseCallback), + m_pNotify(NULL) +{ +#ifdef DXMPERF + PERFLOG_CTOR( pName ? pName : L"CBaseAllocator", (IMemAllocator *) this ); +#endif // DXMPERF + + if (bEvent) { + m_hSem = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL); + if (m_hSem == NULL) { + *phr = E_OUTOFMEMORY; + return; + } + } +} + +#ifdef UNICODE +CBaseAllocator::CBaseAllocator(__in_opt LPCSTR pName, + __inout_opt LPUNKNOWN pUnk, + __inout HRESULT *phr, + BOOL bEvent, + BOOL fEnableReleaseCallback) : + CUnknown(pName, pUnk), + m_lAllocated(0), + m_bChanged(FALSE), + m_bCommitted(FALSE), + m_bDecommitInProgress(FALSE), + m_lSize(0), + m_lCount(0), + m_lAlignment(0), + m_lPrefix(0), + m_hSem(NULL), + m_lWaiting(0), + m_fEnableReleaseCallback(fEnableReleaseCallback), + m_pNotify(NULL) +{ +#ifdef DXMPERF + PERFLOG_CTOR( L"CBaseAllocator", (IMemAllocator *) this ); +#endif // DXMPERF + + if (bEvent) { + m_hSem = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL); + if (m_hSem == NULL) { + *phr = E_OUTOFMEMORY; + return; + } + } +} +#endif + +/* Destructor */ + +CBaseAllocator::~CBaseAllocator() +{ + // we can't call Decommit here since that would mean a call to a + // pure virtual in destructor. + // We must assume that the derived class has gone into decommit state in + // its destructor. +#ifdef DXMPERF + PERFLOG_DTOR( L"CBaseAllocator", (IMemAllocator *) this ); +#endif // DXMPERF + + ASSERT(!m_bCommitted); + if (m_hSem != NULL) { + EXECUTE_ASSERT(CloseHandle(m_hSem)); + } + if (m_pNotify) { + m_pNotify->Release(); + } +} + + +/* Override this to publicise our interfaces */ + +STDMETHODIMP +CBaseAllocator::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + /* Do we know about this interface */ + + if (riid == IID_IMemAllocator || + riid == IID_IMemAllocatorCallbackTemp && m_fEnableReleaseCallback) { + return GetInterface((IMemAllocatorCallbackTemp *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } +} + + +/* This sets the size and count of the required samples. The memory isn't + actually allocated until Commit() is called, if memory has already been + allocated then assuming no samples are outstanding the user may call us + to change the buffering, the memory will be released in Commit() */ + +STDMETHODIMP +CBaseAllocator::SetProperties( + __in ALLOCATOR_PROPERTIES* pRequest, + __out ALLOCATOR_PROPERTIES* pActual) +{ + CheckPointer(pRequest, E_POINTER); + CheckPointer(pActual, E_POINTER); + ValidateReadWritePtr(pActual, sizeof(ALLOCATOR_PROPERTIES)); + CAutoLock cObjectLock(this); + + ZeroMemory(pActual, sizeof(ALLOCATOR_PROPERTIES)); + + ASSERT(pRequest->cbBuffer > 0); + + /* Check the alignment requested */ + if (pRequest->cbAlign != 1) { + DbgLog((LOG_ERROR, 2, TEXT("Alignment requested was 0x%x, not 1"), + pRequest->cbAlign)); + return VFW_E_BADALIGN; + } + + /* Can't do this if already committed, there is an argument that says we + should not reject the SetProperties call if there are buffers still + active. However this is called by the source filter, which is the same + person who is holding the samples. Therefore it is not unreasonable + for them to free all their samples before changing the requirements */ + + if (m_bCommitted) { + return VFW_E_ALREADY_COMMITTED; + } + + /* Must be no outstanding buffers */ + + if (m_lAllocated != m_lFree.GetCount()) { + return VFW_E_BUFFERS_OUTSTANDING; + } + + /* There isn't any real need to check the parameters as they + will just be rejected when the user finally calls Commit */ + + pActual->cbBuffer = m_lSize = pRequest->cbBuffer; + pActual->cBuffers = m_lCount = pRequest->cBuffers; + pActual->cbAlign = m_lAlignment = pRequest->cbAlign; + pActual->cbPrefix = m_lPrefix = pRequest->cbPrefix; + + m_bChanged = TRUE; + return NOERROR; +} + +STDMETHODIMP +CBaseAllocator::GetProperties( + __out ALLOCATOR_PROPERTIES * pActual) +{ + CheckPointer(pActual,E_POINTER); + ValidateReadWritePtr(pActual,sizeof(ALLOCATOR_PROPERTIES)); + + CAutoLock cObjectLock(this); + pActual->cbBuffer = m_lSize; + pActual->cBuffers = m_lCount; + pActual->cbAlign = m_lAlignment; + pActual->cbPrefix = m_lPrefix; + return NOERROR; +} + +// get container for a sample. Blocking, synchronous call to get the +// next free buffer (as represented by an IMediaSample interface). +// on return, the time etc properties will be invalid, but the buffer +// pointer and size will be correct. + +HRESULT CBaseAllocator::GetBuffer(__deref_out IMediaSample **ppBuffer, + __in_opt REFERENCE_TIME *pStartTime, + __in_opt REFERENCE_TIME *pEndTime, + DWORD dwFlags + ) +{ + UNREFERENCED_PARAMETER(pStartTime); + UNREFERENCED_PARAMETER(pEndTime); + UNREFERENCED_PARAMETER(dwFlags); + CMediaSample *pSample; + + *ppBuffer = NULL; + for (;;) + { + { // scope for lock + CAutoLock cObjectLock(this); + + /* Check we are committed */ + if (!m_bCommitted) { + return VFW_E_NOT_COMMITTED; + } + pSample = (CMediaSample *) m_lFree.RemoveHead(); + if (pSample == NULL) { + SetWaiting(); + } + } + + /* If we didn't get a sample then wait for the list to signal */ + + if (pSample) { + break; + } + if (dwFlags & AM_GBF_NOWAIT) { + return VFW_E_TIMEOUT; + } + ASSERT(m_hSem != NULL); + WaitForSingleObject(m_hSem, INFINITE); + } + + /* Addref the buffer up to one. On release + back to zero instead of being deleted, it will requeue itself by + calling the ReleaseBuffer member function. NOTE the owner of a + media sample must always be derived from CBaseAllocator */ + + + ASSERT(pSample->m_cRef == 0); + pSample->m_cRef = 1; + *ppBuffer = pSample; + +#ifdef DXMPERF + PERFLOG_GETBUFFER( (IMemAllocator *) this, pSample ); +#endif // DXMPERF + + return NOERROR; +} + + +/* Final release of a CMediaSample will call this */ + +STDMETHODIMP +CBaseAllocator::ReleaseBuffer(IMediaSample * pSample) +{ + CheckPointer(pSample,E_POINTER); + ValidateReadPtr(pSample,sizeof(IMediaSample)); + +#ifdef DXMPERF + PERFLOG_RELBUFFER( (IMemAllocator *) this, pSample ); +#endif // DXMPERF + + + BOOL bRelease = FALSE; + { + CAutoLock cal(this); + + /* Put back on the free list */ + + m_lFree.Add((CMediaSample *)pSample); + if (m_lWaiting != 0) { + NotifySample(); + } + + // if there is a pending Decommit, then we need to complete it by + // calling Free() when the last buffer is placed on the free list + + LONG l1 = m_lFree.GetCount(); + if (m_bDecommitInProgress && (l1 == m_lAllocated)) { + Free(); + m_bDecommitInProgress = FALSE; + bRelease = TRUE; + } + } + + if (m_pNotify) { + + ASSERT(m_fEnableReleaseCallback); + + // + // Note that this is not synchronized with setting up a notification + // method. + // + m_pNotify->NotifyRelease(); + } + + /* For each buffer there is one AddRef, made in GetBuffer and released + here. This may cause the allocator and all samples to be deleted */ + + if (bRelease) { + Release(); + } + return NOERROR; +} + +STDMETHODIMP +CBaseAllocator::SetNotify( + IMemAllocatorNotifyCallbackTemp* pNotify + ) +{ + ASSERT(m_fEnableReleaseCallback); + CAutoLock lck(this); + if (pNotify) { + pNotify->AddRef(); + } + if (m_pNotify) { + m_pNotify->Release(); + } + m_pNotify = pNotify; + return S_OK; +} + +STDMETHODIMP +CBaseAllocator::GetFreeCount( + __out LONG* plBuffersFree + ) +{ + ASSERT(m_fEnableReleaseCallback); + CAutoLock cObjectLock(this); + *plBuffersFree = m_lCount - m_lAllocated + m_lFree.GetCount(); + return NOERROR; +} + +void +CBaseAllocator::NotifySample() +{ + if (m_lWaiting != 0) { + ASSERT(m_hSem != NULL); + ReleaseSemaphore(m_hSem, m_lWaiting, 0); + m_lWaiting = 0; + } +} + +STDMETHODIMP +CBaseAllocator::Commit() +{ + /* Check we are not decommitted */ + CAutoLock cObjectLock(this); + + // cannot need to alloc or re-alloc if we are committed + if (m_bCommitted) { + return NOERROR; + } + + /* Allow GetBuffer calls */ + + m_bCommitted = TRUE; + + // is there a pending decommit ? if so, just cancel it + if (m_bDecommitInProgress) { + m_bDecommitInProgress = FALSE; + + // don't call Alloc at this point. He cannot allow SetProperties + // between Decommit and the last free, so the buffer size cannot have + // changed. And because some of the buffers are not free yet, he + // cannot re-alloc anyway. + return NOERROR; + } + + DbgLog((LOG_MEMORY, 1, TEXT("Allocating: %ldx%ld"), m_lCount, m_lSize)); + + // actually need to allocate the samples + HRESULT hr = Alloc(); + if (FAILED(hr)) { + m_bCommitted = FALSE; + return hr; + } + AddRef(); + return NOERROR; +} + + +STDMETHODIMP +CBaseAllocator::Decommit() +{ + BOOL bRelease = FALSE; + { + /* Check we are not already decommitted */ + CAutoLock cObjectLock(this); + if (m_bCommitted == FALSE) { + if (m_bDecommitInProgress == FALSE) { + return NOERROR; + } + } + + /* No more GetBuffer calls will succeed */ + m_bCommitted = FALSE; + + // are any buffers outstanding? + if (m_lFree.GetCount() < m_lAllocated) { + // please complete the decommit when last buffer is freed + m_bDecommitInProgress = TRUE; + } else { + m_bDecommitInProgress = FALSE; + + // need to complete the decommit here as there are no + // outstanding buffers + + Free(); + bRelease = TRUE; + } + + // Tell anyone waiting that they can go now so we can + // reject their call +#pragma warning(push) +#ifndef _PREFAST_ +#pragma warning(disable:4068) +#endif +#pragma prefast(suppress:__WARNING_DEREF_NULL_PTR, "Suppress warning related to Free() invalidating 'this' which is no applicable to CBaseAllocator::Free()") + NotifySample(); + +#pragma warning(pop) + } + + if (bRelease) { + Release(); + } + return NOERROR; +} + + +/* Base definition of allocation which checks we are ok to go ahead and do + the full allocation. We return S_FALSE if the requirements are the same */ + +HRESULT +CBaseAllocator::Alloc(void) +{ + /* Error if he hasn't set the size yet */ + if (m_lCount <= 0 || m_lSize <= 0 || m_lAlignment <= 0) { + return VFW_E_SIZENOTSET; + } + + /* should never get here while buffers outstanding */ + ASSERT(m_lFree.GetCount() == m_lAllocated); + + /* If the requirements haven't changed then don't reallocate */ + if (m_bChanged == FALSE) { + return S_FALSE; + } + + return NOERROR; +} + +/* Implement CBaseAllocator::CSampleList::Remove(pSample) + Removes pSample from the list +*/ +void +CBaseAllocator::CSampleList::Remove(__inout CMediaSample * pSample) +{ + CMediaSample **pSearch; + for (pSearch = &m_List; + *pSearch != NULL; + pSearch = &(CBaseAllocator::NextSample(*pSearch))) { + if (*pSearch == pSample) { + *pSearch = CBaseAllocator::NextSample(pSample); + CBaseAllocator::NextSample(pSample) = NULL; + m_nOnList--; + return; + } + } + DbgBreak("Couldn't find sample in list"); +} + +//===================================================================== +//===================================================================== +// Implements CMemAllocator +//===================================================================== +//===================================================================== + + +/* This goes in the factory template table to create new instances */ +CUnknown *CMemAllocator::CreateInstance(__inout_opt LPUNKNOWN pUnk, __inout HRESULT *phr) +{ + CUnknown *pUnkRet = new CMemAllocator(NAME("CMemAllocator"), pUnk, phr); + return pUnkRet; +} + +CMemAllocator::CMemAllocator( + __in_opt LPCTSTR pName, + __inout_opt LPUNKNOWN pUnk, + __inout HRESULT *phr) + : CBaseAllocator(pName, pUnk, phr, TRUE, TRUE), + m_pBuffer(NULL) +{ +} + +#ifdef UNICODE +CMemAllocator::CMemAllocator( + __in_opt LPCSTR pName, + __inout_opt LPUNKNOWN pUnk, + __inout HRESULT *phr) + : CBaseAllocator(pName, pUnk, phr, TRUE, TRUE), + m_pBuffer(NULL) +{ +} +#endif + +/* This sets the size and count of the required samples. The memory isn't + actually allocated until Commit() is called, if memory has already been + allocated then assuming no samples are outstanding the user may call us + to change the buffering, the memory will be released in Commit() */ +STDMETHODIMP +CMemAllocator::SetProperties( + __in ALLOCATOR_PROPERTIES* pRequest, + __out ALLOCATOR_PROPERTIES* pActual) +{ + CheckPointer(pActual,E_POINTER); + ValidateReadWritePtr(pActual,sizeof(ALLOCATOR_PROPERTIES)); + CAutoLock cObjectLock(this); + + ZeroMemory(pActual, sizeof(ALLOCATOR_PROPERTIES)); + + ASSERT(pRequest->cbBuffer > 0); + + SYSTEM_INFO SysInfo; + GetSystemInfo(&SysInfo); + + /* Check the alignment request is a power of 2 */ + if ((-pRequest->cbAlign & pRequest->cbAlign) != pRequest->cbAlign) { + DbgLog((LOG_ERROR, 1, TEXT("Alignment requested 0x%x not a power of 2!"), + pRequest->cbAlign)); + } + /* Check the alignment requested */ + if (pRequest->cbAlign == 0 || + (SysInfo.dwAllocationGranularity & (pRequest->cbAlign - 1)) != 0) { + DbgLog((LOG_ERROR, 1, TEXT("Invalid alignment 0x%x requested - granularity = 0x%x"), + pRequest->cbAlign, SysInfo.dwAllocationGranularity)); + return VFW_E_BADALIGN; + } + + /* Can't do this if already committed, there is an argument that says we + should not reject the SetProperties call if there are buffers still + active. However this is called by the source filter, which is the same + person who is holding the samples. Therefore it is not unreasonable + for them to free all their samples before changing the requirements */ + + if (m_bCommitted == TRUE) { + return VFW_E_ALREADY_COMMITTED; + } + + /* Must be no outstanding buffers */ + + if (m_lFree.GetCount() < m_lAllocated) { + return VFW_E_BUFFERS_OUTSTANDING; + } + + /* There isn't any real need to check the parameters as they + will just be rejected when the user finally calls Commit */ + + // round length up to alignment - remember that prefix is included in + // the alignment + LONG lSize = pRequest->cbBuffer + pRequest->cbPrefix; + LONG lRemainder = lSize % pRequest->cbAlign; + if (lRemainder != 0) { + lSize = lSize - lRemainder + pRequest->cbAlign; + } + pActual->cbBuffer = m_lSize = (lSize - pRequest->cbPrefix); + + pActual->cBuffers = m_lCount = pRequest->cBuffers; + pActual->cbAlign = m_lAlignment = pRequest->cbAlign; + pActual->cbPrefix = m_lPrefix = pRequest->cbPrefix; + + m_bChanged = TRUE; + return NOERROR; +} + +// override this to allocate our resources when Commit is called. +// +// note that our resources may be already allocated when this is called, +// since we don't free them on Decommit. We will only be called when in +// decommit state with all buffers free. +// +// object locked by caller +HRESULT +CMemAllocator::Alloc(void) +{ + CAutoLock lck(this); + + /* Check he has called SetProperties */ + HRESULT hr = CBaseAllocator::Alloc(); + if (FAILED(hr)) { + return hr; + } + + /* If the requirements haven't changed then don't reallocate */ + if (hr == S_FALSE) { + ASSERT(m_pBuffer); + return NOERROR; + } + ASSERT(hr == S_OK); // we use this fact in the loop below + + /* Free the old resources */ + if (m_pBuffer) { + ReallyFree(); + } + + /* Make sure we've got reasonable values */ + if ( m_lSize < 0 || m_lPrefix < 0 || m_lCount < 0 ) { + return E_OUTOFMEMORY; + } + + /* Compute the aligned size */ + LONG lAlignedSize = m_lSize + m_lPrefix; + + /* Check overflow */ + if (lAlignedSize < m_lSize) { + return E_OUTOFMEMORY; + } + + if (m_lAlignment > 1) { + LONG lRemainder = lAlignedSize % m_lAlignment; + if (lRemainder != 0) { + LONG lNewSize = lAlignedSize + m_lAlignment - lRemainder; + if (lNewSize < lAlignedSize) { + return E_OUTOFMEMORY; + } + lAlignedSize = lNewSize; + } + } + + /* Create the contiguous memory block for the samples + making sure it's properly aligned (64K should be enough!) + */ + ASSERT(lAlignedSize % m_lAlignment == 0); + + LONGLONG lToAllocate = m_lCount * (LONGLONG)lAlignedSize; + + /* Check overflow */ + if (lToAllocate > MAXLONG) { + return E_OUTOFMEMORY; + } + + m_pBuffer = (PBYTE)VirtualAlloc(NULL, + (LONG)lToAllocate, + MEM_COMMIT, + PAGE_READWRITE); + + if (m_pBuffer == NULL) { + return E_OUTOFMEMORY; + } + + LPBYTE pNext = m_pBuffer; + CMediaSample *pSample; + + ASSERT(m_lAllocated == 0); + + // Create the new samples - we have allocated m_lSize bytes for each sample + // plus m_lPrefix bytes per sample as a prefix. We set the pointer to + // the memory after the prefix - so that GetPointer() will return a pointer + // to m_lSize bytes. + for (; m_lAllocated < m_lCount; m_lAllocated++, pNext += lAlignedSize) { + + + pSample = new CMediaSample( + NAME("Default memory media sample"), + this, + &hr, + pNext + m_lPrefix, // GetPointer() value + m_lSize); // not including prefix + + ASSERT(SUCCEEDED(hr)); + if (pSample == NULL) { + return E_OUTOFMEMORY; + } + + // This CANNOT fail + m_lFree.Add(pSample); + } + + m_bChanged = FALSE; + return NOERROR; +} + + +// override this to free up any resources we have allocated. +// called from the base class on Decommit when all buffers have been +// returned to the free list. +// +// caller has already locked the object. + +// in our case, we keep the memory until we are deleted, so +// we do nothing here. The memory is deleted in the destructor by +// calling ReallyFree() +void +CMemAllocator::Free(void) +{ + return; +} + + +// called from the destructor (and from Alloc if changing size/count) to +// actually free up the memory +void +CMemAllocator::ReallyFree(void) +{ + /* Should never be deleting this unless all buffers are freed */ + + ASSERT(m_lAllocated == m_lFree.GetCount()); + + /* Free up all the CMediaSamples */ + + CMediaSample *pSample; + for (;;) { + pSample = m_lFree.RemoveHead(); + if (pSample != NULL) { + delete pSample; + } else { + break; + } + } + + m_lAllocated = 0; + + // free the block of buffer memory + if (m_pBuffer) { + EXECUTE_ASSERT(VirtualFree(m_pBuffer, 0, MEM_RELEASE)); + m_pBuffer = NULL; + } +} + + +/* Destructor frees our memory resources */ + +CMemAllocator::~CMemAllocator() +{ + Decommit(); + ReallyFree(); +} + +// ------------------------------------------------------------------------ +// filter registration through IFilterMapper. used if IFilterMapper is +// not found (Quartz 1.0 install) + +STDAPI +AMovieSetupRegisterFilter( const AMOVIESETUP_FILTER * const psetupdata + , IFilterMapper * pIFM + , BOOL bRegister ) +{ + DbgLog((LOG_TRACE, 3, TEXT("= AMovieSetupRegisterFilter"))); + + // check we've got data + // + if( NULL == psetupdata ) return S_FALSE; + + + // unregister filter + // (as pins are subkeys of filter's CLSID key + // they do not need to be removed separately). + // + DbgLog((LOG_TRACE, 3, TEXT("= = unregister filter"))); + HRESULT hr = pIFM->UnregisterFilter( *(psetupdata->clsID) ); + + + if( bRegister ) + { + // register filter + // + DbgLog((LOG_TRACE, 3, TEXT("= = register filter"))); + hr = pIFM->RegisterFilter( *(psetupdata->clsID) + , psetupdata->strName + , psetupdata->dwMerit ); + if( SUCCEEDED(hr) ) + { + // all its pins + // + DbgLog((LOG_TRACE, 3, TEXT("= = register filter pins"))); + for( UINT m1=0; m1 < psetupdata->nPins; m1++ ) + { + hr = pIFM->RegisterPin( *(psetupdata->clsID) + , psetupdata->lpPin[m1].strName + , psetupdata->lpPin[m1].bRendered + , psetupdata->lpPin[m1].bOutput + , psetupdata->lpPin[m1].bZero + , psetupdata->lpPin[m1].bMany + , *(psetupdata->lpPin[m1].clsConnectsToFilter) + , psetupdata->lpPin[m1].strConnectsToPin ); + + if( SUCCEEDED(hr) ) + { + // and each pin's media types + // + DbgLog((LOG_TRACE, 3, TEXT("= = register filter pin types"))); + for( UINT m2=0; m2 < psetupdata->lpPin[m1].nMediaTypes; m2++ ) + { + hr = pIFM->RegisterPinType( *(psetupdata->clsID) + , psetupdata->lpPin[m1].strName + , *(psetupdata->lpPin[m1].lpMediaType[m2].clsMajorType) + , *(psetupdata->lpPin[m1].lpMediaType[m2].clsMinorType) ); + if( FAILED(hr) ) break; + } + if( FAILED(hr) ) break; + } + if( FAILED(hr) ) break; + } + } + } + + // handle one acceptable "error" - that + // of filter not being registered! + // (couldn't find a suitable #define'd + // name for the error!) + // + if( 0x80070002 == hr) + return NOERROR; + else + return hr; +} + +// Remove warnings about unreferenced inline functions +#pragma warning(disable:4514) + diff --git a/Src/Plugins/Input/in_dshow/base/amfilter.h b/Src/Plugins/Input/in_dshow/base/amfilter.h new file mode 100644 index 00000000..8646bc03 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/amfilter.h @@ -0,0 +1,1587 @@ +//------------------------------------------------------------------------------ +// File: AMFilter.h +// +// Desc: DirectShow base classes - efines class hierarchy for streams +// architecture. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __FILTER__ +#define __FILTER__ + +/* The following classes are declared in this header: */ + +class CBaseMediaFilter; // IMediaFilter support +class CBaseFilter; // IBaseFilter,IMediaFilter support +class CBasePin; // Abstract base class for IPin interface +class CEnumPins; // Enumerate input and output pins +class CEnumMediaTypes; // Enumerate the pin's preferred formats +class CBaseOutputPin; // Adds data provider member functions +class CBaseInputPin; // Implements IMemInputPin interface +class CMediaSample; // Basic transport unit for IMemInputPin +class CBaseAllocator; // General list guff for most allocators +class CMemAllocator; // Implements memory buffer allocation + + +//===================================================================== +//===================================================================== +// +// QueryFilterInfo and QueryPinInfo AddRef the interface pointers +// they return. You can use the macro below to release the interface. +// +//===================================================================== +//===================================================================== + +#define QueryFilterInfoReleaseGraph(fi) if ((fi).pGraph) (fi).pGraph->Release(); + +#define QueryPinInfoReleaseFilter(pi) if ((pi).pFilter) (pi).pFilter->Release(); + +//===================================================================== +//===================================================================== +// Defines CBaseMediaFilter +// +// Abstract base class implementing IMediaFilter. +// +// Typically you will derive your filter from CBaseFilter rather than +// this, unless you are implementing an object such as a plug-in +// distributor that needs to support IMediaFilter but not IBaseFilter. +// +// Note that IMediaFilter is derived from IPersist to allow query of +// class id. +//===================================================================== +//===================================================================== + +class AM_NOVTABLE CBaseMediaFilter : public CUnknown, + public IMediaFilter +{ + +protected: + + FILTER_STATE m_State; // current state: running, paused + IReferenceClock *m_pClock; // this filter's reference clock + // note: all filters in a filter graph use the same clock + + // offset from stream time to reference time + CRefTime m_tStart; + + CLSID m_clsid; // This filters clsid + // used for serialization + CCritSec *m_pLock; // Object we use for locking + +public: + + CBaseMediaFilter( + __in_opt LPCTSTR pName, + __inout_opt LPUNKNOWN pUnk, + __in CCritSec *pLock, + REFCLSID clsid); + + virtual ~CBaseMediaFilter(); + + DECLARE_IUNKNOWN + + // override this to say what interfaces we support where + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void ** ppv); + + // + // --- IPersist method --- + // + + STDMETHODIMP GetClassID(__out CLSID *pClsID); + + // --- IMediaFilter methods --- + + STDMETHODIMP GetState(DWORD dwMSecs, __out FILTER_STATE *State); + + STDMETHODIMP SetSyncSource(__inout_opt IReferenceClock *pClock); + + STDMETHODIMP GetSyncSource(__deref_out_opt IReferenceClock **pClock); + + // default implementation of Stop and Pause just record the + // state. Override to activate or de-activate your filter. + // Note that Run when called from Stopped state will call Pause + // to ensure activation, so if you are a source or transform + // you will probably not need to override Run. + STDMETHODIMP Stop(); + STDMETHODIMP Pause(); + + + // the start parameter is the difference to be added to the + // sample's stream time to get the reference time for + // its presentation + STDMETHODIMP Run(REFERENCE_TIME tStart); + + // --- helper methods --- + + // return the current stream time - ie find out what + // stream time should be appearing now + virtual HRESULT StreamTime(CRefTime& rtStream); + + // Is the filter currently active? (running or paused) + BOOL IsActive() { + CAutoLock cObjectLock(m_pLock); + return ((m_State == State_Paused) || (m_State == State_Running)); + }; +}; + +//===================================================================== +//===================================================================== +// Defines CBaseFilter +// +// An abstract class providing basic IBaseFilter support for pin +// enumeration and filter information reading. +// +// We cannot derive from CBaseMediaFilter since methods in IMediaFilter +// are also in IBaseFilter and would be ambiguous. Since much of the code +// assumes that they derive from a class that has m_State and other state +// directly available, we duplicate code from CBaseMediaFilter rather than +// having a member variable. +// +// Derive your filter from this, or from a derived object such as +// CTransformFilter. +//===================================================================== +//===================================================================== + + +class AM_NOVTABLE CBaseFilter : public CUnknown, // Handles an IUnknown + public IBaseFilter, // The Filter Interface + public IAMovieSetup // For un/registration +{ + +friend class CBasePin; + +protected: + FILTER_STATE m_State; // current state: running, paused + IReferenceClock *m_pClock; // this graph's ref clock + CRefTime m_tStart; // offset from stream time to reference time + CLSID m_clsid; // This filters clsid + // used for serialization + CCritSec *m_pLock; // Object we use for locking + + WCHAR *m_pName; // Full filter name + IFilterGraph *m_pGraph; // Graph we belong to + IMediaEventSink *m_pSink; // Called with notify events + LONG m_PinVersion; // Current pin version + +public: + + CBaseFilter( + __in_opt LPCTSTR pName, // Object description + __inout_opt LPUNKNOWN pUnk, // IUnknown of delegating object + __in CCritSec *pLock, // Object who maintains lock + REFCLSID clsid); // The clsid to be used to serialize this filter + + CBaseFilter( + __in_opt LPCTSTR pName, // Object description + __in_opt LPUNKNOWN pUnk, // IUnknown of delegating object + __in CCritSec *pLock, // Object who maintains lock + REFCLSID clsid, // The clsid to be used to serialize this filter + __inout HRESULT *phr); // General OLE return code +#ifdef UNICODE + CBaseFilter( + __in_opt LPCSTR pName, // Object description + __in_opt LPUNKNOWN pUnk, // IUnknown of delegating object + __in CCritSec *pLock, // Object who maintains lock + REFCLSID clsid); // The clsid to be used to serialize this filter + + CBaseFilter( + __in_opt LPCSTR pName, // Object description + __in_opt LPUNKNOWN pUnk, // IUnknown of delegating object + __in CCritSec *pLock, // Object who maintains lock + REFCLSID clsid, // The clsid to be used to serialize this filter + __inout HRESULT *phr); // General OLE return code +#endif + ~CBaseFilter(); + + DECLARE_IUNKNOWN + + // override this to say what interfaces we support where + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void ** ppv); +#ifdef DEBUG + STDMETHODIMP_(ULONG) NonDelegatingRelease(); +#endif + + // + // --- IPersist method --- + // + + STDMETHODIMP GetClassID(__out CLSID *pClsID); + + // --- IMediaFilter methods --- + + STDMETHODIMP GetState(DWORD dwMSecs, __out FILTER_STATE *State); + + STDMETHODIMP SetSyncSource(__in_opt IReferenceClock *pClock); + + STDMETHODIMP GetSyncSource(__deref_out_opt IReferenceClock **pClock); + + + // override Stop and Pause so we can activate the pins. + // Note that Run will call Pause first if activation needed. + // Override these if you want to activate your filter rather than + // your pins. + STDMETHODIMP Stop(); + STDMETHODIMP Pause(); + + // the start parameter is the difference to be added to the + // sample's stream time to get the reference time for + // its presentation + STDMETHODIMP Run(REFERENCE_TIME tStart); + + // --- helper methods --- + + // return the current stream time - ie find out what + // stream time should be appearing now + virtual HRESULT StreamTime(CRefTime& rtStream); + + // Is the filter currently active? + BOOL IsActive() { + CAutoLock cObjectLock(m_pLock); + return ((m_State == State_Paused) || (m_State == State_Running)); + }; + + // Is this filter stopped (without locking) + BOOL IsStopped() { + return (m_State == State_Stopped); + }; + + // + // --- IBaseFilter methods --- + // + + // pin enumerator + STDMETHODIMP EnumPins( + __deref_out IEnumPins ** ppEnum); + + + // default behaviour of FindPin assumes pin ids are their names + STDMETHODIMP FindPin( + LPCWSTR Id, + __deref_out IPin ** ppPin + ); + + STDMETHODIMP QueryFilterInfo( + __out FILTER_INFO * pInfo); + + STDMETHODIMP JoinFilterGraph( + __inout_opt IFilterGraph * pGraph, + __in_opt LPCWSTR pName); + + // return a Vendor information string. Optional - may return E_NOTIMPL. + // memory returned should be freed using CoTaskMemFree + // default implementation returns E_NOTIMPL + STDMETHODIMP QueryVendorInfo( + __deref_out LPWSTR* pVendorInfo + ); + + // --- helper methods --- + + // send an event notification to the filter graph if we know about it. + // returns S_OK if delivered, S_FALSE if the filter graph does not sink + // events, or an error otherwise. + HRESULT NotifyEvent( + long EventCode, + LONG_PTR EventParam1, + LONG_PTR EventParam2); + + // return the filter graph we belong to + __out_opt IFilterGraph *GetFilterGraph() { + return m_pGraph; + } + + // Request reconnect + // pPin is the pin to reconnect + // pmt is the type to reconnect with - can be NULL + // Calls ReconnectEx on the filter graph + HRESULT ReconnectPin(IPin *pPin, __in_opt AM_MEDIA_TYPE const *pmt); + + // find out the current pin version (used by enumerators) + virtual LONG GetPinVersion(); + void IncrementPinVersion(); + + // you need to supply these to access the pins from the enumerator + // and for default Stop and Pause/Run activation. + virtual int GetPinCount() PURE; + virtual CBasePin *GetPin(int n) PURE; + + // --- IAMovieSetup methods --- + + STDMETHODIMP Register(); // ask filter to register itself + STDMETHODIMP Unregister(); // and unregister itself + + // --- setup helper methods --- + // (override to return filters setup data) + + virtual __out_opt LPAMOVIESETUP_FILTER GetSetupData(){ return NULL; } + +}; + + +//===================================================================== +//===================================================================== +// Defines CBasePin +// +// Abstract class that supports the basics of IPin +//===================================================================== +//===================================================================== + +class AM_NOVTABLE CBasePin : public CUnknown, public IPin, public IQualityControl +{ + +protected: + + WCHAR * m_pName; // This pin's name + IPin *m_Connected; // Pin we have connected to + PIN_DIRECTION m_dir; // Direction of this pin + CCritSec *m_pLock; // Object we use for locking + bool m_bRunTimeError; // Run time error generated + bool m_bCanReconnectWhenActive; // OK to reconnect when active + bool m_bTryMyTypesFirst; // When connecting enumerate + // this pin's types first + CBaseFilter *m_pFilter; // Filter we were created by + IQualityControl *m_pQSink; // Target for Quality messages + LONG m_TypeVersion; // Holds current type version + CMediaType m_mt; // Media type of connection + + CRefTime m_tStart; // time from NewSegment call + CRefTime m_tStop; // time from NewSegment + double m_dRate; // rate from NewSegment + +#ifdef DEBUG + LONG m_cRef; // Ref count tracing +#endif + + // displays pin connection information + +#ifdef DEBUG + void DisplayPinInfo(IPin *pReceivePin); + void DisplayTypeInfo(IPin *pPin, const CMediaType *pmt); +#else + void DisplayPinInfo(IPin *pReceivePin) {}; + void DisplayTypeInfo(IPin *pPin, const CMediaType *pmt) {}; +#endif + + // used to agree a media type for a pin connection + + // given a specific media type, attempt a connection (includes + // checking that the type is acceptable to this pin) + HRESULT + AttemptConnection( + IPin* pReceivePin, // connect to this pin + const CMediaType* pmt // using this type + ); + + // try all the media types in this enumerator - for each that + // we accept, try to connect using ReceiveConnection. + HRESULT TryMediaTypes( + IPin *pReceivePin, // connect to this pin + __in_opt const CMediaType *pmt, // proposed type from Connect + IEnumMediaTypes *pEnum); // try this enumerator + + // establish a connection with a suitable mediatype. Needs to + // propose a media type if the pmt pointer is null or partially + // specified - use TryMediaTypes on both our and then the other pin's + // enumerator until we find one that works. + HRESULT AgreeMediaType( + IPin *pReceivePin, // connect to this pin + const CMediaType *pmt); // proposed type from Connect + +public: + + CBasePin( + __in_opt LPCTSTR pObjectName, // Object description + __in CBaseFilter *pFilter, // Owning filter who knows about pins + __in CCritSec *pLock, // Object who implements the lock + __inout HRESULT *phr, // General OLE return code + __in_opt LPCWSTR pName, // Pin name for us + PIN_DIRECTION dir); // Either PINDIR_INPUT or PINDIR_OUTPUT +#ifdef UNICODE + CBasePin( + __in_opt LPCSTR pObjectName, // Object description + __in CBaseFilter *pFilter, // Owning filter who knows about pins + __in CCritSec *pLock, // Object who implements the lock + __inout HRESULT *phr, // General OLE return code + __in_opt LPCWSTR pName, // Pin name for us + PIN_DIRECTION dir); // Either PINDIR_INPUT or PINDIR_OUTPUT +#endif + virtual ~CBasePin(); + + DECLARE_IUNKNOWN + + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void ** ppv); + STDMETHODIMP_(ULONG) NonDelegatingRelease(); + STDMETHODIMP_(ULONG) NonDelegatingAddRef(); + + // --- IPin methods --- + + // take lead role in establishing a connection. Media type pointer + // may be null, or may point to partially-specified mediatype + // (subtype or format type may be GUID_NULL). + STDMETHODIMP Connect( + IPin * pReceivePin, + __in_opt const AM_MEDIA_TYPE *pmt // optional media type + ); + + // (passive) accept a connection from another pin + STDMETHODIMP ReceiveConnection( + IPin * pConnector, // this is the initiating connecting pin + const AM_MEDIA_TYPE *pmt // this is the media type we will exchange + ); + + STDMETHODIMP Disconnect(); + + STDMETHODIMP ConnectedTo(__deref_out IPin **pPin); + + STDMETHODIMP ConnectionMediaType(__out AM_MEDIA_TYPE *pmt); + + STDMETHODIMP QueryPinInfo( + __out PIN_INFO * pInfo + ); + + STDMETHODIMP QueryDirection( + __out PIN_DIRECTION * pPinDir + ); + + STDMETHODIMP QueryId( + __deref_out LPWSTR * Id + ); + + // does the pin support this media type + STDMETHODIMP QueryAccept( + const AM_MEDIA_TYPE *pmt + ); + + // return an enumerator for this pins preferred media types + STDMETHODIMP EnumMediaTypes( + __deref_out IEnumMediaTypes **ppEnum + ); + + // return an array of IPin* - the pins that this pin internally connects to + // All pins put in the array must be AddReffed (but no others) + // Errors: "Can't say" - FAIL, not enough slots - return S_FALSE + // Default: return E_NOTIMPL + // The filter graph will interpret NOT_IMPL as any input pin connects to + // all visible output pins and vice versa. + // apPin can be NULL if nPin==0 (not otherwise). + STDMETHODIMP QueryInternalConnections( + __out_ecount_part(*nPin,*nPin) IPin* *apPin, // array of IPin* + __inout ULONG *nPin // on input, the number of slots + // on output the number of pins + ) { return E_NOTIMPL; } + + // Called when no more data will be sent + STDMETHODIMP EndOfStream(void); + + // Begin/EndFlush still PURE + + // NewSegment notifies of the start/stop/rate applying to the data + // about to be received. Default implementation records data and + // returns S_OK. + // Override this to pass downstream. + STDMETHODIMP NewSegment( + REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate); + + //================================================================================ + // IQualityControl methods + //================================================================================ + + STDMETHODIMP Notify(IBaseFilter * pSender, Quality q); + + STDMETHODIMP SetSink(IQualityControl * piqc); + + // --- helper methods --- + + // Returns true if the pin is connected. false otherwise. + BOOL IsConnected(void) {return (m_Connected != NULL); }; + // Return the pin this is connected to (if any) + IPin * GetConnected() { return m_Connected; }; + + // Check if our filter is currently stopped + BOOL IsStopped() { + return (m_pFilter->m_State == State_Stopped); + }; + + // find out the current type version (used by enumerators) + virtual LONG GetMediaTypeVersion(); + void IncrementTypeVersion(); + + // switch the pin to active (paused or running) mode + // not an error to call this if already active + virtual HRESULT Active(void); + + // switch the pin to inactive state - may already be inactive + virtual HRESULT Inactive(void); + + // Notify of Run() from filter + virtual HRESULT Run(REFERENCE_TIME tStart); + + // check if the pin can support this specific proposed type and format + virtual HRESULT CheckMediaType(const CMediaType *) PURE; + + // set the connection to use this format (previously agreed) + virtual HRESULT SetMediaType(const CMediaType *); + + // check that the connection is ok before verifying it + // can be overridden eg to check what interfaces will be supported. + virtual HRESULT CheckConnect(IPin *); + + // Set and release resources required for a connection + virtual HRESULT BreakConnect(); + virtual HRESULT CompleteConnect(IPin *pReceivePin); + + // returns the preferred formats for a pin + virtual HRESULT GetMediaType(int iPosition, __inout CMediaType *pMediaType); + + // access to NewSegment values + REFERENCE_TIME CurrentStopTime() { + return m_tStop; + } + REFERENCE_TIME CurrentStartTime() { + return m_tStart; + } + double CurrentRate() { + return m_dRate; + } + + // Access name + LPWSTR Name() { return m_pName; }; + + // Can reconnectwhen active? + void SetReconnectWhenActive(bool bCanReconnect) + { + m_bCanReconnectWhenActive = bCanReconnect; + } + + bool CanReconnectWhenActive() + { + return m_bCanReconnectWhenActive; + } + +protected: + STDMETHODIMP DisconnectInternal(); +}; + + +//===================================================================== +//===================================================================== +// Defines CEnumPins +// +// Pin enumerator class that works by calling CBaseFilter. This interface +// is provided by CBaseFilter::EnumPins and calls GetPinCount() and +// GetPin() to enumerate existing pins. Needs to be a separate object so +// that it can be cloned (creating an existing object at the same +// position in the enumeration) +// +//===================================================================== +//===================================================================== + +class CEnumPins : public IEnumPins // The interface we support +{ + int m_Position; // Current ordinal position + int m_PinCount; // Number of pins available + CBaseFilter *m_pFilter; // The filter who owns us + LONG m_Version; // Pin version information + LONG m_cRef; + + typedef CGenericList<CBasePin> CPinList; + + CPinList m_PinCache; // These pointers have not been AddRef'ed and + // so they should not be dereferenced. They are + // merely kept to ID which pins have been enumerated. + +#ifdef DEBUG + DWORD m_dwCookie; +#endif + + /* If while we are retrieving a pin for example from the filter an error + occurs we assume that our internal state is stale with respect to the + filter (someone may have deleted all the pins). We can check before + starting whether or not the operation is likely to fail by asking the + filter what it's current version number is. If the filter has not + overriden the GetPinVersion method then this will always match */ + + BOOL AreWeOutOfSync() { + return (m_pFilter->GetPinVersion() == m_Version ? FALSE : TRUE); + }; + + /* This method performs the same operations as Reset, except is does not clear + the cache of pins already enumerated. */ + + STDMETHODIMP Refresh(); + +public: + + CEnumPins( + __in CBaseFilter *pFilter, + __in_opt CEnumPins *pEnumPins); + + virtual ~CEnumPins(); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, __deref_out void **ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IEnumPins + STDMETHODIMP Next( + ULONG cPins, // place this many pins... + __out_ecount(cPins) IPin ** ppPins, // ...in this array of IPin* + __out_opt ULONG * pcFetched // actual count passed returned here + ); + + STDMETHODIMP Skip(ULONG cPins); + STDMETHODIMP Reset(); + STDMETHODIMP Clone(__deref_out IEnumPins **ppEnum); + + +}; + + +//===================================================================== +//===================================================================== +// Defines CEnumMediaTypes +// +// Enumerates the preferred formats for input and output pins +//===================================================================== +//===================================================================== + +class CEnumMediaTypes : public IEnumMediaTypes // The interface we support +{ + int m_Position; // Current ordinal position + CBasePin *m_pPin; // The pin who owns us + LONG m_Version; // Media type version value + LONG m_cRef; +#ifdef DEBUG + DWORD m_dwCookie; +#endif + + /* The media types a filter supports can be quite dynamic so we add to + the general IEnumXXXX interface the ability to be signaled when they + change via an event handle the connected filter supplies. Until the + Reset method is called after the state changes all further calls to + the enumerator (except Reset) will return E_UNEXPECTED error code */ + + BOOL AreWeOutOfSync() { + return (m_pPin->GetMediaTypeVersion() == m_Version ? FALSE : TRUE); + }; + +public: + + CEnumMediaTypes( + __in CBasePin *pPin, + __in_opt CEnumMediaTypes *pEnumMediaTypes); + + virtual ~CEnumMediaTypes(); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, __deref_out void **ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IEnumMediaTypes + STDMETHODIMP Next( + ULONG cMediaTypes, // place this many pins... + __out_ecount(cMediaTypes) AM_MEDIA_TYPE ** ppMediaTypes, // ...in this array + __out_opt ULONG * pcFetched // actual count passed + ); + + STDMETHODIMP Skip(ULONG cMediaTypes); + STDMETHODIMP Reset(); + STDMETHODIMP Clone(__deref_out IEnumMediaTypes **ppEnum); +}; + + + + +//===================================================================== +//===================================================================== +// Defines CBaseOutputPin +// +// class derived from CBasePin that can pass buffers to a connected pin +// that supports IMemInputPin. Supports IPin. +// +// Derive your output pin from this. +// +//===================================================================== +//===================================================================== + +class AM_NOVTABLE CBaseOutputPin : public CBasePin +{ + +protected: + + IMemAllocator *m_pAllocator; + IMemInputPin *m_pInputPin; // interface on the downstreaminput pin + // set up in CheckConnect when we connect. + +public: + + CBaseOutputPin( + __in_opt LPCTSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName); +#ifdef UNICODE + CBaseOutputPin( + __in_opt LPCSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName); +#endif + // override CompleteConnect() so we can negotiate an allocator + virtual HRESULT CompleteConnect(IPin *pReceivePin); + + // negotiate the allocator and its buffer size/count and other properties + // Calls DecideBufferSize to set properties + virtual HRESULT DecideAllocator(IMemInputPin * pPin, __deref_out IMemAllocator ** pAlloc); + + // override this to set the buffer size and count. Return an error + // if the size/count is not to your liking. + // The allocator properties passed in are those requested by the + // input pin - use eg the alignment and prefix members if you have + // no preference on these. + virtual HRESULT DecideBufferSize( + IMemAllocator * pAlloc, + __inout ALLOCATOR_PROPERTIES * ppropInputRequest + ) PURE; + + // returns an empty sample buffer from the allocator + virtual HRESULT GetDeliveryBuffer(__deref_out IMediaSample ** ppSample, + __in_opt REFERENCE_TIME * pStartTime, + __in_opt REFERENCE_TIME * pEndTime, + DWORD dwFlags); + + // deliver a filled-in sample to the connected input pin + // note - you need to release it after calling this. The receiving + // pin will addref the sample if it needs to hold it beyond the + // call. + virtual HRESULT Deliver(IMediaSample *); + + // override this to control the connection + virtual HRESULT InitAllocator(__deref_out IMemAllocator **ppAlloc); + HRESULT CheckConnect(IPin *pPin); + HRESULT BreakConnect(); + + // override to call Commit and Decommit + HRESULT Active(void); + HRESULT Inactive(void); + + // we have a default handling of EndOfStream which is to return + // an error, since this should be called on input pins only + STDMETHODIMP EndOfStream(void); + + // called from elsewhere in our filter to pass EOS downstream to + // our connected input pin + virtual HRESULT DeliverEndOfStream(void); + + // same for Begin/EndFlush - we handle Begin/EndFlush since it + // is an error on an output pin, and we have Deliver methods to + // call the methods on the connected pin + STDMETHODIMP BeginFlush(void); + STDMETHODIMP EndFlush(void); + virtual HRESULT DeliverBeginFlush(void); + virtual HRESULT DeliverEndFlush(void); + + // deliver NewSegment to connected pin - you will need to + // override this if you queue any data in your output pin. + virtual HRESULT DeliverNewSegment( + REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate); + + //================================================================================ + // IQualityControl methods + //================================================================================ + + // All inherited from CBasePin and not overridden here. + // STDMETHODIMP Notify(IBaseFilter * pSender, Quality q); + // STDMETHODIMP SetSink(IQualityControl * piqc); +}; + + +//===================================================================== +//===================================================================== +// Defines CBaseInputPin +// +// derive your standard input pin from this. +// you need to supply GetMediaType and CheckConnect etc (see CBasePin), +// and you need to supply Receive to do something more useful. +// +//===================================================================== +//===================================================================== + +class AM_NOVTABLE CBaseInputPin : public CBasePin, + public IMemInputPin +{ + +protected: + + IMemAllocator *m_pAllocator; // Default memory allocator + + // allocator is read-only, so received samples + // cannot be modified (probably only relevant to in-place + // transforms + BYTE m_bReadOnly; + + // in flushing state (between BeginFlush and EndFlush) + // if TRUE, all Receives are returned with S_FALSE + BYTE m_bFlushing; + + // Sample properties - initalized in Receive + AM_SAMPLE2_PROPERTIES m_SampleProps; + +public: + + CBaseInputPin( + __in_opt LPCTSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName); +#ifdef UNICODE + CBaseInputPin( + __in_opt LPCSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName); +#endif + virtual ~CBaseInputPin(); + + DECLARE_IUNKNOWN + + // override this to publicise our interfaces + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv); + + // return the allocator interface that this input pin + // would like the output pin to use + STDMETHODIMP GetAllocator(__deref_out IMemAllocator ** ppAllocator); + + // tell the input pin which allocator the output pin is actually + // going to use. + STDMETHODIMP NotifyAllocator( + IMemAllocator * pAllocator, + BOOL bReadOnly); + + // do something with this media sample + STDMETHODIMP Receive(IMediaSample *pSample); + + // do something with these media samples + STDMETHODIMP ReceiveMultiple ( + __in_ecount(nSamples) IMediaSample **pSamples, + long nSamples, + __out long *nSamplesProcessed); + + // See if Receive() blocks + STDMETHODIMP ReceiveCanBlock(); + + // Default handling for BeginFlush - call at the beginning + // of your implementation (makes sure that all Receive calls + // fail). After calling this, you need to free any queued data + // and then call downstream. + STDMETHODIMP BeginFlush(void); + + // default handling for EndFlush - call at end of your implementation + // - before calling this, ensure that there is no queued data and no thread + // pushing any more without a further receive, then call downstream, + // then call this method to clear the m_bFlushing flag and re-enable + // receives + STDMETHODIMP EndFlush(void); + + // this method is optional (can return E_NOTIMPL). + // default implementation returns E_NOTIMPL. Override if you have + // specific alignment or prefix needs, but could use an upstream + // allocator + STDMETHODIMP GetAllocatorRequirements(__out ALLOCATOR_PROPERTIES*pProps); + + // Release the pin's allocator. + HRESULT BreakConnect(); + + // helper method to check the read-only flag + BOOL IsReadOnly() { + return m_bReadOnly; + }; + + // helper method to see if we are flushing + BOOL IsFlushing() { + return m_bFlushing; + }; + + // Override this for checking whether it's OK to process samples + // Also call this from EndOfStream. + virtual HRESULT CheckStreaming(); + + // Pass a Quality notification on to the appropriate sink + HRESULT PassNotify(Quality& q); + + + //================================================================================ + // IQualityControl methods (from CBasePin) + //================================================================================ + + STDMETHODIMP Notify(IBaseFilter * pSender, Quality q); + + // no need to override: + // STDMETHODIMP SetSink(IQualityControl * piqc); + + + // switch the pin to inactive state - may already be inactive + virtual HRESULT Inactive(void); + + // Return sample properties pointer + AM_SAMPLE2_PROPERTIES * SampleProps() { + ASSERT(m_SampleProps.cbData != 0); + return &m_SampleProps; + } + +}; + +/////////////////////////////////////////////////////////////////////////// +// CDynamicOutputPin +// + +class CDynamicOutputPin : public CBaseOutputPin, + public IPinFlowControl +{ +public: +#ifdef UNICODE + CDynamicOutputPin( + __in_opt LPCSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName); +#endif + + CDynamicOutputPin( + __in_opt LPCTSTR pObjectName, + __in CBaseFilter *pFilter, + __in CCritSec *pLock, + __inout HRESULT *phr, + __in_opt LPCWSTR pName); + + ~CDynamicOutputPin(); + + // IUnknown Methods + DECLARE_IUNKNOWN + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv); + + // IPin Methods + STDMETHODIMP Disconnect(void); + + // IPinFlowControl Methods + STDMETHODIMP Block(DWORD dwBlockFlags, HANDLE hEvent); + + // Set graph config info + void SetConfigInfo(IGraphConfig *pGraphConfig, HANDLE hStopEvent); + + #ifdef DEBUG + virtual HRESULT Deliver(IMediaSample *pSample); + virtual HRESULT DeliverEndOfStream(void); + virtual HRESULT DeliverNewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate); + #endif // DEBUG + + HRESULT DeliverBeginFlush(void); + HRESULT DeliverEndFlush(void); + + HRESULT Inactive(void); + HRESULT Active(void); + virtual HRESULT CompleteConnect(IPin *pReceivePin); + + virtual HRESULT StartUsingOutputPin(void); + virtual void StopUsingOutputPin(void); + virtual bool StreamingThreadUsingOutputPin(void); + + HRESULT ChangeOutputFormat + ( + const AM_MEDIA_TYPE *pmt, + REFERENCE_TIME tSegmentStart, + REFERENCE_TIME tSegmentStop, + double dSegmentRate + ); + HRESULT ChangeMediaType(const CMediaType *pmt); + HRESULT DynamicReconnect(const CMediaType *pmt); + +protected: + HRESULT SynchronousBlockOutputPin(void); + HRESULT AsynchronousBlockOutputPin(HANDLE hNotifyCallerPinBlockedEvent); + HRESULT UnblockOutputPin(void); + + void BlockOutputPin(void); + void ResetBlockState(void); + + static HRESULT WaitEvent(HANDLE hEvent); + + enum BLOCK_STATE + { + NOT_BLOCKED, + PENDING, + BLOCKED + }; + + // This lock should be held when the following class members are + // being used: m_hNotifyCallerPinBlockedEvent, m_BlockState, + // m_dwBlockCallerThreadID and m_dwNumOutstandingOutputPinUsers. + CCritSec m_BlockStateLock; + + // This event should be signaled when the output pin is + // not blocked. This is a manual reset event. For more + // information on events, see the documentation for + // CreateEvent() in the Windows SDK. + HANDLE m_hUnblockOutputPinEvent; + + // This event will be signaled when block operation succeedes or + // when the user cancels the block operation. The block operation + // can be canceled by calling IPinFlowControl2::Block( 0, NULL ) + // while the block operation is pending. + HANDLE m_hNotifyCallerPinBlockedEvent; + + // The state of the current block operation. + BLOCK_STATE m_BlockState; + + // The ID of the thread which last called IPinFlowControl::Block(). + // For more information on thread IDs, see the documentation for + // GetCurrentThreadID() in the Windows SDK. + DWORD m_dwBlockCallerThreadID; + + // The number of times StartUsingOutputPin() has been sucessfully + // called and a corresponding call to StopUsingOutputPin() has not + // been made. When this variable is greater than 0, the streaming + // thread is calling IPin::NewSegment(), IPin::EndOfStream(), + // IMemInputPin::Receive() or IMemInputPin::ReceiveMultiple(). The + // streaming thread could also be calling: DynamicReconnect(), + // ChangeMediaType() or ChangeOutputFormat(). The output pin cannot + // be blocked while the output pin is being used. + DWORD m_dwNumOutstandingOutputPinUsers; + + // This event should be set when the IMediaFilter::Stop() is called. + // This is a manual reset event. It is also set when the output pin + // delivers a flush to the connected input pin. + HANDLE m_hStopEvent; + IGraphConfig* m_pGraphConfig; + + // TRUE if the output pin's allocator's samples are read only. + // Otherwise FALSE. For more information, see the documentation + // for IMemInputPin::NotifyAllocator(). + BOOL m_bPinUsesReadOnlyAllocator; + +private: + HRESULT Initialize(void); + HRESULT ChangeMediaTypeHelper(const CMediaType *pmt); + + #ifdef DEBUG + void AssertValid(void); + #endif // DEBUG +}; + +class CAutoUsingOutputPin +{ +public: + CAutoUsingOutputPin( __in CDynamicOutputPin* pOutputPin, __inout HRESULT* phr ); + ~CAutoUsingOutputPin(); + +private: + CDynamicOutputPin* m_pOutputPin; +}; + +inline CAutoUsingOutputPin::CAutoUsingOutputPin( __in CDynamicOutputPin* pOutputPin, __inout HRESULT* phr ) : + m_pOutputPin(NULL) +{ + // The caller should always pass in valid pointers. + ASSERT( NULL != pOutputPin ); + ASSERT( NULL != phr ); + + // Make sure the user initialized phr. + ASSERT( S_OK == *phr ); + + HRESULT hr = pOutputPin->StartUsingOutputPin(); + if( FAILED( hr ) ) + { + *phr = hr; + return; + } + + m_pOutputPin = pOutputPin; +} + +inline CAutoUsingOutputPin::~CAutoUsingOutputPin() +{ + if( NULL != m_pOutputPin ) + { + m_pOutputPin->StopUsingOutputPin(); + } +} + +#ifdef DEBUG + +inline HRESULT CDynamicOutputPin::Deliver(IMediaSample *pSample) +{ + // The caller should call StartUsingOutputPin() before calling this + // method. + ASSERT(StreamingThreadUsingOutputPin()); + + return CBaseOutputPin::Deliver(pSample); +} + +inline HRESULT CDynamicOutputPin::DeliverEndOfStream(void) +{ + // The caller should call StartUsingOutputPin() before calling this + // method. + ASSERT( StreamingThreadUsingOutputPin() ); + + return CBaseOutputPin::DeliverEndOfStream(); +} + +inline HRESULT CDynamicOutputPin::DeliverNewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate) +{ + // The caller should call StartUsingOutputPin() before calling this + // method. + ASSERT(StreamingThreadUsingOutputPin()); + + return CBaseOutputPin::DeliverNewSegment(tStart, tStop, dRate); +} + +#endif // DEBUG + +//===================================================================== +//===================================================================== +// Memory allocators +// +// the shared memory transport between pins requires the input pin +// to provide a memory allocator that can provide sample objects. A +// sample object supports the IMediaSample interface. +// +// CBaseAllocator handles the management of free and busy samples. It +// allocates CMediaSample objects. CBaseAllocator is an abstract class: +// in particular it has no method of initializing the list of free +// samples. CMemAllocator is derived from CBaseAllocator and initializes +// the list of samples using memory from the standard IMalloc interface. +// +// If you want your buffers to live in some special area of memory, +// derive your allocator object from CBaseAllocator. If you derive your +// IMemInputPin interface object from CBaseMemInputPin, you will get +// CMemAllocator-based allocation etc for free and will just need to +// supply the Receive handling, and media type / format negotiation. +//===================================================================== +//===================================================================== + + +//===================================================================== +//===================================================================== +// Defines CMediaSample +// +// an object of this class supports IMediaSample and represents a buffer +// for media data with some associated properties. Releasing it returns +// it to a freelist managed by a CBaseAllocator derived object. +//===================================================================== +//===================================================================== + +class CMediaSample : public IMediaSample2 // The interface we support +{ + +protected: + + friend class CBaseAllocator; + + /* Values for dwFlags - these are used for backward compatiblity + only now - use AM_SAMPLE_xxx + */ + enum { Sample_SyncPoint = 0x01, /* Is this a sync point */ + Sample_Preroll = 0x02, /* Is this a preroll sample */ + Sample_Discontinuity = 0x04, /* Set if start of new segment */ + Sample_TypeChanged = 0x08, /* Has the type changed */ + Sample_TimeValid = 0x10, /* Set if time is valid */ + Sample_MediaTimeValid = 0x20, /* Is the media time valid */ + Sample_TimeDiscontinuity = 0x40, /* Time discontinuity */ + Sample_StopValid = 0x100, /* Stop time valid */ + Sample_ValidFlags = 0x1FF + }; + + /* Properties, the media sample class can be a container for a format + change in which case we take a copy of a type through the SetMediaType + interface function and then return it when GetMediaType is called. As + we do no internal processing on it we leave it as a pointer */ + + DWORD m_dwFlags; /* Flags for this sample */ + /* Type specific flags are packed + into the top word + */ + DWORD m_dwTypeSpecificFlags; /* Media type specific flags */ + __field_ecount_opt(m_cbBuffer) LPBYTE m_pBuffer; /* Pointer to the complete buffer */ + LONG m_lActual; /* Length of data in this sample */ + LONG m_cbBuffer; /* Size of the buffer */ + CBaseAllocator *m_pAllocator; /* The allocator who owns us */ + CMediaSample *m_pNext; /* Chaining in free list */ + REFERENCE_TIME m_Start; /* Start sample time */ + REFERENCE_TIME m_End; /* End sample time */ + LONGLONG m_MediaStart; /* Real media start position */ + LONG m_MediaEnd; /* A difference to get the end */ + AM_MEDIA_TYPE *m_pMediaType; /* Media type change data */ + DWORD m_dwStreamId; /* Stream id */ +public: + LONG m_cRef; /* Reference count */ + + +public: + + CMediaSample( + __in_opt LPCTSTR pName, + __in_opt CBaseAllocator *pAllocator, + __inout_opt HRESULT *phr, + __in_bcount_opt(length) LPBYTE pBuffer = NULL, + LONG length = 0); +#ifdef UNICODE + CMediaSample( + __in_opt LPCSTR pName, + __in_opt CBaseAllocator *pAllocator, + __inout_opt HRESULT *phr, + __in_bcount_opt(length) LPBYTE pBuffer = NULL, + LONG length = 0); +#endif + + virtual ~CMediaSample(); + + /* Note the media sample does not delegate to its owner */ + + STDMETHODIMP QueryInterface(REFIID riid, __deref_out void **ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // set the buffer pointer and length. Used by allocators that + // want variable sized pointers or pointers into already-read data. + // This is only available through a CMediaSample* not an IMediaSample* + // and so cannot be changed by clients. + HRESULT SetPointer(__in_bcount(cBytes) BYTE * ptr, LONG cBytes); + + // Get me a read/write pointer to this buffer's memory. + STDMETHODIMP GetPointer(__deref_out BYTE ** ppBuffer); + + STDMETHODIMP_(LONG) GetSize(void); + + // get the stream time at which this sample should start and finish. + STDMETHODIMP GetTime( + __out REFERENCE_TIME * pTimeStart, // put time here + __out REFERENCE_TIME * pTimeEnd + ); + + // Set the stream time at which this sample should start and finish. + STDMETHODIMP SetTime( + __in_opt REFERENCE_TIME * pTimeStart, // put time here + __in_opt REFERENCE_TIME * pTimeEnd + ); + STDMETHODIMP IsSyncPoint(void); + STDMETHODIMP SetSyncPoint(BOOL bIsSyncPoint); + STDMETHODIMP IsPreroll(void); + STDMETHODIMP SetPreroll(BOOL bIsPreroll); + + STDMETHODIMP_(LONG) GetActualDataLength(void); + STDMETHODIMP SetActualDataLength(LONG lActual); + + // these allow for limited format changes in band + + STDMETHODIMP GetMediaType(__deref_out AM_MEDIA_TYPE **ppMediaType); + STDMETHODIMP SetMediaType(__in_opt AM_MEDIA_TYPE *pMediaType); + + // returns S_OK if there is a discontinuity in the data (this same is + // not a continuation of the previous stream of data + // - there has been a seek). + STDMETHODIMP IsDiscontinuity(void); + // set the discontinuity property - TRUE if this sample is not a + // continuation, but a new sample after a seek. + STDMETHODIMP SetDiscontinuity(BOOL bDiscontinuity); + + // get the media times for this sample + STDMETHODIMP GetMediaTime( + __out LONGLONG * pTimeStart, + __out LONGLONG * pTimeEnd + ); + + // Set the media times for this sample + STDMETHODIMP SetMediaTime( + __in_opt LONGLONG * pTimeStart, + __in_opt LONGLONG * pTimeEnd + ); + + // Set and get properties (IMediaSample2) + STDMETHODIMP GetProperties( + DWORD cbProperties, + __out_bcount(cbProperties) BYTE * pbProperties + ); + + STDMETHODIMP SetProperties( + DWORD cbProperties, + __in_bcount(cbProperties) const BYTE * pbProperties + ); +}; + + +//===================================================================== +//===================================================================== +// Defines CBaseAllocator +// +// Abstract base class that manages a list of media samples +// +// This class provides support for getting buffers from the free list, +// including handling of commit and (asynchronous) decommit. +// +// Derive from this class and override the Alloc and Free functions to +// allocate your CMediaSample (or derived) objects and add them to the +// free list, preparing them as necessary. +//===================================================================== +//===================================================================== + +class AM_NOVTABLE CBaseAllocator : public CUnknown,// A non delegating IUnknown + public IMemAllocatorCallbackTemp, // The interface we support + public CCritSec // Provides object locking +{ + class CSampleList; + friend class CSampleList; + + /* Trick to get at protected member in CMediaSample */ + static CMediaSample * &NextSample(__in CMediaSample *pSample) + { + return pSample->m_pNext; + }; + + /* Mini list class for the free list */ + class CSampleList + { + public: + CSampleList() : m_List(NULL), m_nOnList(0) {}; +#ifdef DEBUG + ~CSampleList() + { + ASSERT(m_nOnList == 0); + }; +#endif + CMediaSample *Head() const { return m_List; }; + CMediaSample *Next(__in CMediaSample *pSample) const { return CBaseAllocator::NextSample(pSample); }; + int GetCount() const { return m_nOnList; }; + void Add(__inout CMediaSample *pSample) + { + ASSERT(pSample != NULL); + CBaseAllocator::NextSample(pSample) = m_List; + m_List = pSample; + m_nOnList++; + }; + CMediaSample *RemoveHead() + { + CMediaSample *pSample = m_List; + if (pSample != NULL) { + m_List = CBaseAllocator::NextSample(m_List); + m_nOnList--; + } + return pSample; + }; + void Remove(__inout CMediaSample *pSample); + + public: + CMediaSample *m_List; + int m_nOnList; + }; +protected: + + CSampleList m_lFree; // Free list + + /* Note to overriders of CBaseAllocator. + + We use a lazy signalling mechanism for waiting for samples. + This means we don't call the OS if no waits occur. + + In order to implement this: + + 1. When a new sample is added to m_lFree call NotifySample() which + calls ReleaseSemaphore on m_hSem with a count of m_lWaiting and + sets m_lWaiting to 0. + This must all be done holding the allocator's critical section. + + 2. When waiting for a sample call SetWaiting() which increments + m_lWaiting BEFORE leaving the allocator's critical section. + + 3. Actually wait by calling WaitForSingleObject(m_hSem, INFINITE) + having left the allocator's critical section. The effect of + this is to remove 1 from the semaphore's count. You MUST call + this once having incremented m_lWaiting. + + The following are then true when the critical section is not held : + (let nWaiting = number about to wait or waiting) + + (1) if (m_lFree.GetCount() != 0) then (m_lWaiting == 0) + (2) m_lWaiting + Semaphore count == nWaiting + + We would deadlock if + nWaiting != 0 && + m_lFree.GetCount() != 0 && + Semaphore count == 0 + + But from (1) if m_lFree.GetCount() != 0 then m_lWaiting == 0 so + from (2) Semaphore count == nWaiting (which is non-0) so the + deadlock can't happen. + */ + + HANDLE m_hSem; // For signalling + long m_lWaiting; // Waiting for a free element + long m_lCount; // how many buffers we have agreed to provide + long m_lAllocated; // how many buffers are currently allocated + long m_lSize; // agreed size of each buffer + long m_lAlignment; // agreed alignment + long m_lPrefix; // agreed prefix (preceeds GetPointer() value) + BOOL m_bChanged; // Have the buffer requirements changed + + // if true, we are decommitted and can't allocate memory + BOOL m_bCommitted; + // if true, the decommit has happened, but we haven't called Free yet + // as there are still outstanding buffers + BOOL m_bDecommitInProgress; + + // Notification interface + IMemAllocatorNotifyCallbackTemp *m_pNotify; + + BOOL m_fEnableReleaseCallback; + + // called to decommit the memory when the last buffer is freed + // pure virtual - need to override this + virtual void Free(void) PURE; + + // override to allocate the memory when commit called + virtual HRESULT Alloc(void); + +public: + + CBaseAllocator( + __in_opt LPCTSTR , __inout_opt LPUNKNOWN, __inout HRESULT *, + BOOL bEvent = TRUE, BOOL fEnableReleaseCallback = FALSE); +#ifdef UNICODE + CBaseAllocator( + __in_opt LPCSTR , __inout_opt LPUNKNOWN, __inout HRESULT *, + BOOL bEvent = TRUE, BOOL fEnableReleaseCallback = FALSE); +#endif + virtual ~CBaseAllocator(); + + DECLARE_IUNKNOWN + + // override this to publicise our interfaces + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv); + + STDMETHODIMP SetProperties( + __in ALLOCATOR_PROPERTIES* pRequest, + __out ALLOCATOR_PROPERTIES* pActual); + + // return the properties actually being used on this allocator + STDMETHODIMP GetProperties( + __out ALLOCATOR_PROPERTIES* pProps); + + // override Commit to allocate memory. We handle the GetBuffer + //state changes + STDMETHODIMP Commit(); + + // override this to handle the memory freeing. We handle any outstanding + // GetBuffer calls + STDMETHODIMP Decommit(); + + // get container for a sample. Blocking, synchronous call to get the + // next free buffer (as represented by an IMediaSample interface). + // on return, the time etc properties will be invalid, but the buffer + // pointer and size will be correct. The two time parameters are + // optional and either may be NULL, they may alternatively be set to + // the start and end times the sample will have attached to it + // bPrevFramesSkipped is not used (used only by the video renderer's + // allocator where it affects quality management in direct draw). + + STDMETHODIMP GetBuffer(__deref_out IMediaSample **ppBuffer, + __in_opt REFERENCE_TIME * pStartTime, + __in_opt REFERENCE_TIME * pEndTime, + DWORD dwFlags); + + // final release of a CMediaSample will call this + STDMETHODIMP ReleaseBuffer(IMediaSample *pBuffer); + // obsolete:: virtual void PutOnFreeList(CMediaSample * pSample); + + STDMETHODIMP SetNotify(IMemAllocatorNotifyCallbackTemp *pNotify); + + STDMETHODIMP GetFreeCount(__out LONG *plBuffersFree); + + // Notify that a sample is available + void NotifySample(); + + // Notify that we're waiting for a sample + void SetWaiting() { m_lWaiting++; }; +}; + + +//===================================================================== +//===================================================================== +// Defines CMemAllocator +// +// this is an allocator based on CBaseAllocator that allocates sample +// buffers in main memory (from 'new'). You must call SetProperties +// before calling Commit. +// +// we don't free the memory when going into Decommit state. The simplest +// way to implement this without complicating CBaseAllocator is to +// have a Free() function, called to go into decommit state, that does +// nothing and a ReallyFree function called from our destructor that +// actually frees the memory. +//===================================================================== +//===================================================================== + +// Make me one from quartz.dll +STDAPI CreateMemoryAllocator(__deref_out IMemAllocator **ppAllocator); + +class CMemAllocator : public CBaseAllocator +{ + +protected: + + LPBYTE m_pBuffer; // combined memory for all buffers + + // override to free the memory when decommit completes + // - we actually do nothing, and save the memory until deletion. + void Free(void); + + // called from the destructor (and from Alloc if changing size/count) to + // actually free up the memory + void ReallyFree(void); + + // overriden to allocate the memory when commit called + HRESULT Alloc(void); + +public: + /* This goes in the factory template table to create new instances */ + static CUnknown *CreateInstance(__inout_opt LPUNKNOWN, __inout HRESULT *); + + STDMETHODIMP SetProperties( + __in ALLOCATOR_PROPERTIES* pRequest, + __out ALLOCATOR_PROPERTIES* pActual); + + CMemAllocator(__in_opt LPCTSTR , __inout_opt LPUNKNOWN, __inout HRESULT *); +#ifdef UNICODE + CMemAllocator(__in_opt LPCSTR , __inout_opt LPUNKNOWN, __inout HRESULT *); +#endif + ~CMemAllocator(); +}; + +// helper used by IAMovieSetup implementation +STDAPI +AMovieSetupRegisterFilter( const AMOVIESETUP_FILTER * const psetupdata + , IFilterMapper * pIFM + , BOOL bRegister ); + + +/////////////////////////////////////////////////////////////////////////// +// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ +/////////////////////////////////////////////////////////////////////////// + +#endif /* __FILTER__ */ + + + diff --git a/Src/Plugins/Input/in_dshow/base/amvideo.cpp b/Src/Plugins/Input/in_dshow/base/amvideo.cpp new file mode 100644 index 00000000..5c2755d6 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/amvideo.cpp @@ -0,0 +1,275 @@ +//------------------------------------------------------------------------------ +// File: AMVideo.cpp +// +// Desc: DirectShow base classes - implements helper functions for +// bitmap formats. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> +#include <limits.h> + +// These are bit field masks for true colour devices + +const DWORD bits555[] = {0x007C00,0x0003E0,0x00001F}; +const DWORD bits565[] = {0x00F800,0x0007E0,0x00001F}; +const DWORD bits888[] = {0xFF0000,0x00FF00,0x0000FF}; + +// This maps bitmap subtypes into a bits per pixel value and also a +// name. unicode and ansi versions are stored because we have to +// return a pointer to a static string. +const struct { + const GUID *pSubtype; + WORD BitCount; + CHAR *pName; + WCHAR *wszName; +} BitCountMap[] = { &MEDIASUBTYPE_RGB1, 1, "RGB Monochrome", L"RGB Monochrome", + &MEDIASUBTYPE_RGB4, 4, "RGB VGA", L"RGB VGA", + &MEDIASUBTYPE_RGB8, 8, "RGB 8", L"RGB 8", + &MEDIASUBTYPE_RGB565, 16, "RGB 565 (16 bit)", L"RGB 565 (16 bit)", + &MEDIASUBTYPE_RGB555, 16, "RGB 555 (16 bit)", L"RGB 555 (16 bit)", + &MEDIASUBTYPE_RGB24, 24, "RGB 24", L"RGB 24", + &MEDIASUBTYPE_RGB32, 32, "RGB 32", L"RGB 32", + &MEDIASUBTYPE_ARGB32, 32, "ARGB 32", L"ARGB 32", + &MEDIASUBTYPE_Overlay, 0, "Overlay", L"Overlay", + &GUID_NULL, 0, "UNKNOWN", L"UNKNOWN" +}; + +// Return the size of the bitmap as defined by this header + +STDAPI_(DWORD) GetBitmapSize(const BITMAPINFOHEADER *pHeader) +{ + return DIBSIZE(*pHeader); +} + + +// This is called if the header has a 16 bit colour depth and needs to work +// out the detailed type from the bit fields (either RGB 565 or RGB 555) + +STDAPI_(const GUID) GetTrueColorType(const BITMAPINFOHEADER *pbmiHeader) +{ + BITMAPINFO *pbmInfo = (BITMAPINFO *) pbmiHeader; + ASSERT(pbmiHeader->biBitCount == 16); + + // If its BI_RGB then it's RGB 555 by default + + if (pbmiHeader->biCompression == BI_RGB) { + return MEDIASUBTYPE_RGB555; + } + + // Compare the bit fields with RGB 555 + + DWORD *pMask = (DWORD *) pbmInfo->bmiColors; + if (pMask[0] == bits555[0]) { + if (pMask[1] == bits555[1]) { + if (pMask[2] == bits555[2]) { + return MEDIASUBTYPE_RGB555; + } + } + } + + // Compare the bit fields with RGB 565 + + pMask = (DWORD *) pbmInfo->bmiColors; + if (pMask[0] == bits565[0]) { + if (pMask[1] == bits565[1]) { + if (pMask[2] == bits565[2]) { + return MEDIASUBTYPE_RGB565; + } + } + } + return GUID_NULL; +} + + +// Given a BITMAPINFOHEADER structure this returns the GUID sub type that is +// used to describe it in format negotiations. For example a video codec fills +// in the format block with a VIDEOINFO structure, it also fills in the major +// type with MEDIATYPE_VIDEO and the subtype with a GUID that matches the bit +// count, for example if it is an eight bit image then MEDIASUBTYPE_RGB8 + +STDAPI_(const GUID) GetBitmapSubtype(const BITMAPINFOHEADER *pbmiHeader) +{ + ASSERT(pbmiHeader); + + // If it's not RGB then create a GUID from the compression type + + if (pbmiHeader->biCompression != BI_RGB) { + if (pbmiHeader->biCompression != BI_BITFIELDS) { + FOURCCMap FourCCMap(pbmiHeader->biCompression); + return (const GUID) FourCCMap; + } + } + + // Map the RGB DIB bit depth to a image GUID + + switch(pbmiHeader->biBitCount) { + case 1 : return MEDIASUBTYPE_RGB1; + case 4 : return MEDIASUBTYPE_RGB4; + case 8 : return MEDIASUBTYPE_RGB8; + case 16 : return GetTrueColorType(pbmiHeader); + case 24 : return MEDIASUBTYPE_RGB24; + case 32 : return MEDIASUBTYPE_RGB32; + } + return GUID_NULL; +} + + +// Given a video bitmap subtype we return the number of bits per pixel it uses +// We return a WORD bit count as thats what the BITMAPINFOHEADER uses. If the +// GUID subtype is not found in the table we return an invalid USHRT_MAX + +STDAPI_(WORD) GetBitCount(const GUID *pSubtype) +{ + ASSERT(pSubtype); + const GUID *pMediaSubtype; + INT iPosition = 0; + + // Scan the mapping list seeing if the source GUID matches any known + // bitmap subtypes, the list is terminated by a GUID_NULL entry + + while (TRUE) { + pMediaSubtype = BitCountMap[iPosition].pSubtype; + if (IsEqualGUID(*pMediaSubtype,GUID_NULL)) { + return USHRT_MAX; + } + if (IsEqualGUID(*pMediaSubtype,*pSubtype)) { + return BitCountMap[iPosition].BitCount; + } + iPosition++; + } +} + + +// Given a bitmap subtype we return a description name that can be used for +// debug purposes. In a retail build this function still returns the names +// If the subtype isn't found in the lookup table we return string UNKNOWN + +int LocateSubtype(const GUID *pSubtype) +{ + ASSERT(pSubtype); + const GUID *pMediaSubtype; + INT iPosition = 0; + + // Scan the mapping list seeing if the source GUID matches any known + // bitmap subtypes, the list is terminated by a GUID_NULL entry + + while (TRUE) { + pMediaSubtype = BitCountMap[iPosition].pSubtype; + if (IsEqualGUID(*pMediaSubtype,*pSubtype) || + IsEqualGUID(*pMediaSubtype,GUID_NULL) + ) + { + break; + } + + iPosition++; + } + + return iPosition; +} + + + +STDAPI_(WCHAR *) GetSubtypeNameW(const GUID *pSubtype) +{ + return BitCountMap[LocateSubtype(pSubtype)].wszName; +} + +STDAPI_(CHAR *) GetSubtypeNameA(const GUID *pSubtype) +{ + return BitCountMap[LocateSubtype(pSubtype)].pName; +} + +#ifndef GetSubtypeName +#error wxutil.h should have defined GetSubtypeName +#endif +#undef GetSubtypeName + +// this is here for people that linked to it directly; most people +// would use the header file that picks the A or W version. +STDAPI_(CHAR *) GetSubtypeName(const GUID *pSubtype) +{ + return GetSubtypeNameA(pSubtype); +} + + +// The mechanism for describing a bitmap format is with the BITMAPINFOHEADER +// This is really messy to deal with because it invariably has fields that +// follow it holding bit fields, palettes and the rest. This function gives +// the number of bytes required to hold a VIDEOINFO that represents it. This +// count includes the prefix information (like the rcSource rectangle) the +// BITMAPINFOHEADER field, and any other colour information on the end. +// +// WARNING If you want to copy a BITMAPINFOHEADER into a VIDEOINFO always make +// sure that you use the HEADER macro because the BITMAPINFOHEADER field isn't +// right at the start of the VIDEOINFO (there are a number of other fields), +// +// CopyMemory(HEADER(pVideoInfo),pbmi,sizeof(BITMAPINFOHEADER)); +// + +STDAPI_(LONG) GetBitmapFormatSize(const BITMAPINFOHEADER *pHeader) +{ + // Everyone has this to start with this + LONG Size = SIZE_PREHEADER + pHeader->biSize; + + ASSERT(pHeader->biSize >= sizeof(BITMAPINFOHEADER)); + + // Does this format use a palette, if the number of colours actually used + // is zero then it is set to the maximum that are allowed for that colour + // depth (an example is 256 for eight bits). Truecolour formats may also + // pass a palette with them in which case the used count is non zero + + // This would scare me. + ASSERT(pHeader->biBitCount <= iPALETTE || pHeader->biClrUsed == 0); + + if (pHeader->biBitCount <= iPALETTE || pHeader->biClrUsed) { + LONG Entries = (DWORD) 1 << pHeader->biBitCount; + if (pHeader->biClrUsed) { + Entries = pHeader->biClrUsed; + } + Size += Entries * sizeof(RGBQUAD); + } + + // Truecolour formats may have a BI_BITFIELDS specifier for compression + // type which means that room for three DWORDs should be allocated that + // specify where in each pixel the RGB colour components may be found + + if (pHeader->biCompression == BI_BITFIELDS) { + Size += SIZE_MASKS; + } + + // A BITMAPINFO for a palettised image may also contain a palette map that + // provides the information to map from a source palette to a destination + // palette during a BitBlt for example, because this information is only + // ever processed during drawing you don't normally store the palette map + // nor have any way of knowing if it is present in the data structure + + return Size; +} + + +// Returns TRUE if the VIDEOINFO contains a palette + +STDAPI_(BOOL) ContainsPalette(const VIDEOINFOHEADER *pVideoInfo) +{ + if (PALETTISED(pVideoInfo) == FALSE) { + if (pVideoInfo->bmiHeader.biClrUsed == 0) { + return FALSE; + } + } + return TRUE; +} + + +// Return a pointer to the first entry in a palette + +STDAPI_(const RGBQUAD *) GetBitmapPalette(const VIDEOINFOHEADER *pVideoInfo) +{ + if (pVideoInfo->bmiHeader.biCompression == BI_BITFIELDS) { + return TRUECOLOR(pVideoInfo)->bmiColors; + } + return COLORS(pVideoInfo); +} diff --git a/Src/Plugins/Input/in_dshow/base/arithutil.cpp b/Src/Plugins/Input/in_dshow/base/arithutil.cpp new file mode 100644 index 00000000..79ddaabf --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/arithutil.cpp @@ -0,0 +1,360 @@ +//------------------------------------------------------------------------------ +// File: ArithUtil.cpp +// +// Desc: DirectShow base classes - implements helper classes for building +// multimedia filters. +// +// Copyright (c) 1992-2004 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + +#include <streams.h> + +// +// Declare function from largeint.h we need so that PPC can build +// + +// +// Enlarged integer divide - 64-bits / 32-bits > 32-bits +// + +#ifndef _X86_ + +#define LLtoU64(x) (*(unsigned __int64*)(void*)(&(x))) + +__inline +ULONG +WINAPI +EnlargedUnsignedDivide ( + IN ULARGE_INTEGER Dividend, + IN ULONG Divisor, + IN PULONG Remainder + ) +{ + // return remainder if necessary + if (Remainder != NULL) + *Remainder = (ULONG)(LLtoU64(Dividend) % Divisor); + return (ULONG)(LLtoU64(Dividend) / Divisor); +} + +#else +__inline +ULONG +WINAPI +EnlargedUnsignedDivide ( + IN ULARGE_INTEGER Dividend, + IN ULONG Divisor, + IN PULONG Remainder + ) +{ + ULONG ulResult; + _asm { + mov eax,Dividend.LowPart + mov edx,Dividend.HighPart + mov ecx,Remainder + div Divisor + or ecx,ecx + jz short label + mov [ecx],edx +label: + mov ulResult,eax + } + return ulResult; +} +#endif + + +/* Arithmetic functions to help with time format conversions +*/ + +#ifdef _M_ALPHA +// work around bug in version 12.00.8385 of the alpha compiler where +// UInt32x32To64 sign-extends its arguments (?) +#undef UInt32x32To64 +#define UInt32x32To64(a, b) (((ULONGLONG)((ULONG)(a)) & 0xffffffff) * ((ULONGLONG)((ULONG)(b)) & 0xffffffff)) +#endif + +/* Compute (a * b + d) / c */ +LONGLONG WINAPI llMulDiv(LONGLONG a, LONGLONG b, LONGLONG c, LONGLONG d) +{ + /* Compute the absolute values to avoid signed arithmetic problems */ + ULARGE_INTEGER ua, ub; + DWORDLONG uc; + + ua.QuadPart = (DWORDLONG)(a >= 0 ? a : -a); + ub.QuadPart = (DWORDLONG)(b >= 0 ? b : -b); + uc = (DWORDLONG)(c >= 0 ? c : -c); + BOOL bSign = (a < 0) ^ (b < 0); + + /* Do long multiplication */ + ULARGE_INTEGER p[2]; + p[0].QuadPart = UInt32x32To64(ua.LowPart, ub.LowPart); + + /* This next computation cannot overflow into p[1].HighPart because + the max number we can compute here is: + + (2 ** 32 - 1) * (2 ** 32 - 1) + // ua.LowPart * ub.LowPart + (2 ** 32) * (2 ** 31) * (2 ** 32 - 1) * 2 // x.LowPart * y.HighPart * 2 + + == 2 ** 96 - 2 ** 64 + (2 ** 64 - 2 ** 33 + 1) + == 2 ** 96 - 2 ** 33 + 1 + < 2 ** 96 + */ + + ULARGE_INTEGER x; + x.QuadPart = UInt32x32To64(ua.LowPart, ub.HighPart) + + UInt32x32To64(ua.HighPart, ub.LowPart) + + p[0].HighPart; + p[0].HighPart = x.LowPart; + p[1].QuadPart = UInt32x32To64(ua.HighPart, ub.HighPart) + x.HighPart; + + if (d != 0) { + ULARGE_INTEGER ud[2]; + if (bSign) { + ud[0].QuadPart = (DWORDLONG)(-d); + if (d > 0) { + /* -d < 0 */ + ud[1].QuadPart = (DWORDLONG)(LONGLONG)-1; + } else { + ud[1].QuadPart = (DWORDLONG)0; + } + } else { + ud[0].QuadPart = (DWORDLONG)d; + if (d < 0) { + ud[1].QuadPart = (DWORDLONG)(LONGLONG)-1; + } else { + ud[1].QuadPart = (DWORDLONG)0; + } + } + /* Now do extended addition */ + ULARGE_INTEGER uliTotal; + + /* Add ls DWORDs */ + uliTotal.QuadPart = (DWORDLONG)ud[0].LowPart + p[0].LowPart; + p[0].LowPart = uliTotal.LowPart; + + /* Propagate carry */ + uliTotal.LowPart = uliTotal.HighPart; + uliTotal.HighPart = 0; + + /* Add 2nd most ls DWORDs */ + uliTotal.QuadPart += (DWORDLONG)ud[0].HighPart + p[0].HighPart; + p[0].HighPart = uliTotal.LowPart; + + /* Propagate carry */ + uliTotal.LowPart = uliTotal.HighPart; + uliTotal.HighPart = 0; + + /* Add MS DWORDLONGs - no carry expected */ + p[1].QuadPart += ud[1].QuadPart + uliTotal.QuadPart; + + /* Now see if we got a sign change from the addition */ + if ((LONG)p[1].HighPart < 0) { + bSign = !bSign; + + /* Negate the current value (ugh!) */ + p[0].QuadPart = ~p[0].QuadPart; + p[1].QuadPart = ~p[1].QuadPart; + p[0].QuadPart += 1; + p[1].QuadPart += (p[0].QuadPart == 0); + } + } + + /* Now for the division */ + if (c < 0) { + bSign = !bSign; + } + + + /* This will catch c == 0 and overflow */ + if (uc <= p[1].QuadPart) { + return bSign ? (LONGLONG)0x8000000000000000 : + (LONGLONG)0x7FFFFFFFFFFFFFFF; + } + + DWORDLONG ullResult; + + /* Do the division */ + /* If the dividend is a DWORD_LONG use the compiler */ + if (p[1].QuadPart == 0) { + ullResult = p[0].QuadPart / uc; + return bSign ? -(LONGLONG)ullResult : (LONGLONG)ullResult; + } + + /* If the divisor is a DWORD then its simpler */ + ULARGE_INTEGER ulic; + ulic.QuadPart = uc; + if (ulic.HighPart == 0) { + ULARGE_INTEGER uliDividend; + ULARGE_INTEGER uliResult; + DWORD dwDivisor = (DWORD)uc; + // ASSERT(p[1].HighPart == 0 && p[1].LowPart < dwDivisor); + uliDividend.HighPart = p[1].LowPart; + uliDividend.LowPart = p[0].HighPart; +#ifndef USE_LARGEINT + uliResult.HighPart = (DWORD)(uliDividend.QuadPart / dwDivisor); + p[0].HighPart = (DWORD)(uliDividend.QuadPart % dwDivisor); + uliResult.LowPart = 0; + uliResult.QuadPart = p[0].QuadPart / dwDivisor + uliResult.QuadPart; +#else + /* NOTE - this routine will take exceptions if + the result does not fit in a DWORD + */ + if (uliDividend.QuadPart >= (DWORDLONG)dwDivisor) { + uliResult.HighPart = EnlargedUnsignedDivide( + uliDividend, + dwDivisor, + &p[0].HighPart); + } else { + uliResult.HighPart = 0; + } + uliResult.LowPart = EnlargedUnsignedDivide( + p[0], + dwDivisor, + NULL); +#endif + return bSign ? -(LONGLONG)uliResult.QuadPart : + (LONGLONG)uliResult.QuadPart; + } + + + ullResult = 0; + + /* OK - do long division */ + for (int i = 0; i < 64; i++) { + ullResult <<= 1; + + /* Shift 128 bit p left 1 */ + p[1].QuadPart <<= 1; + if ((p[0].HighPart & 0x80000000) != 0) { + p[1].LowPart++; + } + p[0].QuadPart <<= 1; + + /* Compare */ + if (uc <= p[1].QuadPart) { + p[1].QuadPart -= uc; + ullResult += 1; + } + } + + return bSign ? - (LONGLONG)ullResult : (LONGLONG)ullResult; +} + +LONGLONG WINAPI Int64x32Div32(LONGLONG a, LONG b, LONG c, LONG d) +{ + ULARGE_INTEGER ua; + DWORD ub; + DWORD uc; + + /* Compute the absolute values to avoid signed arithmetic problems */ + ua.QuadPart = (DWORDLONG)(a >= 0 ? a : -a); + ub = (DWORD)(b >= 0 ? b : -b); + uc = (DWORD)(c >= 0 ? c : -c); + BOOL bSign = (a < 0) ^ (b < 0); + + /* Do long multiplication */ + ULARGE_INTEGER p0; + DWORD p1; + p0.QuadPart = UInt32x32To64(ua.LowPart, ub); + + if (ua.HighPart != 0) { + ULARGE_INTEGER x; + x.QuadPart = UInt32x32To64(ua.HighPart, ub) + p0.HighPart; + p0.HighPart = x.LowPart; + p1 = x.HighPart; + } else { + p1 = 0; + } + + if (d != 0) { + ULARGE_INTEGER ud0; + DWORD ud1; + + if (bSign) { + // + // Cast d to LONGLONG first otherwise -0x80000000 sign extends + // incorrectly + // + ud0.QuadPart = (DWORDLONG)(-(LONGLONG)d); + if (d > 0) { + /* -d < 0 */ + ud1 = (DWORD)-1; + } else { + ud1 = (DWORD)0; + } + } else { + ud0.QuadPart = (DWORDLONG)d; + if (d < 0) { + ud1 = (DWORD)-1; + } else { + ud1 = (DWORD)0; + } + } + /* Now do extended addition */ + ULARGE_INTEGER uliTotal; + + /* Add ls DWORDs */ + uliTotal.QuadPart = (DWORDLONG)ud0.LowPart + p0.LowPart; + p0.LowPart = uliTotal.LowPart; + + /* Propagate carry */ + uliTotal.LowPart = uliTotal.HighPart; + uliTotal.HighPart = 0; + + /* Add 2nd most ls DWORDs */ + uliTotal.QuadPart += (DWORDLONG)ud0.HighPart + p0.HighPart; + p0.HighPart = uliTotal.LowPart; + + /* Add MS DWORDLONGs - no carry expected */ + p1 += ud1 + uliTotal.HighPart; + + /* Now see if we got a sign change from the addition */ + if ((LONG)p1 < 0) { + bSign = !bSign; + + /* Negate the current value (ugh!) */ + p0.QuadPart = ~p0.QuadPart; + p1 = ~p1; + p0.QuadPart += 1; + p1 += (p0.QuadPart == 0); + } + } + + /* Now for the division */ + if (c < 0) { + bSign = !bSign; + } + + + /* This will catch c == 0 and overflow */ + if (uc <= p1) { + return bSign ? (LONGLONG)0x8000000000000000 : + (LONGLONG)0x7FFFFFFFFFFFFFFF; + } + + /* Do the division */ + + /* If the divisor is a DWORD then its simpler */ + ULARGE_INTEGER uliDividend; + ULARGE_INTEGER uliResult; + DWORD dwDivisor = uc; + uliDividend.HighPart = p1; + uliDividend.LowPart = p0.HighPart; + /* NOTE - this routine will take exceptions if + the result does not fit in a DWORD + */ + if (uliDividend.QuadPart >= (DWORDLONG)dwDivisor) { + uliResult.HighPart = EnlargedUnsignedDivide( + uliDividend, + dwDivisor, + &p0.HighPart); + } else { + uliResult.HighPart = 0; + } + uliResult.LowPart = EnlargedUnsignedDivide( + p0, + dwDivisor, + NULL); + return bSign ? -(LONGLONG)uliResult.QuadPart : + (LONGLONG)uliResult.QuadPart; +} diff --git a/Src/Plugins/Input/in_dshow/base/cache.h b/Src/Plugins/Input/in_dshow/base/cache.h new file mode 100644 index 00000000..a2d57524 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/cache.h @@ -0,0 +1,74 @@ +//------------------------------------------------------------------------------ +// File: Cache.h +// +// Desc: DirectShow base classes - efines a non-MFC generic cache class. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +/* This class implements a simple cache. A cache object is instantiated + with the number of items it is to hold. An item is a pointer to an + object derived from CBaseObject (helps reduce memory leaks). The cache + can then have objects added to it and removed from it. The cache size + is fixed at construction time and may therefore run out or be flooded. + If it runs out it returns a NULL pointer, if it fills up it also returns + a NULL pointer instead of a pointer to the object just inserted */ + +/* Making these classes inherit from CBaseObject does nothing for their + functionality but it allows us to check there are no memory leaks */ + +/* WARNING Be very careful when using this class, what it lets you do is + store and retrieve objects so that you can minimise object creation + which in turns improves efficiency. However the object you store is + exactly the same as the object you get back which means that it short + circuits the constructor initialisation phase. This means any class + variables the object has (eg pointers) are highly likely to be invalid. + Therefore ensure you reinitialise the object before using it again */ + + +#ifndef __CACHE__ +#define __CACHE__ + + +class CCache : CBaseObject { + + /* Make copy constructor and assignment operator inaccessible */ + + CCache(const CCache &refCache); + CCache &operator=(const CCache &refCache); + +private: + + /* These are initialised in the constructor. The first variable points to + an array of pointers, each of which points to a CBaseObject derived + object. The m_iCacheSize is the static fixed size for the cache and the + m_iUsed defines the number of places filled with objects at any time. + We fill the array of pointers from the start (ie m_ppObjects[0] first) + and then only add and remove objects from the end position, so in this + respect the array of object pointers should be treated as a stack */ + + CBaseObject **m_ppObjects; + const INT m_iCacheSize; + INT m_iUsed; + +public: + + CCache(__in_opt LPCTSTR pName,INT iItems); + virtual ~CCache(); + + /* Add an item to the cache */ + CBaseObject *AddToCache(__in CBaseObject *pObject); + + /* Remove an item from the cache */ + CBaseObject *RemoveFromCache(); + + /* Delete all the objects held in the cache */ + void RemoveAll(void); + + /* Return the cache size which is set during construction */ + INT GetCacheSize(void) const {return m_iCacheSize;}; +}; + +#endif /* __CACHE__ */ + diff --git a/Src/Plugins/Input/in_dshow/base/checkbmi.h b/Src/Plugins/Input/in_dshow/base/checkbmi.h new file mode 100644 index 00000000..9761daec --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/checkbmi.h @@ -0,0 +1,120 @@ +// Copyright (c) 1992 - 1997 Microsoft Corporation. All Rights Reserved. + +#ifndef _CHECKBMI_H_ +#define _CHECKBMI_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +// Helper +__inline BOOL MultiplyCheckOverflow(DWORD a, DWORD b, __deref_out_range(==, a * b) DWORD *pab) { + *pab = a * b; + if ((a == 0) || (((*pab) / a) == b)) { + return TRUE; + } + return FALSE; +} + + +// Checks if the fields in a BITMAPINFOHEADER won't generate +// overlows and buffer overruns +// This is not a complete check and does not guarantee code using this structure will be secure +// from attack +// Bugs this is guarding against: +// 1. Total structure size calculation overflowing +// 2. biClrUsed > 256 for 8-bit palettized content +// 3. Total bitmap size in bytes overflowing +// 4. biSize < size of the base structure leading to accessessing random memory +// 5. Total structure size exceeding know size of data +// + +__success(return != 0) __inline BOOL ValidateBitmapInfoHeader( + const BITMAPINFOHEADER *pbmi, // pointer to structure to check + __out_range(>=, sizeof(BITMAPINFOHEADER)) DWORD cbSize // size of memory block containing structure +) +{ + DWORD dwWidthInBytes; + DWORD dwBpp; + DWORD dwWidthInBits; + DWORD dwHeight; + DWORD dwSizeImage; + DWORD dwClrUsed; + + // Reject bad parameters - do the size check first to avoid reading bad memory + if (cbSize < sizeof(BITMAPINFOHEADER) || + pbmi->biSize < sizeof(BITMAPINFOHEADER) || + pbmi->biSize > 4096) { + return FALSE; + } + + // Reject 0 size + if (pbmi->biWidth == 0 || pbmi->biHeight == 0) { + return FALSE; + } + + // Use bpp of 200 for validating against further overflows if not set for compressed format + dwBpp = 200; + + if (pbmi->biBitCount > dwBpp) { + return FALSE; + } + + // Strictly speaking abs can overflow so cast explicitly to DWORD + dwHeight = (DWORD)abs(pbmi->biHeight); + + if (!MultiplyCheckOverflow(dwBpp, (DWORD)pbmi->biWidth, &dwWidthInBits)) { + return FALSE; + } + + // Compute correct width in bytes - rounding up to 4 bytes + dwWidthInBytes = (dwWidthInBits / 8 + 3) & ~3; + + if (!MultiplyCheckOverflow(dwWidthInBytes, dwHeight, &dwSizeImage)) { + return FALSE; + } + + // Fail if total size is 0 - this catches indivual quantities being 0 + // Also don't allow huge values > 1GB which might cause arithmetic + // errors for users + if (dwSizeImage > 0x40000000 || + pbmi->biSizeImage > 0x40000000) { + return FALSE; + } + + // Fail if biClrUsed looks bad + if (pbmi->biClrUsed > 256) { + return FALSE; + } + + if (pbmi->biClrUsed == 0 && pbmi->biBitCount <= 8 && pbmi->biBitCount > 0) { + dwClrUsed = (1 << pbmi->biBitCount); + } else { + dwClrUsed = pbmi->biClrUsed; + } + + // Check total size + if (cbSize < pbmi->biSize + dwClrUsed * sizeof(RGBQUAD) + + (pbmi->biCompression == BI_BITFIELDS ? 3 * sizeof(DWORD) : 0)) { + return FALSE; + } + + // If it is RGB validate biSizeImage - lots of code assumes the size is correct + if (pbmi->biCompression == BI_RGB || pbmi->biCompression == BI_BITFIELDS) { + if (pbmi->biSizeImage != 0) { + DWORD dwBits = (DWORD)pbmi->biWidth * (DWORD)pbmi->biBitCount; + DWORD dwWidthInBytes = ((DWORD)((dwBits+31) & (~31)) / 8); + DWORD dwTotalSize = (DWORD)abs(pbmi->biHeight) * dwWidthInBytes; + if (dwTotalSize > pbmi->biSizeImage) { + return FALSE; + } + } + } + return TRUE; +} + +#ifdef __cplusplus +} +#endif + +#endif // _CHECKBMI_H_ diff --git a/Src/Plugins/Input/in_dshow/base/combase.cpp b/Src/Plugins/Input/in_dshow/base/combase.cpp new file mode 100644 index 00000000..9374b177 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/combase.cpp @@ -0,0 +1,265 @@ +//------------------------------------------------------------------------------ +// File: ComBase.cpp +// +// Desc: DirectShow base classes - implements class hierarchy for creating +// COM objects. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> +#pragma warning( disable : 4514 ) // Disable warnings re unused inline functions + + +/* Define the static member variable */ + +LONG CBaseObject::m_cObjects = 0; + + +/* Constructor */ + +CBaseObject::CBaseObject(__in_opt LPCTSTR pName) +{ + /* Increment the number of active objects */ + InterlockedIncrement(&m_cObjects); + +#ifdef DEBUG + +#ifdef UNICODE + m_dwCookie = DbgRegisterObjectCreation(0, pName); +#else + m_dwCookie = DbgRegisterObjectCreation(pName, 0); +#endif + +#endif +} + +#ifdef UNICODE +CBaseObject::CBaseObject(const char *pName) +{ + /* Increment the number of active objects */ + InterlockedIncrement(&m_cObjects); + +#ifdef DEBUG + m_dwCookie = DbgRegisterObjectCreation(pName, 0); +#endif +} +#endif + +HINSTANCE hlibOLEAut32; + +/* Destructor */ + +CBaseObject::~CBaseObject() +{ + /* Decrement the number of objects active */ + if (InterlockedDecrement(&m_cObjects) == 0) { + if (hlibOLEAut32) { + FreeLibrary(hlibOLEAut32); + + hlibOLEAut32 = 0; + } + }; + + +#ifdef DEBUG + DbgRegisterObjectDestruction(m_dwCookie); +#endif +} + +static const TCHAR szOle32Aut[] = TEXT("OleAut32.dll"); + +HINSTANCE LoadOLEAut32() +{ + if (hlibOLEAut32 == 0) { + + hlibOLEAut32 = LoadLibrary(szOle32Aut); + } + + return hlibOLEAut32; +} + + +/* Constructor */ + +// We know we use "this" in the initialization list, we also know we don't modify *phr. +#pragma warning( disable : 4355 4100 ) +CUnknown::CUnknown(__in_opt LPCTSTR pName, __in_opt LPUNKNOWN pUnk) +: CBaseObject(pName) +/* Start the object with a reference count of zero - when the */ +/* object is queried for it's first interface this may be */ +/* incremented depending on whether or not this object is */ +/* currently being aggregated upon */ +, m_cRef(0) +/* Set our pointer to our IUnknown interface. */ +/* If we have an outer, use its, otherwise use ours. */ +/* This pointer effectivly points to the owner of */ +/* this object and can be accessed by the GetOwner() method. */ +, m_pUnknown( pUnk != 0 ? pUnk : reinterpret_cast<LPUNKNOWN>( static_cast<PNDUNKNOWN>(this) ) ) + /* Why the double cast? Well, the inner cast is a type-safe cast */ + /* to pointer to a type from which we inherit. The second is */ + /* type-unsafe but works because INonDelegatingUnknown "behaves */ + /* like" IUnknown. (Only the names on the methods change.) */ +{ + // Everything we need to do has been done in the initializer list +} + +// This does the same as above except it has a useless HRESULT argument +// use the previous constructor, this is just left for compatibility... +CUnknown::CUnknown(__in_opt LPCTSTR pName, __in_opt LPUNKNOWN pUnk, __inout_opt HRESULT *phr) : + CBaseObject(pName), + m_cRef(0), + m_pUnknown( pUnk != 0 ? pUnk : reinterpret_cast<LPUNKNOWN>( static_cast<PNDUNKNOWN>(this) ) ) +{ +} + +#ifdef UNICODE +CUnknown::CUnknown(__in_opt LPCSTR pName, __in_opt LPUNKNOWN pUnk) +: CBaseObject(pName), m_cRef(0), + m_pUnknown( pUnk != 0 ? pUnk : reinterpret_cast<LPUNKNOWN>( static_cast<PNDUNKNOWN>(this) ) ) +{ } + +CUnknown::CUnknown(__in_opt LPCSTR pName, __in_opt LPUNKNOWN pUnk, __inout_opt HRESULT *phr) : + CBaseObject(pName), m_cRef(0), + m_pUnknown( pUnk != 0 ? pUnk : reinterpret_cast<LPUNKNOWN>( static_cast<PNDUNKNOWN>(this) ) ) +{ } + +#endif + +#pragma warning( default : 4355 4100 ) + + +/* QueryInterface */ + +STDMETHODIMP CUnknown::NonDelegatingQueryInterface(REFIID riid, __deref_out void ** ppv) +{ + CheckPointer(ppv,E_POINTER); + ValidateReadWritePtr(ppv,sizeof(PVOID)); + + /* We know only about IUnknown */ + + if (riid == IID_IUnknown) { + GetInterface((LPUNKNOWN) (PNDUNKNOWN) this, ppv); + return NOERROR; + } else { + *ppv = NULL; + return E_NOINTERFACE; + } +} + +/* We have to ensure that we DON'T use a max macro, since these will typically */ +/* lead to one of the parameters being evaluated twice. Since we are worried */ +/* about concurrency, we can't afford to access the m_cRef twice since we can't */ +/* afford to run the risk that its value having changed between accesses. */ + +template<class T> inline static T ourmax( const T & a, const T & b ) +{ + return a > b ? a : b; +} + +/* AddRef */ + +STDMETHODIMP_(ULONG) CUnknown::NonDelegatingAddRef() +{ + LONG lRef = InterlockedIncrement( &m_cRef ); + ASSERT(lRef > 0); + DbgLog((LOG_MEMORY,3,TEXT(" Obj %d ref++ = %d"), + m_dwCookie, m_cRef)); + return ourmax(ULONG(m_cRef), 1ul); +} + + +/* Release */ + +STDMETHODIMP_(ULONG) CUnknown::NonDelegatingRelease() +{ + /* If the reference count drops to zero delete ourselves */ + + LONG lRef = InterlockedDecrement( &m_cRef ); + ASSERT(lRef >= 0); + + DbgLog((LOG_MEMORY,3,TEXT(" Object %d ref-- = %d"), + m_dwCookie, m_cRef)); + if (lRef == 0) { + + // COM rules say we must protect against re-entrancy. + // If we are an aggregator and we hold our own interfaces + // on the aggregatee, the QI for these interfaces will + // addref ourselves. So after doing the QI we must release + // a ref count on ourselves. Then, before releasing the + // private interface, we must addref ourselves. When we do + // this from the destructor here it will result in the ref + // count going to 1 and then back to 0 causing us to + // re-enter the destructor. Hence we add an extra refcount here + // once we know we will delete the object. + // for an example aggregator see filgraph\distrib.cpp. + + m_cRef++; + + delete this; + return ULONG(0); + } else { + // Don't touch m_cRef again even in this leg as the object + // may have just been released on another thread too + return ourmax(ULONG(lRef), 1ul); + } +} + + +/* Return an interface pointer to a requesting client + performing a thread safe AddRef as necessary */ + +STDAPI GetInterface(LPUNKNOWN pUnk, __out void **ppv) +{ + CheckPointer(ppv, E_POINTER); + *ppv = pUnk; + pUnk->AddRef(); + return NOERROR; +} + + +/* Compares two interfaces and returns TRUE if they are on the same object */ + +BOOL WINAPI IsEqualObject(IUnknown *pFirst, IUnknown *pSecond) +{ + /* Different objects can't have the same interface pointer for + any interface + */ + if (pFirst == pSecond) { + return TRUE; + } + /* OK - do it the hard way - check if they have the same + IUnknown pointers - a single object can only have one of these + */ + LPUNKNOWN pUnknown1; // Retrieve the IUnknown interface + LPUNKNOWN pUnknown2; // Retrieve the other IUnknown interface + HRESULT hr; // General OLE return code + + ASSERT(pFirst); + ASSERT(pSecond); + + /* See if the IUnknown pointers match */ + + hr = pFirst->QueryInterface(IID_IUnknown,(void **) &pUnknown1); + if (FAILED(hr)) { + return FALSE; + } + ASSERT(pUnknown1); + + /* Release the extra interface we hold */ + + pUnknown1->Release(); + + hr = pSecond->QueryInterface(IID_IUnknown,(void **) &pUnknown2); + if (FAILED(hr)) { + return FALSE; + } + ASSERT(pUnknown2); + + /* Release the extra interface we hold */ + + pUnknown2->Release(); + return (pUnknown1 == pUnknown2); +} + diff --git a/Src/Plugins/Input/in_dshow/base/combase.h b/Src/Plugins/Input/in_dshow/base/combase.h new file mode 100644 index 00000000..44ca5354 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/combase.h @@ -0,0 +1,305 @@ +//------------------------------------------------------------------------------ +// File: ComBase.h +// +// Desc: DirectShow base classes - defines a class hierarchy for creating +// COM objects. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +/* + +a. Derive your COM object from CUnknown + +b. Make a static CreateInstance function that takes an LPUNKNOWN, an HRESULT * + and a TCHAR *. The LPUNKNOWN defines the object to delegate IUnknown calls + to. The HRESULT * allows error codes to be passed around constructors and + the TCHAR * is a descriptive name that can be printed on the debugger. + + It is important that constructors only change the HRESULT * if they have + to set an ERROR code, if it was successful then leave it alone or you may + overwrite an error code from an object previously created. + + When you call a constructor the descriptive name should be in static store + as we do not copy the string. To stop large amounts of memory being used + in retail builds by all these static strings use the NAME macro, + + CMyFilter = new CImplFilter(NAME("My filter"),pUnknown,phr); + if (FAILED(hr)) { + return hr; + } + + In retail builds NAME(_x_) compiles to NULL, the base CBaseObject class + knows not to do anything with objects that don't have a name. + +c. Have a constructor for your object that passes the LPUNKNOWN, HRESULT * and + TCHAR * to the CUnknown constructor. You can set the HRESULT if you have an + error, or just simply pass it through to the constructor. + + The object creation will fail in the class factory if the HRESULT indicates + an error (ie FAILED(HRESULT) == TRUE) + +d. Create a FactoryTemplate with your object's class id and CreateInstance + function. + +Then (for each interface) either + +Multiple inheritance + +1. Also derive it from ISomeInterface +2. Include DECLARE_IUNKNOWN in your class definition to declare + implementations of QueryInterface, AddRef and Release that + call the outer unknown +3. Override NonDelegatingQueryInterface to expose ISomeInterface by + code something like + + if (riid == IID_ISomeInterface) { + return GetInterface((ISomeInterface *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } + +4. Declare and implement the member functions of ISomeInterface. + +or: Nested interfaces + +1. Declare a class derived from CUnknown +2. Include DECLARE_IUNKNOWN in your class definition +3. Override NonDelegatingQueryInterface to expose ISomeInterface by + code something like + + if (riid == IID_ISomeInterface) { + return GetInterface((ISomeInterface *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } + +4. Implement the member functions of ISomeInterface. Use GetOwner() to + access the COM object class. + +And in your COM object class: + +5. Make the nested class a friend of the COM object class, and declare + an instance of the nested class as a member of the COM object class. + + NOTE that because you must always pass the outer unknown and an hResult + to the CUnknown constructor you cannot use a default constructor, in + other words you will have to make the member variable a pointer to the + class and make a NEW call in your constructor to actually create it. + +6. override the NonDelegatingQueryInterface with code like this: + + if (riid == IID_ISomeInterface) { + return m_pImplFilter-> + NonDelegatingQueryInterface(IID_ISomeInterface, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } + +You can have mixed classes which support some interfaces via multiple +inheritance and some via nested classes + +*/ + +#ifndef __COMBASE__ +#define __COMBASE__ + +// Filter Setup data structures no defined in axextend.idl + +typedef REGPINTYPES +AMOVIESETUP_MEDIATYPE, * PAMOVIESETUP_MEDIATYPE, * FAR LPAMOVIESETUP_MEDIATYPE; + +typedef REGFILTERPINS +AMOVIESETUP_PIN, * PAMOVIESETUP_PIN, * FAR LPAMOVIESETUP_PIN; + +typedef struct _AMOVIESETUP_FILTER +{ + const CLSID * clsID; + const WCHAR * strName; + DWORD dwMerit; + UINT nPins; + const AMOVIESETUP_PIN * lpPin; +} +AMOVIESETUP_FILTER, * PAMOVIESETUP_FILTER, * FAR LPAMOVIESETUP_FILTER; + +/* The DLLENTRY module initialises the module handle on loading */ + +extern HINSTANCE g_hInst; + +/* On DLL load remember which platform we are running on */ + +extern DWORD g_amPlatform; +extern OSVERSIONINFO g_osInfo; // Filled in by GetVersionEx + +/* Version of IUnknown that is renamed to allow a class to support both + non delegating and delegating IUnknowns in the same COM object */ + +#ifndef INONDELEGATINGUNKNOWN_DEFINED +DECLARE_INTERFACE(INonDelegatingUnknown) +{ + STDMETHOD(NonDelegatingQueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG, NonDelegatingAddRef)(THIS) PURE; + STDMETHOD_(ULONG, NonDelegatingRelease)(THIS) PURE; +}; +#define INONDELEGATINGUNKNOWN_DEFINED +#endif + +typedef INonDelegatingUnknown *PNDUNKNOWN; + + +/* This is the base object class that supports active object counting. As + part of the debug facilities we trace every time a C++ object is created + or destroyed. The name of the object has to be passed up through the class + derivation list during construction as you cannot call virtual functions + in the constructor. The downside of all this is that every single object + constructor has to take an object name parameter that describes it */ + +class CBaseObject +{ + +private: + + // Disable the copy constructor and assignment by default so you will get + // compiler errors instead of unexpected behaviour if you pass objects + // by value or assign objects. + CBaseObject(const CBaseObject& objectSrc); // no implementation + void operator=(const CBaseObject& objectSrc); // no implementation + +private: + static LONG m_cObjects; /* Total number of objects active */ + +protected: +#ifdef DEBUG + DWORD m_dwCookie; /* Cookie identifying this object */ +#endif + + +public: + + /* These increment and decrement the number of active objects */ + + CBaseObject(__in_opt LPCTSTR pName); +#ifdef UNICODE + CBaseObject(__in_opt LPCSTR pName); +#endif + ~CBaseObject(); + + /* Call this to find if there are any CUnknown derived objects active */ + + static LONG ObjectsActive() { + return m_cObjects; + }; +}; + + +/* An object that supports one or more COM interfaces will be based on + this class. It supports counting of total objects for DLLCanUnloadNow + support, and an implementation of the core non delegating IUnknown */ + +class AM_NOVTABLE CUnknown : public INonDelegatingUnknown, + public CBaseObject +{ +private: + const LPUNKNOWN m_pUnknown; /* Owner of this object */ + +protected: /* So we can override NonDelegatingRelease() */ + volatile LONG m_cRef; /* Number of reference counts */ + +public: + + CUnknown(__in_opt LPCTSTR pName, __in_opt LPUNKNOWN pUnk); + virtual ~CUnknown() {}; + + // This is redundant, just use the other constructor + // as we never touch the HRESULT in this anyway + CUnknown(__in_opt LPCTSTR Name, __in_opt LPUNKNOWN pUnk, __inout_opt HRESULT *phr); +#ifdef UNICODE + CUnknown(__in_opt LPCSTR pName, __in_opt LPUNKNOWN pUnk); + CUnknown(__in_opt LPCSTR pName, __in_opt LPUNKNOWN pUnk,__inout_opt HRESULT *phr); +#endif + + /* Return the owner of this object */ + + LPUNKNOWN GetOwner() const { + return m_pUnknown; + }; + + /* Called from the class factory to create a new instance, it is + pure virtual so it must be overriden in your derived class */ + + /* static CUnknown *CreateInstance(LPUNKNOWN, HRESULT *) */ + + /* Non delegating unknown implementation */ + + STDMETHODIMP NonDelegatingQueryInterface(REFIID, __deref_out void **); + STDMETHODIMP_(ULONG) NonDelegatingAddRef(); + STDMETHODIMP_(ULONG) NonDelegatingRelease(); +}; + +/* Return an interface pointer to a requesting client + performing a thread safe AddRef as necessary */ + +STDAPI GetInterface(LPUNKNOWN pUnk, __out void **ppv); + +/* A function that can create a new COM object */ + +typedef CUnknown *(CALLBACK *LPFNNewCOMObject)(__in_opt LPUNKNOWN pUnkOuter, __inout_opt HRESULT *phr); + +/* A function (can be NULL) which is called from the DLL entrypoint + routine for each factory template: + + bLoading - TRUE on DLL load, FALSE on DLL unload + rclsid - the m_ClsID of the entry +*/ +typedef void (CALLBACK *LPFNInitRoutine)(BOOL bLoading, const CLSID *rclsid); + +/* Create one of these per object class in an array so that + the default class factory code can create new instances */ + +class CFactoryTemplate { + +public: + + const WCHAR * m_Name; + const CLSID * m_ClsID; + LPFNNewCOMObject m_lpfnNew; + LPFNInitRoutine m_lpfnInit; + const AMOVIESETUP_FILTER * m_pAMovieSetup_Filter; + + BOOL IsClassID(REFCLSID rclsid) const { + return (IsEqualCLSID(*m_ClsID,rclsid)); + }; + + CUnknown *CreateInstance(__inout_opt LPUNKNOWN pUnk, __inout_opt HRESULT *phr) const { + CheckPointer(phr,NULL); + return m_lpfnNew(pUnk, phr); + }; +}; + + +/* You must override the (pure virtual) NonDelegatingQueryInterface to return + interface pointers (using GetInterface) to the interfaces your derived + class supports (the default implementation only supports IUnknown) */ + +#define DECLARE_IUNKNOWN \ + STDMETHODIMP QueryInterface(REFIID riid, __deref_out void **ppv) { \ + return GetOwner()->QueryInterface(riid,ppv); \ + }; \ + STDMETHODIMP_(ULONG) AddRef() { \ + return GetOwner()->AddRef(); \ + }; \ + STDMETHODIMP_(ULONG) Release() { \ + return GetOwner()->Release(); \ + }; + + + +HINSTANCE LoadOLEAut32(); + + +#endif /* __COMBASE__ */ + + + + diff --git a/Src/Plugins/Input/in_dshow/base/cprop.cpp b/Src/Plugins/Input/in_dshow/base/cprop.cpp new file mode 100644 index 00000000..a60f2cfa --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/cprop.cpp @@ -0,0 +1,383 @@ +//------------------------------------------------------------------------------ +// File: CProp.cpp +// +// Desc: DirectShow base classes - implements CBasePropertyPage class. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> + +// Constructor for the base property page class. As described in the header +// file we must be initialised with dialog and title resource identifiers. +// The class supports IPropertyPage and overrides AddRef and Release calls +// to keep track of the reference counts. When the last count is released +// we call SetPageSite(NULL) and SetObjects(0,NULL) to release interfaces +// previously obtained by the property page when it had SetObjects called + +CBasePropertyPage::CBasePropertyPage(__in_opt LPCTSTR pName, // Debug only name + __inout_opt LPUNKNOWN pUnk, // COM Delegator + int DialogId, // Resource ID + int TitleId) : // To get tital + CUnknown(pName,pUnk), + m_DialogId(DialogId), + m_TitleId(TitleId), + m_hwnd(NULL), + m_Dlg(NULL), + m_pPageSite(NULL), + m_bObjectSet(FALSE), + m_bDirty(FALSE) +{ +} + +#ifdef UNICODE +CBasePropertyPage::CBasePropertyPage(__in_opt LPCSTR pName, // Debug only name + __inout_opt LPUNKNOWN pUnk, // COM Delegator + int DialogId, // Resource ID + int TitleId) : // To get tital + CUnknown(pName,pUnk), + m_DialogId(DialogId), + m_TitleId(TitleId), + m_hwnd(NULL), + m_Dlg(NULL), + m_pPageSite(NULL), + m_bObjectSet(FALSE), + m_bDirty(FALSE) +{ +} +#endif + +// Increment our reference count + +STDMETHODIMP_(ULONG) CBasePropertyPage::NonDelegatingAddRef() +{ + LONG lRef = InterlockedIncrement(&m_cRef); + ASSERT(lRef > 0); + return max(ULONG(m_cRef),1ul); +} + + +// Release a reference count and protect against reentrancy + +STDMETHODIMP_(ULONG) CBasePropertyPage::NonDelegatingRelease() +{ + // If the reference count drops to zero delete ourselves + + LONG lRef = InterlockedDecrement(&m_cRef); + if (lRef == 0) { + m_cRef++; + SetPageSite(NULL); + SetObjects(0,NULL); + delete this; + return ULONG(0); + } else { + // Don't touch m_cRef again here! + return max(ULONG(lRef),1ul); + } +} + + +// Expose our IPropertyPage interface + +STDMETHODIMP +CBasePropertyPage::NonDelegatingQueryInterface(REFIID riid,__deref_out void **ppv) +{ + if (riid == IID_IPropertyPage) { + return GetInterface((IPropertyPage *)this,ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid,ppv); + } +} + + +// Get the page info so that the page site can size itself + +STDMETHODIMP CBasePropertyPage::GetPageInfo(__out LPPROPPAGEINFO pPageInfo) +{ + CheckPointer(pPageInfo,E_POINTER); + WCHAR wszTitle[STR_MAX_LENGTH]; + WideStringFromResource(wszTitle,m_TitleId); + + // Allocate dynamic memory for the property page title + + LPOLESTR pszTitle; + HRESULT hr = AMGetWideString(wszTitle, &pszTitle); + if (FAILED(hr)) { + NOTE("No caption memory"); + return hr; + } + + pPageInfo->cb = sizeof(PROPPAGEINFO); + pPageInfo->pszTitle = pszTitle; + pPageInfo->pszDocString = NULL; + pPageInfo->pszHelpFile = NULL; + pPageInfo->dwHelpContext = 0; + + // Set defaults in case GetDialogSize fails + pPageInfo->size.cx = 340; + pPageInfo->size.cy = 150; + + GetDialogSize(m_DialogId, DialogProc,0L,&pPageInfo->size); + return NOERROR; +} + + +// Handles the messages for our property window + +INT_PTR CALLBACK CBasePropertyPage::DialogProc(HWND hwnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam) +{ + CBasePropertyPage *pPropertyPage; + + switch (uMsg) { + + case WM_INITDIALOG: + + _SetWindowLongPtr(hwnd, DWLP_USER, lParam); + + // This pointer may be NULL when calculating size + + pPropertyPage = (CBasePropertyPage *) lParam; + if (pPropertyPage == NULL) { + return (LRESULT) 1; + } + pPropertyPage->m_Dlg = hwnd; + } + + // This pointer may be NULL when calculating size + + pPropertyPage = _GetWindowLongPtr<CBasePropertyPage*>(hwnd, DWLP_USER); + if (pPropertyPage == NULL) { + return (LRESULT) 1; + } + return pPropertyPage->OnReceiveMessage(hwnd,uMsg,wParam,lParam); +} + + +// Tells us the object that should be informed of the property changes + +STDMETHODIMP CBasePropertyPage::SetObjects(ULONG cObjects,__in_ecount_opt(cObjects) LPUNKNOWN *ppUnk) +{ + if (cObjects == 1) { + + if ((ppUnk == NULL) || (*ppUnk == NULL)) { + return E_POINTER; + } + + // Set a flag to say that we have set the Object + m_bObjectSet = TRUE ; + return OnConnect(*ppUnk); + + } else if (cObjects == 0) { + + // Set a flag to say that we have not set the Object for the page + m_bObjectSet = FALSE ; + return OnDisconnect(); + } + + DbgBreak("No support for more than one object"); + return E_UNEXPECTED; +} + + +// Create the window we will use to edit properties + +STDMETHODIMP CBasePropertyPage::Activate(HWND hwndParent, + LPCRECT pRect, + BOOL fModal) +{ + CheckPointer(pRect,E_POINTER); + + // Return failure if SetObject has not been called. + if (m_bObjectSet == FALSE) { + return E_UNEXPECTED; + } + + if (m_hwnd) { + return E_UNEXPECTED; + } + + m_hwnd = CreateDialogParam(g_hInst, + MAKEINTRESOURCE(m_DialogId), + hwndParent, + DialogProc, + (LPARAM) this); + if (m_hwnd == NULL) { + return E_OUTOFMEMORY; + } + + OnActivate(); + Move(pRect); + return Show(SW_SHOWNORMAL); +} + + +// Set the position of the property page + +STDMETHODIMP CBasePropertyPage::Move(LPCRECT pRect) +{ + CheckPointer(pRect,E_POINTER); + + if (m_hwnd == NULL) { + return E_UNEXPECTED; + } + + MoveWindow(m_hwnd, // Property page handle + pRect->left, // x coordinate + pRect->top, // y coordinate + WIDTH(pRect), // Overall window width + HEIGHT(pRect), // And likewise height + TRUE); // Should we repaint it + + return NOERROR; +} + + +// Display the property dialog + +STDMETHODIMP CBasePropertyPage::Show(UINT nCmdShow) +{ + // Have we been activated yet + + if (m_hwnd == NULL) { + return E_UNEXPECTED; + } + + // Ignore wrong show flags + + if ((nCmdShow != SW_SHOW) && (nCmdShow != SW_SHOWNORMAL) && (nCmdShow != SW_HIDE)) { + return E_INVALIDARG; + } + + ShowWindow(m_hwnd,nCmdShow); + InvalidateRect(m_hwnd,NULL,TRUE); + return NOERROR; +} + + +// Destroy the property page dialog + +STDMETHODIMP CBasePropertyPage::Deactivate(void) +{ + if (m_hwnd == NULL) { + return E_UNEXPECTED; + } + + // Remove WS_EX_CONTROLPARENT before DestroyWindow call + + DWORD dwStyle = GetWindowLong(m_hwnd, GWL_EXSTYLE); + dwStyle = dwStyle & (~WS_EX_CONTROLPARENT); + + // Set m_hwnd to be NULL temporarily so the message handler + // for WM_STYLECHANGING doesn't add the WS_EX_CONTROLPARENT + // style back in + HWND hwnd = m_hwnd; + m_hwnd = NULL; + SetWindowLong(hwnd, GWL_EXSTYLE, dwStyle); + m_hwnd = hwnd; + + OnDeactivate(); + + // Destroy the dialog window + + DestroyWindow(m_hwnd); + m_hwnd = NULL; + return NOERROR; +} + + +// Tells the application property page site + +STDMETHODIMP CBasePropertyPage::SetPageSite(__in_opt LPPROPERTYPAGESITE pPageSite) +{ + if (pPageSite) { + + if (m_pPageSite) { + return E_UNEXPECTED; + } + + m_pPageSite = pPageSite; + m_pPageSite->AddRef(); + + } else { + + if (m_pPageSite == NULL) { + return E_UNEXPECTED; + } + + m_pPageSite->Release(); + m_pPageSite = NULL; + } + return NOERROR; +} + + +// Apply any changes so far made + +STDMETHODIMP CBasePropertyPage::Apply() +{ + // In ActiveMovie 1.0 we used to check whether we had been activated or + // not. This is too constrictive. Apply should be allowed as long as + // SetObject was called to set an object. So we will no longer check to + // see if we have been activated (ie., m_hWnd != NULL), but instead + // make sure that m_bObjectSet is TRUE (ie., SetObject has been called). + + if (m_bObjectSet == FALSE) { + return E_UNEXPECTED; + } + + // Must have had a site set + + if (m_pPageSite == NULL) { + return E_UNEXPECTED; + } + + // Has anything changed + + if (m_bDirty == FALSE) { + return NOERROR; + } + + // Commit derived class changes + + HRESULT hr = OnApplyChanges(); + if (SUCCEEDED(hr)) { + m_bDirty = FALSE; + } + return hr; +} + + +// Base class definition for message handling + +INT_PTR CBasePropertyPage::OnReceiveMessage(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam) +{ + // we would like the TAB key to move around the tab stops in our property + // page, but for some reason OleCreatePropertyFrame clears the CONTROLPARENT + // style behind our back, so we need to switch it back on now behind its + // back. Otherwise the tab key will be useless in every page. + // + + CBasePropertyPage *pPropertyPage; + { + pPropertyPage = _GetWindowLongPtr<CBasePropertyPage*>(hwnd, DWLP_USER); + + if (pPropertyPage->m_hwnd == NULL) { + return 0; + } + switch (uMsg) { + case WM_STYLECHANGING: + if (wParam == GWL_EXSTYLE) { + LPSTYLESTRUCT lpss = (LPSTYLESTRUCT)lParam; + lpss->styleNew |= WS_EX_CONTROLPARENT; + return 0; + } + } + } + + return DefWindowProc(hwnd,uMsg,wParam,lParam); +} + diff --git a/Src/Plugins/Input/in_dshow/base/cprop.h b/Src/Plugins/Input/in_dshow/base/cprop.h new file mode 100644 index 00000000..a030f8ff --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/cprop.h @@ -0,0 +1,95 @@ +//------------------------------------------------------------------------------ +// File: CProp.h +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __CPROP__ +#define __CPROP__ + +// Base property page class. Filters typically expose custom properties by +// implementing special control interfaces, examples are IDirectDrawVideo +// and IQualProp on renderers. This allows property pages to be built that +// use the given interface. Applications such as the ActiveMovie OCX query +// filters for the property pages they support and expose them to the user +// +// This class provides all the framework for a property page. A property +// page is a COM object that supports IPropertyPage. We should be created +// with a resource ID for the dialog which we will load when required. We +// should also be given in the constructor a resource ID for a title string +// we will load from the DLLs STRINGTABLE. The property page titles must be +// stored in resource files so that they can be easily internationalised +// +// We have a number of virtual methods (not PURE) that may be overriden in +// derived classes to query for interfaces and so on. These functions have +// simple implementations here that just return NOERROR. Derived classes +// will almost definately have to override the message handler method called +// OnReceiveMessage. We have a static dialog procedure that calls the method +// so that derived classes don't have to fiddle around with the this pointer + +class AM_NOVTABLE CBasePropertyPage : public IPropertyPage, public CUnknown +{ +protected: + + LPPROPERTYPAGESITE m_pPageSite; // Details for our property site + HWND m_hwnd; // Window handle for the page + HWND m_Dlg; // Actual dialog window handle + BOOL m_bDirty; // Has anything been changed + int m_TitleId; // Resource identifier for title + int m_DialogId; // Dialog resource identifier + + static INT_PTR CALLBACK DialogProc(HWND hwnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam); + +private: + BOOL m_bObjectSet ; // SetObject has been called or not. +public: + + CBasePropertyPage(__in_opt LPCTSTR pName, // Debug only name + __inout_opt LPUNKNOWN pUnk, // COM Delegator + int DialogId, // Resource ID + int TitleId); // To get tital + +#ifdef UNICODE + CBasePropertyPage(__in_opt LPCSTR pName, + __inout_opt LPUNKNOWN pUnk, + int DialogId, + int TitleId); +#endif + virtual ~CBasePropertyPage() { }; + DECLARE_IUNKNOWN + + // Override these virtual methods + + virtual HRESULT OnConnect(IUnknown *pUnknown) { return NOERROR; }; + virtual HRESULT OnDisconnect() { return NOERROR; }; + virtual HRESULT OnActivate() { return NOERROR; }; + virtual HRESULT OnDeactivate() { return NOERROR; }; + virtual HRESULT OnApplyChanges() { return NOERROR; }; + virtual INT_PTR OnReceiveMessage(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam); + + // These implement an IPropertyPage interface + + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv); + STDMETHODIMP_(ULONG) NonDelegatingRelease(); + STDMETHODIMP_(ULONG) NonDelegatingAddRef(); + STDMETHODIMP SetPageSite(__in_opt LPPROPERTYPAGESITE pPageSite); + STDMETHODIMP Activate(HWND hwndParent, LPCRECT prect,BOOL fModal); + STDMETHODIMP Deactivate(void); + STDMETHODIMP GetPageInfo(__out LPPROPPAGEINFO pPageInfo); + STDMETHODIMP SetObjects(ULONG cObjects, __in_ecount_opt(cObjects) LPUNKNOWN *ppUnk); + STDMETHODIMP Show(UINT nCmdShow); + STDMETHODIMP Move(LPCRECT prect); + STDMETHODIMP IsPageDirty(void) { return m_bDirty ? S_OK : S_FALSE; } + STDMETHODIMP Apply(void); + STDMETHODIMP Help(LPCWSTR lpszHelpDir) { return E_NOTIMPL; } + STDMETHODIMP TranslateAccelerator(__inout LPMSG lpMsg) { return E_NOTIMPL; } +}; + +#endif // __CPROP__ + diff --git a/Src/Plugins/Input/in_dshow/base/ctlutil.cpp b/Src/Plugins/Input/in_dshow/base/ctlutil.cpp new file mode 100644 index 00000000..a76fc9bf --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/ctlutil.cpp @@ -0,0 +1,2541 @@ +//------------------------------------------------------------------------------ +// File: CtlUtil.cpp +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// Base classes implementing IDispatch parsing for the basic control dual +// interfaces. Derive from these and implement just the custom method and +// property methods. We also implement CPosPassThru that can be used by +// renderers and transforms to pass by IMediaPosition and IMediaSeeking + + +#include <streams.h> +#include <limits.h> +#include "seekpt.h" + +// 'bool' non standard reserved word +#pragma warning(disable:4237) + + +// --- CBaseDispatch implementation ---------- +CBaseDispatch::~CBaseDispatch() +{ + if (m_pti) { + m_pti->Release(); + } +} + + +// return 1 if we support GetTypeInfo + +STDMETHODIMP +CBaseDispatch::GetTypeInfoCount(__out UINT * pctinfo) +{ + CheckPointer(pctinfo,E_POINTER); + ValidateReadWritePtr(pctinfo,sizeof(UINT *)); + *pctinfo = 1; + return S_OK; +} + + +typedef HRESULT (STDAPICALLTYPE *LPLOADTYPELIB)( + const OLECHAR FAR *szFile, + __deref_out ITypeLib FAR* FAR* pptlib); + +typedef HRESULT (STDAPICALLTYPE *LPLOADREGTYPELIB)(REFGUID rguid, + WORD wVerMajor, + WORD wVerMinor, + LCID lcid, + __deref_out ITypeLib FAR* FAR* pptlib); + +// attempt to find our type library + +STDMETHODIMP +CBaseDispatch::GetTypeInfo( + REFIID riid, + UINT itinfo, + LCID lcid, + __deref_out ITypeInfo ** pptinfo) +{ + CheckPointer(pptinfo,E_POINTER); + ValidateReadWritePtr(pptinfo,sizeof(ITypeInfo *)); + HRESULT hr; + + *pptinfo = NULL; + + // we only support one type element + if (0 != itinfo) { + return TYPE_E_ELEMENTNOTFOUND; + } + + if (NULL == pptinfo) { + return E_POINTER; + } + + // always look for neutral + if (NULL == m_pti) { + + LPLOADTYPELIB lpfnLoadTypeLib; + LPLOADREGTYPELIB lpfnLoadRegTypeLib; + ITypeLib *ptlib; + HINSTANCE hInst; + + static const char szTypeLib[] = "LoadTypeLib"; + static const char szRegTypeLib[] = "LoadRegTypeLib"; + static const WCHAR szControl[] = L"control.tlb"; + + // + // Try to get the Ole32Aut.dll module handle. + // + + hInst = LoadOLEAut32(); + if (hInst == NULL) { + DWORD dwError = GetLastError(); + return AmHresultFromWin32(dwError); + } + lpfnLoadRegTypeLib = (LPLOADREGTYPELIB)GetProcAddress(hInst, + szRegTypeLib); + if (lpfnLoadRegTypeLib == NULL) { + DWORD dwError = GetLastError(); + return AmHresultFromWin32(dwError); + } + + hr = (*lpfnLoadRegTypeLib)(LIBID_QuartzTypeLib, 1, 0, // version 1.0 + lcid, &ptlib); + + if (FAILED(hr)) { + + // attempt to load directly - this will fill the + // registry in if it finds it + + lpfnLoadTypeLib = (LPLOADTYPELIB)GetProcAddress(hInst, szTypeLib); + if (lpfnLoadTypeLib == NULL) { + DWORD dwError = GetLastError(); + return AmHresultFromWin32(dwError); + } + + hr = (*lpfnLoadTypeLib)(szControl, &ptlib); + if (FAILED(hr)) { + return hr; + } + } + + hr = ptlib->GetTypeInfoOfGuid( + riid, + &m_pti); + + ptlib->Release(); + + if (FAILED(hr)) { + return hr; + } + } + + *pptinfo = m_pti; + m_pti->AddRef(); + return S_OK; +} + + +STDMETHODIMP +CBaseDispatch::GetIDsOfNames( + REFIID riid, + __in_ecount(cNames) LPOLESTR * rgszNames, + UINT cNames, + LCID lcid, + __out_ecount(cNames) DISPID * rgdispid) +{ + // although the IDispatch riid is dead, we use this to pass from + // the interface implementation class to us the iid we are talking about. + + ITypeInfo * pti; + HRESULT hr = GetTypeInfo(riid, 0, lcid, &pti); + + if (SUCCEEDED(hr)) { + hr = pti->GetIDsOfNames(rgszNames, cNames, rgdispid); + + pti->Release(); + } + return hr; +} + + +// --- CMediaControl implementation --------- + +CMediaControl::CMediaControl(const TCHAR * name,LPUNKNOWN pUnk) : + CUnknown(name, pUnk) +{ +} + +// expose our interfaces IMediaControl and IUnknown + +STDMETHODIMP +CMediaControl::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + ValidateReadWritePtr(ppv,sizeof(PVOID)); + if (riid == IID_IMediaControl) { + return GetInterface( (IMediaControl *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } +} + + +// return 1 if we support GetTypeInfo + +STDMETHODIMP +CMediaControl::GetTypeInfoCount(__out UINT * pctinfo) +{ + return m_basedisp.GetTypeInfoCount(pctinfo); +} + + +// attempt to find our type library + +STDMETHODIMP +CMediaControl::GetTypeInfo( + UINT itinfo, + LCID lcid, + __deref_out ITypeInfo ** pptinfo) +{ + return m_basedisp.GetTypeInfo( + IID_IMediaControl, + itinfo, + lcid, + pptinfo); +} + + +STDMETHODIMP +CMediaControl::GetIDsOfNames( + REFIID riid, + __in_ecount(cNames) LPOLESTR * rgszNames, + UINT cNames, + LCID lcid, + __out_ecount(cNames) DISPID * rgdispid) +{ + return m_basedisp.GetIDsOfNames( + IID_IMediaControl, + rgszNames, + cNames, + lcid, + rgdispid); +} + + +STDMETHODIMP +CMediaControl::Invoke( + DISPID dispidMember, + REFIID riid, + LCID lcid, + WORD wFlags, + __in DISPPARAMS * pdispparams, + __out_opt VARIANT * pvarResult, + __out_opt EXCEPINFO * pexcepinfo, + __out_opt UINT * puArgErr) +{ + // this parameter is a dead leftover from an earlier interface + if (IID_NULL != riid) { + return DISP_E_UNKNOWNINTERFACE; + } + + ITypeInfo * pti; + HRESULT hr = GetTypeInfo(0, lcid, &pti); + + if (FAILED(hr)) { + return hr; + } + + hr = pti->Invoke( + (IMediaControl *)this, + dispidMember, + wFlags, + pdispparams, + pvarResult, + pexcepinfo, + puArgErr); + + pti->Release(); + return hr; +} + + +// --- CMediaEvent implementation ---------- + + +CMediaEvent::CMediaEvent(__in_opt LPCTSTR name,__in_opt LPUNKNOWN pUnk) : + CUnknown(name, pUnk) +{ +} + + +// expose our interfaces IMediaEvent and IUnknown + +STDMETHODIMP +CMediaEvent::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + ValidateReadWritePtr(ppv,sizeof(PVOID)); + if (riid == IID_IMediaEvent || riid == IID_IMediaEventEx) { + return GetInterface( (IMediaEventEx *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } +} + + +// return 1 if we support GetTypeInfo + +STDMETHODIMP +CMediaEvent::GetTypeInfoCount(__out UINT * pctinfo) +{ + return m_basedisp.GetTypeInfoCount(pctinfo); +} + + +// attempt to find our type library + +STDMETHODIMP +CMediaEvent::GetTypeInfo( + UINT itinfo, + LCID lcid, + __deref_out ITypeInfo ** pptinfo) +{ + return m_basedisp.GetTypeInfo( + IID_IMediaEvent, + itinfo, + lcid, + pptinfo); +} + + +STDMETHODIMP +CMediaEvent::GetIDsOfNames( + REFIID riid, + __in_ecount(cNames) LPOLESTR * rgszNames, + UINT cNames, + LCID lcid, + __out_ecount(cNames) DISPID * rgdispid) +{ + return m_basedisp.GetIDsOfNames( + IID_IMediaEvent, + rgszNames, + cNames, + lcid, + rgdispid); +} + + +STDMETHODIMP +CMediaEvent::Invoke( + DISPID dispidMember, + REFIID riid, + LCID lcid, + WORD wFlags, + __in DISPPARAMS * pdispparams, + __out_opt VARIANT * pvarResult, + __out_opt EXCEPINFO * pexcepinfo, + __out_opt UINT * puArgErr) +{ + // this parameter is a dead leftover from an earlier interface + if (IID_NULL != riid) { + return DISP_E_UNKNOWNINTERFACE; + } + + ITypeInfo * pti; + HRESULT hr = GetTypeInfo(0, lcid, &pti); + + if (FAILED(hr)) { + return hr; + } + + hr = pti->Invoke( + (IMediaEvent *)this, + dispidMember, + wFlags, + pdispparams, + pvarResult, + pexcepinfo, + puArgErr); + + pti->Release(); + return hr; +} + + +// --- CMediaPosition implementation ---------- + + +CMediaPosition::CMediaPosition(__in_opt LPCTSTR name,__in_opt LPUNKNOWN pUnk) : + CUnknown(name, pUnk) +{ +} + +CMediaPosition::CMediaPosition(__in_opt LPCTSTR name, + __in_opt LPUNKNOWN pUnk, + __inout HRESULT * phr) : + CUnknown(name, pUnk) +{ + UNREFERENCED_PARAMETER(phr); +} + + +// expose our interfaces IMediaPosition and IUnknown + +STDMETHODIMP +CMediaPosition::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + ValidateReadWritePtr(ppv,sizeof(PVOID)); + if (riid == IID_IMediaPosition) { + return GetInterface( (IMediaPosition *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } +} + + +// return 1 if we support GetTypeInfo + +STDMETHODIMP +CMediaPosition::GetTypeInfoCount(__out UINT * pctinfo) +{ + return m_basedisp.GetTypeInfoCount(pctinfo); +} + + +// attempt to find our type library + +STDMETHODIMP +CMediaPosition::GetTypeInfo( + UINT itinfo, + LCID lcid, + __deref_out ITypeInfo ** pptinfo) +{ + return m_basedisp.GetTypeInfo( + IID_IMediaPosition, + itinfo, + lcid, + pptinfo); +} + + +STDMETHODIMP +CMediaPosition::GetIDsOfNames( + REFIID riid, + __in_ecount(cNames) LPOLESTR * rgszNames, + UINT cNames, + LCID lcid, + __out_ecount(cNames) DISPID * rgdispid) +{ + return m_basedisp.GetIDsOfNames( + IID_IMediaPosition, + rgszNames, + cNames, + lcid, + rgdispid); +} + + +STDMETHODIMP +CMediaPosition::Invoke( + DISPID dispidMember, + REFIID riid, + LCID lcid, + WORD wFlags, + __in DISPPARAMS * pdispparams, + __out_opt VARIANT * pvarResult, + __out_opt EXCEPINFO * pexcepinfo, + __out_opt UINT * puArgErr) +{ + // this parameter is a dead leftover from an earlier interface + if (IID_NULL != riid) { + return DISP_E_UNKNOWNINTERFACE; + } + + ITypeInfo * pti; + HRESULT hr = GetTypeInfo(0, lcid, &pti); + + if (FAILED(hr)) { + return hr; + } + + hr = pti->Invoke( + (IMediaPosition *)this, + dispidMember, + wFlags, + pdispparams, + pvarResult, + pexcepinfo, + puArgErr); + + pti->Release(); + return hr; +} + + +// --- IMediaPosition and IMediaSeeking pass through class ---------- + + +CPosPassThru::CPosPassThru(__in_opt LPCTSTR pName, + __in_opt LPUNKNOWN pUnk, + __inout HRESULT *phr, + IPin *pPin) : + CMediaPosition(pName,pUnk), + m_pPin(pPin) +{ + if (pPin == NULL) { + *phr = E_POINTER; + return; + } +} + + +// Expose our IMediaSeeking and IMediaPosition interfaces + +STDMETHODIMP +CPosPassThru::NonDelegatingQueryInterface(REFIID riid,__deref_out void **ppv) +{ + CheckPointer(ppv,E_POINTER); + *ppv = NULL; + + if (riid == IID_IMediaSeeking) { + return GetInterface( static_cast<IMediaSeeking *>(this), ppv); + } + return CMediaPosition::NonDelegatingQueryInterface(riid,ppv); +} + + +// Return the IMediaPosition interface from our peer + +HRESULT +CPosPassThru::GetPeer(IMediaPosition ** ppMP) +{ + *ppMP = NULL; + + IPin *pConnected; + HRESULT hr = m_pPin->ConnectedTo(&pConnected); + if (FAILED(hr)) { + return E_NOTIMPL; + } + IMediaPosition * pMP; + hr = pConnected->QueryInterface(IID_IMediaPosition, (void **) &pMP); + pConnected->Release(); + if (FAILED(hr)) { + return E_NOTIMPL; + } + + *ppMP = pMP; + return S_OK; +} + + +// Return the IMediaSeeking interface from our peer + +HRESULT +CPosPassThru::GetPeerSeeking(__deref_out IMediaSeeking ** ppMS) +{ + *ppMS = NULL; + + IPin *pConnected; + HRESULT hr = m_pPin->ConnectedTo(&pConnected); + if (FAILED(hr)) { + return E_NOTIMPL; + } + IMediaSeeking * pMS; + hr = pConnected->QueryInterface(IID_IMediaSeeking, (void **) &pMS); + pConnected->Release(); + if (FAILED(hr)) { + return E_NOTIMPL; + } + + *ppMS = pMS; + return S_OK; +} + + +// --- IMediaSeeking methods ---------- + + +STDMETHODIMP +CPosPassThru::GetCapabilities(__out DWORD * pCaps) +{ + IMediaSeeking* pMS; + HRESULT hr = GetPeerSeeking(&pMS); + if (FAILED(hr)) { + return hr; + } + + hr = pMS->GetCapabilities(pCaps); + pMS->Release(); + return hr; +} + +STDMETHODIMP +CPosPassThru::CheckCapabilities(__inout DWORD * pCaps) +{ + IMediaSeeking* pMS; + HRESULT hr = GetPeerSeeking(&pMS); + if (FAILED(hr)) { + return hr; + } + + hr = pMS->CheckCapabilities(pCaps); + pMS->Release(); + return hr; +} + +STDMETHODIMP +CPosPassThru::IsFormatSupported(const GUID * pFormat) +{ + IMediaSeeking* pMS; + HRESULT hr = GetPeerSeeking(&pMS); + if (FAILED(hr)) { + return hr; + } + + hr = pMS->IsFormatSupported(pFormat); + pMS->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::QueryPreferredFormat(__out GUID *pFormat) +{ + IMediaSeeking* pMS; + HRESULT hr = GetPeerSeeking(&pMS); + if (FAILED(hr)) { + return hr; + } + + hr = pMS->QueryPreferredFormat(pFormat); + pMS->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::SetTimeFormat(const GUID * pFormat) +{ + IMediaSeeking* pMS; + HRESULT hr = GetPeerSeeking(&pMS); + if (FAILED(hr)) { + return hr; + } + + hr = pMS->SetTimeFormat(pFormat); + pMS->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::GetTimeFormat(__out GUID *pFormat) +{ + IMediaSeeking* pMS; + HRESULT hr = GetPeerSeeking(&pMS); + if (FAILED(hr)) { + return hr; + } + + hr = pMS->GetTimeFormat(pFormat); + pMS->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::IsUsingTimeFormat(const GUID * pFormat) +{ + IMediaSeeking* pMS; + HRESULT hr = GetPeerSeeking(&pMS); + if (FAILED(hr)) { + return hr; + } + + hr = pMS->IsUsingTimeFormat(pFormat); + pMS->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::ConvertTimeFormat(__out LONGLONG * pTarget, + __in_opt const GUID * pTargetFormat, + LONGLONG Source, + __in_opt const GUID * pSourceFormat ) +{ + IMediaSeeking* pMS; + HRESULT hr = GetPeerSeeking(&pMS); + if (FAILED(hr)) { + return hr; + } + + hr = pMS->ConvertTimeFormat(pTarget, pTargetFormat, Source, pSourceFormat ); + pMS->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::SetPositions( __inout_opt LONGLONG * pCurrent, + DWORD CurrentFlags, + __inout_opt LONGLONG * pStop, + DWORD StopFlags ) +{ + IMediaSeeking* pMS; + HRESULT hr = GetPeerSeeking(&pMS); + if (FAILED(hr)) { + return hr; + } + + hr = pMS->SetPositions(pCurrent, CurrentFlags, pStop, StopFlags ); + pMS->Release(); + return hr; +} + +STDMETHODIMP +CPosPassThru::GetPositions(__out_opt LONGLONG *pCurrent, __out_opt LONGLONG * pStop) +{ + IMediaSeeking* pMS; + HRESULT hr = GetPeerSeeking(&pMS); + if (FAILED(hr)) { + return hr; + } + + hr = pMS->GetPositions(pCurrent,pStop); + pMS->Release(); + return hr; +} + +HRESULT +CPosPassThru::GetSeekingLongLong +( HRESULT (__stdcall IMediaSeeking::*pMethod)( __out LONGLONG * ) +, LONGLONG * pll +) +{ + IMediaSeeking* pMS; + HRESULT hr = GetPeerSeeking(&pMS); + if (SUCCEEDED(hr)) + { + hr = (pMS->*pMethod)(pll); + pMS->Release(); + } + return hr; +} + +// If we don't have a current position then ask upstream + +STDMETHODIMP +CPosPassThru::GetCurrentPosition(__out LONGLONG *pCurrent) +{ + // Can we report the current position + HRESULT hr = GetMediaTime(pCurrent,NULL); + if (SUCCEEDED(hr)) hr = NOERROR; + else hr = GetSeekingLongLong( &IMediaSeeking::GetCurrentPosition, pCurrent ); + return hr; +} + + +STDMETHODIMP +CPosPassThru::GetStopPosition(__out LONGLONG *pStop) +{ + return GetSeekingLongLong( &IMediaSeeking::GetStopPosition, pStop );; +} + +STDMETHODIMP +CPosPassThru::GetDuration(__out LONGLONG *pDuration) +{ + return GetSeekingLongLong( &IMediaSeeking::GetDuration, pDuration );; +} + + +STDMETHODIMP +CPosPassThru::GetPreroll(__out LONGLONG *pllPreroll) +{ + return GetSeekingLongLong( &IMediaSeeking::GetPreroll, pllPreroll );; +} + + +STDMETHODIMP +CPosPassThru::GetAvailable( __out_opt LONGLONG *pEarliest, __out_opt LONGLONG *pLatest ) +{ + IMediaSeeking* pMS; + HRESULT hr = GetPeerSeeking(&pMS); + if (FAILED(hr)) { + return hr; + } + + hr = pMS->GetAvailable( pEarliest, pLatest ); + pMS->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::GetRate(__out double * pdRate) +{ + IMediaSeeking* pMS; + HRESULT hr = GetPeerSeeking(&pMS); + if (FAILED(hr)) { + return hr; + } + hr = pMS->GetRate(pdRate); + pMS->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::SetRate(double dRate) +{ + if (0.0 == dRate) { + return E_INVALIDARG; + } + + IMediaSeeking* pMS; + HRESULT hr = GetPeerSeeking(&pMS); + if (FAILED(hr)) { + return hr; + } + hr = pMS->SetRate(dRate); + pMS->Release(); + return hr; +} + + + + +// --- IMediaPosition methods ---------- + + +STDMETHODIMP +CPosPassThru::get_Duration(__out REFTIME * plength) +{ + IMediaPosition* pMP; + HRESULT hr = GetPeer(&pMP); + if (FAILED(hr)) { + return hr; + } + + hr = pMP->get_Duration(plength); + pMP->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::get_CurrentPosition(__out REFTIME * pllTime) +{ + IMediaPosition* pMP; + HRESULT hr = GetPeer(&pMP); + if (FAILED(hr)) { + return hr; + } + hr = pMP->get_CurrentPosition(pllTime); + pMP->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::put_CurrentPosition(REFTIME llTime) +{ + IMediaPosition* pMP; + HRESULT hr = GetPeer(&pMP); + if (FAILED(hr)) { + return hr; + } + hr = pMP->put_CurrentPosition(llTime); + pMP->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::get_StopTime(__out REFTIME * pllTime) +{ + IMediaPosition* pMP; + HRESULT hr = GetPeer(&pMP); + if (FAILED(hr)) { + return hr; + } + hr = pMP->get_StopTime(pllTime); + pMP->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::put_StopTime(REFTIME llTime) +{ + IMediaPosition* pMP; + HRESULT hr = GetPeer(&pMP); + if (FAILED(hr)) { + return hr; + } + hr = pMP->put_StopTime(llTime); + pMP->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::get_PrerollTime(__out REFTIME * pllTime) +{ + IMediaPosition* pMP; + HRESULT hr = GetPeer(&pMP); + if (FAILED(hr)) { + return hr; + } + hr = pMP->get_PrerollTime(pllTime); + pMP->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::put_PrerollTime(REFTIME llTime) +{ + IMediaPosition* pMP; + HRESULT hr = GetPeer(&pMP); + if (FAILED(hr)) { + return hr; + } + hr = pMP->put_PrerollTime(llTime); + pMP->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::get_Rate(__out double * pdRate) +{ + IMediaPosition* pMP; + HRESULT hr = GetPeer(&pMP); + if (FAILED(hr)) { + return hr; + } + hr = pMP->get_Rate(pdRate); + pMP->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::put_Rate(double dRate) +{ + if (0.0 == dRate) { + return E_INVALIDARG; + } + + IMediaPosition* pMP; + HRESULT hr = GetPeer(&pMP); + if (FAILED(hr)) { + return hr; + } + hr = pMP->put_Rate(dRate); + pMP->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::CanSeekForward(__out LONG *pCanSeekForward) +{ + IMediaPosition* pMP; + HRESULT hr = GetPeer(&pMP); + if (FAILED(hr)) { + return hr; + } + hr = pMP->CanSeekForward(pCanSeekForward); + pMP->Release(); + return hr; +} + + +STDMETHODIMP +CPosPassThru::CanSeekBackward(__out LONG *pCanSeekBackward) +{ + IMediaPosition* pMP; + HRESULT hr = GetPeer(&pMP); + if (FAILED(hr)) { + return hr; + } + hr = pMP->CanSeekBackward(pCanSeekBackward); + pMP->Release(); + return hr; +} + + +// --- Implements the CRendererPosPassThru class ---------- + + +// Media times (eg current frame, field, sample etc) are passed through the +// filtergraph in media samples. When a renderer gets a sample with media +// times in it, it will call one of the RegisterMediaTime methods we expose +// (one takes an IMediaSample, the other takes the media times direct). We +// store the media times internally and return them in GetCurrentPosition. + +CRendererPosPassThru::CRendererPosPassThru(__in_opt LPCTSTR pName, + __in_opt LPUNKNOWN pUnk, + __inout HRESULT *phr, + IPin *pPin) : + CPosPassThru(pName,pUnk,phr,pPin), + m_StartMedia(0), + m_EndMedia(0), + m_bReset(TRUE) +{ +} + + +// Sets the media times the object should report + +HRESULT +CRendererPosPassThru::RegisterMediaTime(IMediaSample *pMediaSample) +{ + ASSERT(pMediaSample); + LONGLONG StartMedia; + LONGLONG EndMedia; + + CAutoLock cAutoLock(&m_PositionLock); + + // Get the media times from the sample + + HRESULT hr = pMediaSample->GetTime(&StartMedia,&EndMedia); + if (FAILED(hr)) + { + ASSERT(hr == VFW_E_SAMPLE_TIME_NOT_SET); + return hr; + } + + m_StartMedia = StartMedia; + m_EndMedia = EndMedia; + m_bReset = FALSE; + return NOERROR; +} + + +// Sets the media times the object should report + +HRESULT +CRendererPosPassThru::RegisterMediaTime(LONGLONG StartTime,LONGLONG EndTime) +{ + CAutoLock cAutoLock(&m_PositionLock); + m_StartMedia = StartTime; + m_EndMedia = EndTime; + m_bReset = FALSE; + return NOERROR; +} + + +// Return the current media times registered in the object + +HRESULT +CRendererPosPassThru::GetMediaTime(__out LONGLONG *pStartTime, __out_opt LONGLONG *pEndTime) +{ + ASSERT(pStartTime); + + CAutoLock cAutoLock(&m_PositionLock); + if (m_bReset == TRUE) { + return E_FAIL; + } + + // We don't have to return the end time + + HRESULT hr = ConvertTimeFormat( pStartTime, 0, m_StartMedia, &TIME_FORMAT_MEDIA_TIME ); + if (pEndTime && SUCCEEDED(hr)) { + hr = ConvertTimeFormat( pEndTime, 0, m_EndMedia, &TIME_FORMAT_MEDIA_TIME ); + } + return hr; +} + + +// Resets the media times we hold + +HRESULT +CRendererPosPassThru::ResetMediaTime() +{ + CAutoLock cAutoLock(&m_PositionLock); + m_StartMedia = 0; + m_EndMedia = 0; + m_bReset = TRUE; + return NOERROR; +} + +// Intended to be called by the owing filter during EOS processing so +// that the media times can be adjusted to the stop time. This ensures +// that the GetCurrentPosition will actully get to the stop position. +HRESULT +CRendererPosPassThru::EOS() +{ + HRESULT hr; + + if ( m_bReset == TRUE ) hr = E_FAIL; + else + { + LONGLONG llStop; + if SUCCEEDED(hr=GetStopPosition(&llStop)) + { + CAutoLock cAutoLock(&m_PositionLock); + m_StartMedia = + m_EndMedia = llStop; + } + } + return hr; +} + +// -- CSourceSeeking implementation ------------ + +CSourceSeeking::CSourceSeeking( + __in_opt LPCTSTR pName, + __in_opt LPUNKNOWN pUnk, + __inout HRESULT* phr, + __in CCritSec * pLock) : + CUnknown(pName, pUnk), + m_pLock(pLock), + m_rtStart((long)0) +{ + m_rtStop = _I64_MAX / 2; + m_rtDuration = m_rtStop; + m_dRateSeeking = 1.0; + + m_dwSeekingCaps = AM_SEEKING_CanSeekForwards + | AM_SEEKING_CanSeekBackwards + | AM_SEEKING_CanSeekAbsolute + | AM_SEEKING_CanGetStopPos + | AM_SEEKING_CanGetDuration; +} + +HRESULT CSourceSeeking::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + if(riid == IID_IMediaSeeking) { + CheckPointer(ppv, E_POINTER); + return GetInterface(static_cast<IMediaSeeking *>(this), ppv); + } + else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } +} + + +HRESULT CSourceSeeking::IsFormatSupported(const GUID * pFormat) +{ + CheckPointer(pFormat, E_POINTER); + // only seeking in time (REFERENCE_TIME units) is supported + return *pFormat == TIME_FORMAT_MEDIA_TIME ? S_OK : S_FALSE; +} + +HRESULT CSourceSeeking::QueryPreferredFormat(__out GUID *pFormat) +{ + CheckPointer(pFormat, E_POINTER); + *pFormat = TIME_FORMAT_MEDIA_TIME; + return S_OK; +} + +HRESULT CSourceSeeking::SetTimeFormat(const GUID * pFormat) +{ + CheckPointer(pFormat, E_POINTER); + + // nothing to set; just check that it's TIME_FORMAT_TIME + return *pFormat == TIME_FORMAT_MEDIA_TIME ? S_OK : E_INVALIDARG; +} + +HRESULT CSourceSeeking::IsUsingTimeFormat(const GUID * pFormat) +{ + CheckPointer(pFormat, E_POINTER); + return *pFormat == TIME_FORMAT_MEDIA_TIME ? S_OK : S_FALSE; +} + +HRESULT CSourceSeeking::GetTimeFormat(__out GUID *pFormat) +{ + CheckPointer(pFormat, E_POINTER); + *pFormat = TIME_FORMAT_MEDIA_TIME; + return S_OK; +} + +HRESULT CSourceSeeking::GetDuration(__out LONGLONG *pDuration) +{ + CheckPointer(pDuration, E_POINTER); + CAutoLock lock(m_pLock); + *pDuration = m_rtDuration; + return S_OK; +} + +HRESULT CSourceSeeking::GetStopPosition(__out LONGLONG *pStop) +{ + CheckPointer(pStop, E_POINTER); + CAutoLock lock(m_pLock); + *pStop = m_rtStop; + return S_OK; +} + +HRESULT CSourceSeeking::GetCurrentPosition(__out LONGLONG *pCurrent) +{ + // GetCurrentPosition is typically supported only in renderers and + // not in source filters. + return E_NOTIMPL; +} + +HRESULT CSourceSeeking::GetCapabilities( __out DWORD * pCapabilities ) +{ + CheckPointer(pCapabilities, E_POINTER); + *pCapabilities = m_dwSeekingCaps; + return S_OK; +} + +HRESULT CSourceSeeking::CheckCapabilities( __inout DWORD * pCapabilities ) +{ + CheckPointer(pCapabilities, E_POINTER); + + // make sure all requested capabilities are in our mask + return (~m_dwSeekingCaps & *pCapabilities) ? S_FALSE : S_OK; +} + +HRESULT CSourceSeeking::ConvertTimeFormat( __out LONGLONG * pTarget, + __in_opt const GUID * pTargetFormat, + LONGLONG Source, + __in_opt const GUID * pSourceFormat ) +{ + CheckPointer(pTarget, E_POINTER); + // format guids can be null to indicate current format + + // since we only support TIME_FORMAT_MEDIA_TIME, we don't really + // offer any conversions. + if(pTargetFormat == 0 || *pTargetFormat == TIME_FORMAT_MEDIA_TIME) + { + if(pSourceFormat == 0 || *pSourceFormat == TIME_FORMAT_MEDIA_TIME) + { + *pTarget = Source; + return S_OK; + } + } + + return E_INVALIDARG; +} + + +HRESULT CSourceSeeking::SetPositions( __inout_opt LONGLONG * pCurrent, + DWORD CurrentFlags, + __inout_opt LONGLONG * pStop, + DWORD StopFlags ) +{ + DWORD StopPosBits = StopFlags & AM_SEEKING_PositioningBitsMask; + DWORD StartPosBits = CurrentFlags & AM_SEEKING_PositioningBitsMask; + + if(StopFlags) { + CheckPointer(pStop, E_POINTER); + + // accept only relative, incremental, or absolute positioning + if(StopPosBits != StopFlags) { + return E_INVALIDARG; + } + } + + if(CurrentFlags) { + CheckPointer(pCurrent, E_POINTER); + if(StartPosBits != AM_SEEKING_AbsolutePositioning && + StartPosBits != AM_SEEKING_RelativePositioning) { + return E_INVALIDARG; + } + } + + + // scope for autolock + { + CAutoLock lock(m_pLock); + + // set start position + if(StartPosBits == AM_SEEKING_AbsolutePositioning) + { + m_rtStart = *pCurrent; + } + else if(StartPosBits == AM_SEEKING_RelativePositioning) + { + m_rtStart += *pCurrent; + } + + // set stop position + if(StopPosBits == AM_SEEKING_AbsolutePositioning) + { + m_rtStop = *pStop; + } + else if(StopPosBits == AM_SEEKING_IncrementalPositioning) + { + m_rtStop = m_rtStart + *pStop; + } + else if(StopPosBits == AM_SEEKING_RelativePositioning) + { + m_rtStop = m_rtStop + *pStop; + } + } + + + HRESULT hr = S_OK; + if(SUCCEEDED(hr) && StopPosBits) { + hr = ChangeStop(); + } + if(StartPosBits) { + hr = ChangeStart(); + } + + return hr; +} + + +HRESULT CSourceSeeking::GetPositions( __out_opt LONGLONG * pCurrent, __out_opt LONGLONG * pStop ) +{ + if(pCurrent) { + *pCurrent = m_rtStart; + } + if(pStop) { + *pStop = m_rtStop; + } + + return S_OK;; +} + + +HRESULT CSourceSeeking::GetAvailable( __out_opt LONGLONG * pEarliest, __out_opt LONGLONG * pLatest ) +{ + if(pEarliest) { + *pEarliest = 0; + } + if(pLatest) { + CAutoLock lock(m_pLock); + *pLatest = m_rtDuration; + } + return S_OK; +} + +HRESULT CSourceSeeking::SetRate( double dRate) +{ + { + CAutoLock lock(m_pLock); + m_dRateSeeking = dRate; + } + return ChangeRate(); +} + +HRESULT CSourceSeeking::GetRate( __out double * pdRate) +{ + CheckPointer(pdRate, E_POINTER); + CAutoLock lock(m_pLock); + *pdRate = m_dRateSeeking; + return S_OK; +} + +HRESULT CSourceSeeking::GetPreroll(__out LONGLONG *pPreroll) +{ + CheckPointer(pPreroll, E_POINTER); + *pPreroll = 0; + return S_OK; +} + + + + + +// --- CSourcePosition implementation ---------- + + +CSourcePosition::CSourcePosition(__in_opt LPCTSTR pName, + __in_opt LPUNKNOWN pUnk, + __inout HRESULT* phr, + __in CCritSec * pLock) : + CMediaPosition(pName, pUnk), + m_pLock(pLock), + m_Start(CRefTime((LONGLONG)0)) +{ + m_Stop = _I64_MAX; + m_Rate = 1.0; +} + + +STDMETHODIMP +CSourcePosition::get_Duration(__out REFTIME * plength) +{ + CheckPointer(plength,E_POINTER); + ValidateReadWritePtr(plength,sizeof(REFTIME)); + CAutoLock lock(m_pLock); + + *plength = m_Duration; + return S_OK; +} + + +STDMETHODIMP +CSourcePosition::put_CurrentPosition(REFTIME llTime) +{ + m_pLock->Lock(); + m_Start = llTime; + m_pLock->Unlock(); + + return ChangeStart(); +} + + +STDMETHODIMP +CSourcePosition::get_StopTime(__out REFTIME * pllTime) +{ + CheckPointer(pllTime,E_POINTER); + ValidateReadWritePtr(pllTime,sizeof(REFTIME)); + CAutoLock lock(m_pLock); + + *pllTime = m_Stop; + return S_OK; +} + + +STDMETHODIMP +CSourcePosition::put_StopTime(REFTIME llTime) +{ + m_pLock->Lock(); + m_Stop = llTime; + m_pLock->Unlock(); + + return ChangeStop(); +} + + +STDMETHODIMP +CSourcePosition::get_PrerollTime(__out REFTIME * pllTime) +{ + CheckPointer(pllTime,E_POINTER); + ValidateReadWritePtr(pllTime,sizeof(REFTIME)); + return E_NOTIMPL; +} + + +STDMETHODIMP +CSourcePosition::put_PrerollTime(REFTIME llTime) +{ + return E_NOTIMPL; +} + + +STDMETHODIMP +CSourcePosition::get_Rate(__out double * pdRate) +{ + CheckPointer(pdRate,E_POINTER); + ValidateReadWritePtr(pdRate,sizeof(double)); + CAutoLock lock(m_pLock); + + *pdRate = m_Rate; + return S_OK; +} + + +STDMETHODIMP +CSourcePosition::put_Rate(double dRate) +{ + m_pLock->Lock(); + m_Rate = dRate; + m_pLock->Unlock(); + + return ChangeRate(); +} + + +// By default we can seek forwards + +STDMETHODIMP +CSourcePosition::CanSeekForward(__out LONG *pCanSeekForward) +{ + CheckPointer(pCanSeekForward,E_POINTER); + *pCanSeekForward = OATRUE; + return S_OK; +} + + +// By default we can seek backwards + +STDMETHODIMP +CSourcePosition::CanSeekBackward(__out LONG *pCanSeekBackward) +{ + CheckPointer(pCanSeekBackward,E_POINTER); + *pCanSeekBackward = OATRUE; + return S_OK; +} + + +// --- Implementation of CBasicAudio class ---------- + + +CBasicAudio::CBasicAudio(__in_opt LPCTSTR pName,__in_opt LPUNKNOWN punk) : + CUnknown(pName, punk) +{ +} + +// overriden to publicise our interfaces + +STDMETHODIMP +CBasicAudio::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + ValidateReadWritePtr(ppv,sizeof(PVOID)); + if (riid == IID_IBasicAudio) { + return GetInterface( (IBasicAudio *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } +} + + +STDMETHODIMP +CBasicAudio::GetTypeInfoCount(__out UINT * pctinfo) +{ + return m_basedisp.GetTypeInfoCount(pctinfo); +} + + +STDMETHODIMP +CBasicAudio::GetTypeInfo( + UINT itinfo, + LCID lcid, + __deref_out ITypeInfo ** pptinfo) +{ + return m_basedisp.GetTypeInfo( + IID_IBasicAudio, + itinfo, + lcid, + pptinfo); +} + + +STDMETHODIMP +CBasicAudio::GetIDsOfNames( + REFIID riid, + __in_ecount(cNames) LPOLESTR * rgszNames, + UINT cNames, + LCID lcid, + __out_ecount(cNames) DISPID * rgdispid) +{ + return m_basedisp.GetIDsOfNames( + IID_IBasicAudio, + rgszNames, + cNames, + lcid, + rgdispid); +} + + +STDMETHODIMP +CBasicAudio::Invoke( + DISPID dispidMember, + REFIID riid, + LCID lcid, + WORD wFlags, + __in DISPPARAMS * pdispparams, + __out_opt VARIANT * pvarResult, + __out_opt EXCEPINFO * pexcepinfo, + __out_opt UINT * puArgErr) +{ + // this parameter is a dead leftover from an earlier interface + if (IID_NULL != riid) { + return DISP_E_UNKNOWNINTERFACE; + } + + ITypeInfo * pti; + HRESULT hr = GetTypeInfo(0, lcid, &pti); + + if (FAILED(hr)) { + return hr; + } + + hr = pti->Invoke( + (IBasicAudio *)this, + dispidMember, + wFlags, + pdispparams, + pvarResult, + pexcepinfo, + puArgErr); + + pti->Release(); + return hr; +} + + +// --- IVideoWindow implementation ---------- + +CBaseVideoWindow::CBaseVideoWindow(__in_opt LPCTSTR pName,__in_opt LPUNKNOWN punk) : + CUnknown(pName, punk) +{ +} + + +// overriden to publicise our interfaces + +STDMETHODIMP +CBaseVideoWindow::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + ValidateReadWritePtr(ppv,sizeof(PVOID)); + if (riid == IID_IVideoWindow) { + return GetInterface( (IVideoWindow *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } +} + + +STDMETHODIMP +CBaseVideoWindow::GetTypeInfoCount(__out UINT * pctinfo) +{ + return m_basedisp.GetTypeInfoCount(pctinfo); +} + + +STDMETHODIMP +CBaseVideoWindow::GetTypeInfo( + UINT itinfo, + LCID lcid, + __deref_out ITypeInfo ** pptinfo) +{ + return m_basedisp.GetTypeInfo( + IID_IVideoWindow, + itinfo, + lcid, + pptinfo); +} + + +STDMETHODIMP +CBaseVideoWindow::GetIDsOfNames( + REFIID riid, + __in_ecount(cNames) LPOLESTR * rgszNames, + UINT cNames, + LCID lcid, + __out_ecount(cNames) DISPID * rgdispid) +{ + return m_basedisp.GetIDsOfNames( + IID_IVideoWindow, + rgszNames, + cNames, + lcid, + rgdispid); +} + + +STDMETHODIMP +CBaseVideoWindow::Invoke( + DISPID dispidMember, + REFIID riid, + LCID lcid, + WORD wFlags, + __in DISPPARAMS * pdispparams, + __out_opt VARIANT * pvarResult, + __out_opt EXCEPINFO * pexcepinfo, + __out_opt UINT * puArgErr) +{ + // this parameter is a dead leftover from an earlier interface + if (IID_NULL != riid) { + return DISP_E_UNKNOWNINTERFACE; + } + + ITypeInfo * pti; + HRESULT hr = GetTypeInfo(0, lcid, &pti); + + if (FAILED(hr)) { + return hr; + } + + hr = pti->Invoke( + (IVideoWindow *)this, + dispidMember, + wFlags, + pdispparams, + pvarResult, + pexcepinfo, + puArgErr); + + pti->Release(); + return hr; +} + + +// --- IBasicVideo implementation ---------- + + +CBaseBasicVideo::CBaseBasicVideo(__in_opt LPCTSTR pName,__in_opt LPUNKNOWN punk) : + CUnknown(pName, punk) +{ +} + + +// overriden to publicise our interfaces + +STDMETHODIMP +CBaseBasicVideo::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + ValidateReadWritePtr(ppv,sizeof(PVOID)); + if (riid == IID_IBasicVideo || riid == IID_IBasicVideo2) { + return GetInterface( static_cast<IBasicVideo2 *>(this), ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } +} + + +STDMETHODIMP +CBaseBasicVideo::GetTypeInfoCount(__out UINT * pctinfo) +{ + return m_basedisp.GetTypeInfoCount(pctinfo); +} + + +STDMETHODIMP +CBaseBasicVideo::GetTypeInfo( + UINT itinfo, + LCID lcid, + __deref_out ITypeInfo ** pptinfo) +{ + return m_basedisp.GetTypeInfo( + IID_IBasicVideo, + itinfo, + lcid, + pptinfo); +} + + +STDMETHODIMP +CBaseBasicVideo::GetIDsOfNames( + REFIID riid, + __in_ecount(cNames) LPOLESTR * rgszNames, + UINT cNames, + LCID lcid, + __out_ecount(cNames) DISPID * rgdispid) +{ + return m_basedisp.GetIDsOfNames( + IID_IBasicVideo, + rgszNames, + cNames, + lcid, + rgdispid); +} + + +STDMETHODIMP +CBaseBasicVideo::Invoke( + DISPID dispidMember, + REFIID riid, + LCID lcid, + WORD wFlags, + __in DISPPARAMS * pdispparams, + __out_opt VARIANT * pvarResult, + __out_opt EXCEPINFO * pexcepinfo, + __out_opt UINT * puArgErr) +{ + // this parameter is a dead leftover from an earlier interface + if (IID_NULL != riid) { + return DISP_E_UNKNOWNINTERFACE; + } + + ITypeInfo * pti; + HRESULT hr = GetTypeInfo(0, lcid, &pti); + + if (FAILED(hr)) { + return hr; + } + + hr = pti->Invoke( + (IBasicVideo *)this, + dispidMember, + wFlags, + pdispparams, + pvarResult, + pexcepinfo, + puArgErr); + + pti->Release(); + return hr; +} + + +// --- Implementation of Deferred Commands ---------- + + +CDispParams::CDispParams(UINT nArgs, __in_ecount(nArgs) VARIANT* pArgs, __inout_opt HRESULT *phr) +{ + cNamedArgs = 0; + rgdispidNamedArgs = NULL; + cArgs = nArgs; + + if (cArgs) { + rgvarg = new VARIANT[cArgs]; + if (NULL == rgvarg) { + cArgs = 0; + if (phr) { + *phr = E_OUTOFMEMORY; + } + return; + } + + for (UINT i = 0; i < cArgs; i++) { + + // Why aren't we using VariantCopy? + + VARIANT * pDest = &rgvarg[i]; + VARIANT * pSrc = &pArgs[i]; + + pDest->vt = pSrc->vt; + switch(pDest->vt) { + + case VT_I4: + pDest->lVal = pSrc->lVal; + break; + + case VT_UI1: + pDest->bVal = pSrc->bVal; + break; + + case VT_I2: + pDest->iVal = pSrc->iVal; + break; + + case VT_R4: + pDest->fltVal = pSrc->fltVal; + break; + + case VT_R8: + pDest->dblVal = pSrc->dblVal; + break; + + case VT_BOOL: + pDest->boolVal = pSrc->boolVal; + break; + + case VT_ERROR: + pDest->scode = pSrc->scode; + break; + + case VT_CY: + pDest->cyVal = pSrc->cyVal; + break; + + case VT_DATE: + pDest->date = pSrc->date; + break; + + case VT_BSTR: + if ((PVOID)pSrc->bstrVal == NULL) { + pDest->bstrVal = NULL; + } else { + + // a BSTR is a WORD followed by a UNICODE string. + // the pointer points just after the WORD + + WORD len = * (WORD*) (pSrc->bstrVal - (sizeof(WORD) / sizeof(OLECHAR))); + OLECHAR* pch = new OLECHAR[len + (sizeof(WORD)/sizeof(OLECHAR))]; + if (pch) { + WORD *pui = (WORD*)pch; + *pui = len; + pDest->bstrVal = pch + (sizeof(WORD)/sizeof(OLECHAR)); + CopyMemory(pDest->bstrVal, pSrc->bstrVal, len*sizeof(OLECHAR)); + } else { + cArgs = i; + if (phr) { + *phr = E_OUTOFMEMORY; + } + } + } + break; + + case VT_UNKNOWN: + pDest->punkVal = pSrc->punkVal; + pDest->punkVal->AddRef(); + break; + + case VT_DISPATCH: + pDest->pdispVal = pSrc->pdispVal; + pDest->pdispVal->AddRef(); + break; + + default: + // a type we haven't got round to adding yet! + ASSERT(0); + break; + } + } + + } else { + rgvarg = NULL; + } + +} + + +CDispParams::~CDispParams() +{ + for (UINT i = 0; i < cArgs; i++) { + switch(rgvarg[i].vt) { + case VT_BSTR: + // Explicitly cast BSTR to PVOID to tell code scanning tools we really mean to test the pointer + if ((PVOID)rgvarg[i].bstrVal != NULL) { + OLECHAR * pch = rgvarg[i].bstrVal - (sizeof(WORD)/sizeof(OLECHAR)); + delete pch; + } + break; + + case VT_UNKNOWN: + rgvarg[i].punkVal->Release(); + break; + + case VT_DISPATCH: + rgvarg[i].pdispVal->Release(); + break; + } + } + delete[] rgvarg; +} + + +// lifetime is controlled by refcounts (see defer.h) + +CDeferredCommand::CDeferredCommand( + __inout CCmdQueue * pQ, + __in_opt LPUNKNOWN pUnk, + __inout HRESULT * phr, + __in LPUNKNOWN pUnkExecutor, + REFTIME time, + __in GUID* iid, + long dispidMethod, + short wFlags, + long nArgs, + __in_ecount(nArgs) VARIANT* pDispParams, + __out VARIANT* pvarResult, + __out short* puArgErr, + BOOL bStream + ) : + CUnknown(NAME("DeferredCommand"), pUnk), + m_pQueue(pQ), + m_pUnk(pUnkExecutor), + m_iid(iid), + m_dispidMethod(dispidMethod), + m_wFlags(wFlags), + m_DispParams(nArgs, pDispParams, phr), + m_pvarResult(pvarResult), + m_bStream(bStream), + m_hrResult(E_ABORT) + +{ + // convert REFTIME to REFERENCE_TIME + COARefTime convertor(time); + m_time = convertor; + + // no check of time validity - it's ok to queue a command that's + // already late + + // check iid is supportable on pUnk by QueryInterface for it + IUnknown * pInterface; + HRESULT hr = m_pUnk->QueryInterface(GetIID(), (void**) &pInterface); + if (FAILED(hr)) { + *phr = hr; + return; + } + pInterface->Release(); + + + // !!! check dispidMethod and param/return types using typelib + ITypeInfo *pti; + hr = m_Dispatch.GetTypeInfo(*iid, 0, 0, &pti); + if (FAILED(hr)) { + *phr = hr; + return; + } + // !!! some sort of ITypeInfo validity check here + pti->Release(); + + + // Fix up the dispid for put and get + if (wFlags == DISPATCH_PROPERTYPUT) { + m_DispParams.cNamedArgs = 1; + m_DispId = DISPID_PROPERTYPUT; + m_DispParams.rgdispidNamedArgs = &m_DispId; + } + + // all checks ok - add to queue + hr = pQ->Insert(this); + if (FAILED(hr)) { + *phr = hr; + } +} + + +// refcounts are held by caller of InvokeAt... and by list. So if +// we get here, we can't be on the list + +#if 0 +CDeferredCommand::~CDeferredCommand() +{ + // this assert is invalid since if the queue is deleted while we are + // still on the queue, we will have been removed by the queue and this + // m_pQueue will not have been modified. + // ASSERT(m_pQueue == NULL); + + // we don't hold a ref count on pUnk, which is the object that should + // execute the command. + // This is because there would otherwise be a circular refcount problem + // since pUnk probably owns the CmdQueue object that has a refcount + // on us. + // The lifetime of pUnk is guaranteed by it being part of, or lifetime + // controlled by, our parent object. As long as we are on the list, pUnk + // must be valid. Once we are off the list, we do not use pUnk. + +} +#endif + + +// overriden to publicise our interfaces + +STDMETHODIMP +CDeferredCommand::NonDelegatingQueryInterface(REFIID riid, __out void **ppv) +{ + ValidateReadWritePtr(ppv,sizeof(PVOID)); + if (riid == IID_IDeferredCommand) { + return GetInterface( (IDeferredCommand *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } +} + + +// remove from q. this will reduce the refcount by one (since the q +// holds a count) but can't make us go away since he must have a +// refcount in order to call this method. + +STDMETHODIMP +CDeferredCommand::Cancel() +{ + if (m_pQueue == NULL) { + return VFW_E_ALREADY_CANCELLED; + } + + HRESULT hr = m_pQueue->Remove(this); + if (FAILED(hr)) { + return hr; + } + + m_pQueue = NULL; + return S_OK; +} + + +STDMETHODIMP +CDeferredCommand::Confidence(__out LONG* pConfidence) +{ + return E_NOTIMPL; +} + + +STDMETHODIMP +CDeferredCommand::GetHResult(__out HRESULT * phrResult) +{ + CheckPointer(phrResult,E_POINTER); + ValidateReadWritePtr(phrResult,sizeof(HRESULT)); + + if (m_pQueue != NULL) { + return E_ABORT; + } + *phrResult = m_hrResult; + return S_OK; +} + + +// set the time to be a new time (checking that it is valid) and +// then requeue + +STDMETHODIMP +CDeferredCommand::Postpone(REFTIME newtime) +{ + + // check that this time is not past + // convert REFTIME to REFERENCE_TIME + COARefTime convertor(newtime); + + // check that the time has not passed + if (m_pQueue->CheckTime(convertor, IsStreamTime())) { + return VFW_E_TIME_ALREADY_PASSED; + } + + // extract from list + HRESULT hr = m_pQueue->Remove(this); + if (FAILED(hr)) { + return hr; + } + + // change time + m_time = convertor; + + // requeue + hr = m_pQueue->Insert(this); + + return hr; +} + + +HRESULT +CDeferredCommand::Invoke() +{ + // check that we are still outstanding + if (m_pQueue == NULL) { + return VFW_E_ALREADY_CANCELLED; + } + + // get the type info + ITypeInfo* pti; + HRESULT hr = m_Dispatch.GetTypeInfo(GetIID(), 0, 0, &pti); + if (FAILED(hr)) { + return hr; + } + + // qi for the expected interface and then invoke it. Note that we have to + // treat the returned interface as IUnknown since we don't know its type. + IUnknown* pInterface; + + hr = m_pUnk->QueryInterface(GetIID(), (void**) &pInterface); + if (FAILED(hr)) { + pti->Release(); + return hr; + } + + EXCEPINFO expinfo; + UINT uArgErr; + m_hrResult = pti->Invoke( + pInterface, + GetMethod(), + GetFlags(), + GetParams(), + GetResult(), + &expinfo, + &uArgErr); + + // release the interface we QI'd for + pInterface->Release(); + pti->Release(); + + + // remove from list whether or not successful + // or we loop indefinitely + hr = m_pQueue->Remove(this); + m_pQueue = NULL; + return hr; +} + + + +// --- CCmdQueue methods ---------- + + +CCmdQueue::CCmdQueue(__inout_opt HRESULT *phr) : + m_listPresentation(NAME("Presentation time command list")), + m_listStream(NAME("Stream time command list")), + m_evDue(TRUE, phr), // manual reset + m_dwAdvise(0), + m_pClock(NULL), + m_bRunning(FALSE) +{ +} + + +CCmdQueue::~CCmdQueue() +{ + // empty all our lists + + // we hold a refcount on each, so traverse and Release each + // entry then RemoveAll to empty the list + POSITION pos = m_listPresentation.GetHeadPosition(); + + while(pos) { + CDeferredCommand* pCmd = m_listPresentation.GetNext(pos); + pCmd->Release(); + } + m_listPresentation.RemoveAll(); + + pos = m_listStream.GetHeadPosition(); + + while(pos) { + CDeferredCommand* pCmd = m_listStream.GetNext(pos); + pCmd->Release(); + } + m_listStream.RemoveAll(); + + if (m_pClock) { + if (m_dwAdvise) { + m_pClock->Unadvise(m_dwAdvise); + m_dwAdvise = 0; + } + m_pClock->Release(); + } +} + + +// returns a new CDeferredCommand object that will be initialised with +// the parameters and will be added to the queue during construction. +// returns S_OK if successfully created otherwise an error and +// no object has been queued. + +HRESULT +CCmdQueue::New( + __out CDeferredCommand **ppCmd, + __in LPUNKNOWN pUnk, // this object will execute command + REFTIME time, + __in GUID* iid, + long dispidMethod, + short wFlags, + long cArgs, + __in_ecount(cArgs) VARIANT* pDispParams, + __out VARIANT* pvarResult, + __out short* puArgErr, + BOOL bStream +) +{ + CAutoLock lock(&m_Lock); + + HRESULT hr = S_OK; + *ppCmd = NULL; + + CDeferredCommand* pCmd; + pCmd = new CDeferredCommand( + this, + NULL, // not aggregated + &hr, + pUnk, // this guy will execute + time, + iid, + dispidMethod, + wFlags, + cArgs, + pDispParams, + pvarResult, + puArgErr, + bStream); + + if (pCmd == NULL) { + hr = E_OUTOFMEMORY; + } else { + *ppCmd = pCmd; + } + return hr; +} + + +HRESULT +CCmdQueue::Insert(__in CDeferredCommand* pCmd) +{ + CAutoLock lock(&m_Lock); + + // addref the item + pCmd->AddRef(); + + CGenericList<CDeferredCommand> * pList; + if (pCmd->IsStreamTime()) { + pList = &m_listStream; + } else { + pList = &m_listPresentation; + } + POSITION pos = pList->GetHeadPosition(); + + // seek past all items that are before us + while (pos && + (pList->GetValid(pos)->GetTime() <= pCmd->GetTime())) { + + pList->GetNext(pos); + } + + // now at end of list or in front of items that come later + if (!pos) { + pList->AddTail(pCmd); + } else { + pList->AddBefore(pos, pCmd); + } + + SetTimeAdvise(); + return S_OK; +} + + +HRESULT +CCmdQueue::Remove(__in CDeferredCommand* pCmd) +{ + CAutoLock lock(&m_Lock); + HRESULT hr = S_OK; + + CGenericList<CDeferredCommand> * pList; + if (pCmd->IsStreamTime()) { + pList = &m_listStream; + } else { + pList = &m_listPresentation; + } + POSITION pos = pList->GetHeadPosition(); + + // traverse the list + while (pos && (pList->GetValid(pos) != pCmd)) { + pList->GetNext(pos); + } + + // did we drop off the end? + if (!pos) { + hr = VFW_E_NOT_FOUND; + } else { + + // found it - now take off list + pList->Remove(pos); + + // Insert did an AddRef, so release it + pCmd->Release(); + + // check that timer request is still for earliest time + SetTimeAdvise(); + } + return hr; +} + + +// set the clock used for timing + +HRESULT +CCmdQueue::SetSyncSource(__in_opt IReferenceClock* pClock) +{ + CAutoLock lock(&m_Lock); + + // addref the new clock first in case they are the same + if (pClock) { + pClock->AddRef(); + } + + // kill any advise on the old clock + if (m_pClock) { + if (m_dwAdvise) { + m_pClock->Unadvise(m_dwAdvise); + m_dwAdvise = 0; + } + m_pClock->Release(); + } + m_pClock = pClock; + + // set up a new advise + SetTimeAdvise(); + return S_OK; +} + + +// set up a timer event with the reference clock + +void +CCmdQueue::SetTimeAdvise(void) +{ + // make sure we have a clock to use + if (!m_pClock) { + return; + } + + // reset the event whenever we are requesting a new signal + m_evDue.Reset(); + + // time 0 is earliest + CRefTime current; + + // find the earliest presentation time + POSITION pos = m_listPresentation.GetHeadPosition(); + if (pos != NULL) { + current = m_listPresentation.GetValid(pos)->GetTime(); + } + + // if we're running, check the stream times too + if (m_bRunning) { + + CRefTime t; + pos = m_listStream.GetHeadPosition(); + if (NULL != pos) { + t = m_listStream.GetValid(pos)->GetTime(); + + // add on stream time offset to get presentation time + t += m_StreamTimeOffset; + + // is this earlier? + if ((current == TimeZero) || (t < current)) { + current = t; + } + } + } + + // need to change? + if ((current > TimeZero) && (current != m_tCurrentAdvise)) { + if (m_dwAdvise) { + m_pClock->Unadvise(m_dwAdvise); + // reset the event whenever we are requesting a new signal + m_evDue.Reset(); + } + + // ask for time advice - the first two params are either + // stream time offset and stream time or + // presentation time and 0. we always use the latter + HRESULT hr = m_pClock->AdviseTime( + (REFERENCE_TIME)current, + TimeZero, + (HEVENT) HANDLE(m_evDue), + &m_dwAdvise); + + ASSERT(SUCCEEDED(hr)); + m_tCurrentAdvise = current; + } +} + + +// switch to run mode. Streamtime to Presentation time mapping known. + +HRESULT +CCmdQueue::Run(REFERENCE_TIME tStreamTimeOffset) +{ + CAutoLock lock(&m_Lock); + + m_StreamTimeOffset = tStreamTimeOffset; + m_bRunning = TRUE; + + // ensure advise is accurate + SetTimeAdvise(); + return S_OK; +} + + +// switch to Stopped or Paused mode. Time mapping not known. + +HRESULT +CCmdQueue::EndRun() +{ + CAutoLock lock(&m_Lock); + + m_bRunning = FALSE; + + // check timer setting - stream times + SetTimeAdvise(); + return S_OK; +} + + +// return a pointer to the next due command. Blocks for msTimeout +// milliseconds until there is a due command. +// Stream-time commands will only become due between Run and Endrun calls. +// The command remains queued until invoked or cancelled. +// Returns E_ABORT if timeout occurs, otherwise S_OK (or other error). +// +// returns an AddRef'd object + +HRESULT +CCmdQueue::GetDueCommand(__out CDeferredCommand ** ppCmd, long msTimeout) +{ + // loop until we timeout or find a due command + for (;;) { + + { + CAutoLock lock(&m_Lock); + + + // find the earliest command + CDeferredCommand * pCmd = NULL; + + // check the presentation time and the + // stream time list to find the earliest + + POSITION pos = m_listPresentation.GetHeadPosition(); + + if (NULL != pos) { + pCmd = m_listPresentation.GetValid(pos); + } + + if (m_bRunning) { + pos = m_listStream.GetHeadPosition(); + if (NULL != pos) { + CDeferredCommand* pStrm = m_listStream.GetValid(pos); + + CRefTime t = pStrm->GetTime() + m_StreamTimeOffset; + if (!pCmd || (t < pCmd->GetTime())) { + pCmd = pStrm; + } + } + } + + // if we have found one, is it due? + if (pCmd) { + if (CheckTime(pCmd->GetTime(), pCmd->IsStreamTime())) { + + // yes it's due - addref it + pCmd->AddRef(); + *ppCmd = pCmd; + return S_OK; + } + } + } + + // block until the advise is signalled + if (WaitForSingleObject(m_evDue, msTimeout) != WAIT_OBJECT_0) { + return E_ABORT; + } + } +} + + +// return a pointer to a command that will be due for a given time. +// Pass in a stream time here. The stream time offset will be passed +// in via the Run method. +// Commands remain queued until invoked or cancelled. +// This method will not block. It will report E_ABORT if there are no +// commands due yet. +// +// returns an AddRef'd object + +HRESULT +CCmdQueue::GetCommandDueFor(REFERENCE_TIME rtStream, __out CDeferredCommand**ppCmd) +{ + CAutoLock lock(&m_Lock); + + CRefTime tStream(rtStream); + + // find the earliest stream and presentation time commands + CDeferredCommand* pStream = NULL; + POSITION pos = m_listStream.GetHeadPosition(); + if (NULL != pos) { + pStream = m_listStream.GetValid(pos); + } + CDeferredCommand* pPresent = NULL; + pos = m_listPresentation.GetHeadPosition(); + if (NULL != pos) { + pPresent = m_listPresentation.GetValid(pos); + } + + // is there a presentation time that has passed already + if (pPresent && CheckTime(pPresent->GetTime(), FALSE)) { + pPresent->AddRef(); + *ppCmd = pPresent; + return S_OK; + } + + // is there a stream time command due before this stream time + if (pStream && (pStream->GetTime() <= tStream)) { + pStream->AddRef(); + *ppCmd = pStream; + return S_OK; + } + + // if we are running, we can map presentation times to + // stream time. In this case, is there a presentation time command + // that will be due before this stream time is presented? + if (m_bRunning && pPresent) { + + // this stream time will appear at... + tStream += m_StreamTimeOffset; + + // due before that? + if (pPresent->GetTime() <= tStream) { + *ppCmd = pPresent; + return S_OK; + } + } + + // no commands due yet + return VFW_E_NOT_FOUND; +} + diff --git a/Src/Plugins/Input/in_dshow/base/ctlutil.h b/Src/Plugins/Input/in_dshow/base/ctlutil.h new file mode 100644 index 00000000..e3f7085d --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/ctlutil.h @@ -0,0 +1,923 @@ +//------------------------------------------------------------------------------ +// File: CtlUtil.h +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// Base classes implementing IDispatch parsing for the basic control dual +// interfaces. Derive from these and implement just the custom method and +// property methods. We also implement CPosPassThru that can be used by +// renderers and transforms to pass by IMediaPosition and IMediaSeeking + +#ifndef __CTLUTIL__ +#define __CTLUTIL__ + +// OLE Automation has different ideas of TRUE and FALSE + +#define OATRUE (-1) +#define OAFALSE (0) + + +// It's possible that we could replace this class with CreateStdDispatch + +class CBaseDispatch +{ + ITypeInfo * m_pti; + +public: + + CBaseDispatch() : m_pti(NULL) {} + ~CBaseDispatch(); + + /* IDispatch methods */ + STDMETHODIMP GetTypeInfoCount(__out UINT * pctinfo); + + STDMETHODIMP GetTypeInfo( + REFIID riid, + UINT itinfo, + LCID lcid, + __deref_out ITypeInfo ** pptinfo); + + STDMETHODIMP GetIDsOfNames( + REFIID riid, + __in_ecount(cNames) LPOLESTR * rgszNames, + UINT cNames, + LCID lcid, + __out_ecount(cNames) DISPID * rgdispid); +}; + + +class AM_NOVTABLE CMediaControl : + public IMediaControl, + public CUnknown +{ + CBaseDispatch m_basedisp; + +public: + + CMediaControl(const TCHAR *, LPUNKNOWN); + + DECLARE_IUNKNOWN + + // override this to publicise our interfaces + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv); + + /* IDispatch methods */ + STDMETHODIMP GetTypeInfoCount(__out UINT * pctinfo); + + STDMETHODIMP GetTypeInfo( + UINT itinfo, + LCID lcid, + __deref_out ITypeInfo ** pptinfo); + + STDMETHODIMP GetIDsOfNames( + REFIID riid, + __in_ecount(cNames) LPOLESTR * rgszNames, + UINT cNames, + LCID lcid, + __out_ecount(cNames) DISPID * rgdispid); + + STDMETHODIMP Invoke( + DISPID dispidMember, + REFIID riid, + LCID lcid, + WORD wFlags, + __in DISPPARAMS * pdispparams, + __out_opt VARIANT * pvarResult, + __out_opt EXCEPINFO * pexcepinfo, + __out_opt UINT * puArgErr); +}; + + +class AM_NOVTABLE CMediaEvent : + public IMediaEventEx, + public CUnknown +{ + CBaseDispatch m_basedisp; + +public: + + CMediaEvent(__in_opt LPCTSTR, __in_opt LPUNKNOWN); + + DECLARE_IUNKNOWN + + // override this to publicise our interfaces + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv); + + /* IDispatch methods */ + STDMETHODIMP GetTypeInfoCount(__out UINT * pctinfo); + + STDMETHODIMP GetTypeInfo( + UINT itinfo, + LCID lcid, + __deref_out ITypeInfo ** pptinfo); + + STDMETHODIMP GetIDsOfNames( + REFIID riid, + __in_ecount(cNames) LPOLESTR * rgszNames, + UINT cNames, + LCID lcid, + __out_ecount(cNames) DISPID * rgdispid); + + STDMETHODIMP Invoke( + DISPID dispidMember, + REFIID riid, + LCID lcid, + WORD wFlags, + __in DISPPARAMS * pdispparams, + __out_opt VARIANT * pvarResult, + __out_opt EXCEPINFO * pexcepinfo, + __out_opt UINT * puArgErr); +}; + + +class AM_NOVTABLE CMediaPosition : + public IMediaPosition, + public CUnknown +{ + CBaseDispatch m_basedisp; + + +public: + + CMediaPosition(__in_opt LPCTSTR, __in_opt LPUNKNOWN); + CMediaPosition(__in_opt LPCTSTR, __in_opt LPUNKNOWN, __inout HRESULT *phr); + + DECLARE_IUNKNOWN + + // override this to publicise our interfaces + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv); + + /* IDispatch methods */ + STDMETHODIMP GetTypeInfoCount(__out UINT * pctinfo); + + STDMETHODIMP GetTypeInfo( + UINT itinfo, + LCID lcid, + __deref_out ITypeInfo ** pptinfo); + + STDMETHODIMP GetIDsOfNames( + REFIID riid, + __in_ecount(cNames) LPOLESTR * rgszNames, + UINT cNames, + LCID lcid, + __out_ecount(cNames) DISPID * rgdispid); + + STDMETHODIMP Invoke( + DISPID dispidMember, + REFIID riid, + LCID lcid, + WORD wFlags, + __in DISPPARAMS * pdispparams, + __out_opt VARIANT * pvarResult, + __out_opt EXCEPINFO * pexcepinfo, + __out_opt UINT * puArgErr); + +}; + + +// OA-compatibility means that we must use double as the RefTime value, +// and REFERENCE_TIME (essentially a LONGLONG) within filters. +// this class converts between the two + +class COARefTime : public CRefTime { +public: + + COARefTime() { + }; + + COARefTime(CRefTime t) + : CRefTime(t) + { + }; + + COARefTime(REFERENCE_TIME t) + : CRefTime(t) + { + }; + + COARefTime(double d) { + m_time = (LONGLONG) (d * 10000000); + }; + + operator double() { + return double(m_time) / 10000000; + }; + + operator REFERENCE_TIME() { + return m_time; + }; + + COARefTime& operator=(const double& rd) { + m_time = (LONGLONG) (rd * 10000000); + return *this; + } + + COARefTime& operator=(const REFERENCE_TIME& rt) { + m_time = rt; + return *this; + } + + inline BOOL operator==(const COARefTime& rt) + { + return m_time == rt.m_time; + }; + + inline BOOL operator!=(const COARefTime& rt) + { + return m_time != rt.m_time; + }; + + inline BOOL operator < (const COARefTime& rt) + { + return m_time < rt.m_time; + }; + + inline BOOL operator > (const COARefTime& rt) + { + return m_time > rt.m_time; + }; + + inline BOOL operator >= (const COARefTime& rt) + { + return m_time >= rt.m_time; + }; + + inline BOOL operator <= (const COARefTime& rt) + { + return m_time <= rt.m_time; + }; + + inline COARefTime operator+(const COARefTime& rt) + { + return COARefTime(m_time + rt.m_time); + }; + + inline COARefTime operator-(const COARefTime& rt) + { + return COARefTime(m_time - rt.m_time); + }; + + inline COARefTime operator*(LONG l) + { + return COARefTime(m_time * l); + }; + + inline COARefTime operator/(LONG l) + { + return COARefTime(m_time / l); + }; + +private: + // Prevent bugs from constructing from LONG (which gets + // converted to double and then multiplied by 10000000 + COARefTime(LONG); + LONG operator=(LONG); +}; + + +// A utility class that handles IMediaPosition and IMediaSeeking on behalf +// of single-input pin renderers, or transform filters. +// +// Renderers will expose this from the filter; transform filters will +// expose it from the output pin and not the renderer. +// +// Create one of these, giving it your IPin* for your input pin, and delegate +// all IMediaPosition methods to it. It will query the input pin for +// IMediaPosition and respond appropriately. +// +// Call ForceRefresh if the pin connection changes. +// +// This class no longer caches the upstream IMediaPosition or IMediaSeeking +// it acquires it on each method call. This means ForceRefresh is not needed. +// The method is kept for source compatibility and to minimise the changes +// if we need to put it back later for performance reasons. + +class CPosPassThru : public IMediaSeeking, public CMediaPosition +{ + IPin *m_pPin; + + HRESULT GetPeer(__deref_out IMediaPosition **ppMP); + HRESULT GetPeerSeeking(__deref_out IMediaSeeking **ppMS); + +public: + + CPosPassThru(__in_opt LPCTSTR, __in_opt LPUNKNOWN, __inout HRESULT*, IPin *); + DECLARE_IUNKNOWN + + HRESULT ForceRefresh() { + return S_OK; + }; + + // override to return an accurate current position + virtual HRESULT GetMediaTime(__out LONGLONG *pStartTime, __out_opt LONGLONG *pEndTime) { + return E_FAIL; + } + + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid,__deref_out void **ppv); + + // IMediaSeeking methods + STDMETHODIMP GetCapabilities( __out DWORD * pCapabilities ); + STDMETHODIMP CheckCapabilities( __inout DWORD * pCapabilities ); + STDMETHODIMP SetTimeFormat(const GUID * pFormat); + STDMETHODIMP GetTimeFormat(__out GUID *pFormat); + STDMETHODIMP IsUsingTimeFormat(const GUID * pFormat); + STDMETHODIMP IsFormatSupported( const GUID * pFormat); + STDMETHODIMP QueryPreferredFormat( __out GUID *pFormat); + STDMETHODIMP ConvertTimeFormat(__out LONGLONG * pTarget, + __in_opt const GUID * pTargetFormat, + LONGLONG Source, + __in_opt const GUID * pSourceFormat ); + STDMETHODIMP SetPositions( __inout_opt LONGLONG * pCurrent, DWORD CurrentFlags + , __inout_opt LONGLONG * pStop, DWORD StopFlags ); + + STDMETHODIMP GetPositions( __out_opt LONGLONG * pCurrent, __out_opt LONGLONG * pStop ); + STDMETHODIMP GetCurrentPosition( __out LONGLONG * pCurrent ); + STDMETHODIMP GetStopPosition( __out LONGLONG * pStop ); + STDMETHODIMP SetRate( double dRate); + STDMETHODIMP GetRate( __out double * pdRate); + STDMETHODIMP GetDuration( __out LONGLONG *pDuration); + STDMETHODIMP GetAvailable( __out_opt LONGLONG *pEarliest, __out_opt LONGLONG *pLatest ); + STDMETHODIMP GetPreroll( __out LONGLONG *pllPreroll ); + + // IMediaPosition properties + STDMETHODIMP get_Duration(__out REFTIME * plength); + STDMETHODIMP put_CurrentPosition(REFTIME llTime); + STDMETHODIMP get_StopTime(__out REFTIME * pllTime); + STDMETHODIMP put_StopTime(REFTIME llTime); + STDMETHODIMP get_PrerollTime(__out REFTIME * pllTime); + STDMETHODIMP put_PrerollTime(REFTIME llTime); + STDMETHODIMP get_Rate(__out double * pdRate); + STDMETHODIMP put_Rate(double dRate); + STDMETHODIMP get_CurrentPosition(__out REFTIME * pllTime); + STDMETHODIMP CanSeekForward(__out LONG *pCanSeekForward); + STDMETHODIMP CanSeekBackward(__out LONG *pCanSeekBackward); + +private: + HRESULT GetSeekingLongLong( HRESULT (__stdcall IMediaSeeking::*pMethod)( LONGLONG * ), + __out LONGLONG * pll ); +}; + + +// Adds the ability to return a current position + +class CRendererPosPassThru : public CPosPassThru +{ + CCritSec m_PositionLock; // Locks access to our position + LONGLONG m_StartMedia; // Start media time last seen + LONGLONG m_EndMedia; // And likewise the end media + BOOL m_bReset; // Have media times been set + +public: + + // Used to help with passing media times through graph + + CRendererPosPassThru(__in_opt LPCTSTR, __in_opt LPUNKNOWN, __inout HRESULT*, IPin *); + HRESULT RegisterMediaTime(IMediaSample *pMediaSample); + HRESULT RegisterMediaTime(LONGLONG StartTime,LONGLONG EndTime); + HRESULT GetMediaTime(__out LONGLONG *pStartTime,__out_opt LONGLONG *pEndTime); + HRESULT ResetMediaTime(); + HRESULT EOS(); +}; + +STDAPI CreatePosPassThru( + __in_opt LPUNKNOWN pAgg, + BOOL bRenderer, + IPin *pPin, + __deref_out IUnknown **ppPassThru +); + +// A class that handles the IDispatch part of IBasicAudio and leaves the +// properties and methods themselves pure virtual. + +class AM_NOVTABLE CBasicAudio : public IBasicAudio, public CUnknown +{ + CBaseDispatch m_basedisp; + +public: + + CBasicAudio(__in_opt LPCTSTR, __in_opt LPUNKNOWN); + + DECLARE_IUNKNOWN + + // override this to publicise our interfaces + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv); + + /* IDispatch methods */ + STDMETHODIMP GetTypeInfoCount(__out UINT * pctinfo); + + STDMETHODIMP GetTypeInfo( + UINT itinfo, + LCID lcid, + __deref_out ITypeInfo ** pptinfo); + + STDMETHODIMP GetIDsOfNames( + REFIID riid, + __in_ecount(cNames) LPOLESTR * rgszNames, + UINT cNames, + LCID lcid, + __out_ecount(cNames) DISPID * rgdispid); + + STDMETHODIMP Invoke( + DISPID dispidMember, + REFIID riid, + LCID lcid, + WORD wFlags, + __in DISPPARAMS * pdispparams, + __out_opt VARIANT * pvarResult, + __out_opt EXCEPINFO * pexcepinfo, + __out_opt UINT * puArgErr); +}; + + +// A class that handles the IDispatch part of IBasicVideo and leaves the +// properties and methods themselves pure virtual. + +class AM_NOVTABLE CBaseBasicVideo : public IBasicVideo2, public CUnknown +{ + CBaseDispatch m_basedisp; + +public: + + CBaseBasicVideo(__in_opt LPCTSTR, __in_opt LPUNKNOWN); + + DECLARE_IUNKNOWN + + // override this to publicise our interfaces + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv); + + /* IDispatch methods */ + STDMETHODIMP GetTypeInfoCount(__out UINT * pctinfo); + + STDMETHODIMP GetTypeInfo( + UINT itinfo, + LCID lcid, + __deref_out ITypeInfo ** pptinfo); + + STDMETHODIMP GetIDsOfNames( + REFIID riid, + __in_ecount(cNames) LPOLESTR * rgszNames, + UINT cNames, + LCID lcid, + __out_ecount(cNames) DISPID * rgdispid); + + STDMETHODIMP Invoke( + DISPID dispidMember, + REFIID riid, + LCID lcid, + WORD wFlags, + __in DISPPARAMS * pdispparams, + __out_opt VARIANT * pvarResult, + __out_opt EXCEPINFO * pexcepinfo, + __out_opt UINT * puArgErr); + + STDMETHODIMP GetPreferredAspectRatio( + __out long *plAspectX, + __out long *plAspectY) + { + return E_NOTIMPL; + } +}; + + +// A class that handles the IDispatch part of IVideoWindow and leaves the +// properties and methods themselves pure virtual. + +class AM_NOVTABLE CBaseVideoWindow : public IVideoWindow, public CUnknown +{ + CBaseDispatch m_basedisp; + +public: + + CBaseVideoWindow(__in_opt LPCTSTR, __in_opt LPUNKNOWN); + + DECLARE_IUNKNOWN + + // override this to publicise our interfaces + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv); + + /* IDispatch methods */ + STDMETHODIMP GetTypeInfoCount(__out UINT * pctinfo); + + STDMETHODIMP GetTypeInfo( + UINT itinfo, + LCID lcid, + __deref_out ITypeInfo ** pptinfo); + + STDMETHODIMP GetIDsOfNames( + REFIID riid, + __in_ecount(cNames) LPOLESTR * rgszNames, + UINT cNames, + LCID lcid, + __out_ecount(cNames) DISPID * rgdispid); + + STDMETHODIMP Invoke( + DISPID dispidMember, + REFIID riid, + LCID lcid, + WORD wFlags, + __in DISPPARAMS * pdispparams, + __out_opt VARIANT * pvarResult, + __out_opt EXCEPINFO * pexcepinfo, + __out_opt UINT * puArgErr); +}; + + +// abstract class to help source filters with their implementation +// of IMediaPosition. Derive from this and set the duration (and stop +// position). Also override NotifyChange to do something when the properties +// change. + +class AM_NOVTABLE CSourcePosition : public CMediaPosition +{ + +public: + CSourcePosition(__in_opt LPCTSTR, __in_opt LPUNKNOWN, __inout HRESULT*, __in CCritSec *); + + // IMediaPosition methods + STDMETHODIMP get_Duration(__out REFTIME * plength); + STDMETHODIMP put_CurrentPosition(REFTIME llTime); + STDMETHODIMP get_StopTime(__out REFTIME * pllTime); + STDMETHODIMP put_StopTime(REFTIME llTime); + STDMETHODIMP get_PrerollTime(__out REFTIME * pllTime); + STDMETHODIMP put_PrerollTime(REFTIME llTime); + STDMETHODIMP get_Rate(__out double * pdRate); + STDMETHODIMP put_Rate(double dRate); + STDMETHODIMP CanSeekForward(__out LONG *pCanSeekForward); + STDMETHODIMP CanSeekBackward(__out LONG *pCanSeekBackward); + + // override if you can return the data you are actually working on + STDMETHODIMP get_CurrentPosition(__out REFTIME * pllTime) { + return E_NOTIMPL; + }; + +protected: + + // we call this to notify changes. Override to handle them + virtual HRESULT ChangeStart() PURE; + virtual HRESULT ChangeStop() PURE; + virtual HRESULT ChangeRate() PURE; + + COARefTime m_Duration; + COARefTime m_Start; + COARefTime m_Stop; + double m_Rate; + + CCritSec * m_pLock; +}; + +class AM_NOVTABLE CSourceSeeking : + public IMediaSeeking, + public CUnknown +{ + +public: + + DECLARE_IUNKNOWN; + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv); + + // IMediaSeeking methods + + STDMETHODIMP IsFormatSupported(const GUID * pFormat); + STDMETHODIMP QueryPreferredFormat(__out GUID *pFormat); + STDMETHODIMP SetTimeFormat(const GUID * pFormat); + STDMETHODIMP IsUsingTimeFormat(const GUID * pFormat); + STDMETHODIMP GetTimeFormat(__out GUID *pFormat); + STDMETHODIMP GetDuration(__out LONGLONG *pDuration); + STDMETHODIMP GetStopPosition(__out LONGLONG *pStop); + STDMETHODIMP GetCurrentPosition(__out LONGLONG *pCurrent); + STDMETHODIMP GetCapabilities( __out DWORD * pCapabilities ); + STDMETHODIMP CheckCapabilities( __inout DWORD * pCapabilities ); + STDMETHODIMP ConvertTimeFormat( __out LONGLONG * pTarget, + __in_opt const GUID * pTargetFormat, + LONGLONG Source, + __in_opt const GUID * pSourceFormat ); + + STDMETHODIMP SetPositions( __inout_opt LONGLONG * pCurrent, DWORD CurrentFlags + , __inout_opt LONGLONG * pStop, DWORD StopFlags ); + + STDMETHODIMP GetPositions( __out_opt LONGLONG * pCurrent, __out_opt LONGLONG * pStop ); + + STDMETHODIMP GetAvailable( __out_opt LONGLONG * pEarliest, __out_opt LONGLONG * pLatest ); + STDMETHODIMP SetRate( double dRate); + STDMETHODIMP GetRate( __out double * pdRate); + STDMETHODIMP GetPreroll(__out LONGLONG *pPreroll); + + +protected: + + // ctor + CSourceSeeking(__in_opt LPCTSTR, __in_opt LPUNKNOWN, __inout HRESULT*, __in CCritSec *); + + // we call this to notify changes. Override to handle them + virtual HRESULT ChangeStart() PURE; + virtual HRESULT ChangeStop() PURE; + virtual HRESULT ChangeRate() PURE; + + CRefTime m_rtDuration; // length of stream + CRefTime m_rtStart; // source will start here + CRefTime m_rtStop; // source will stop here + double m_dRateSeeking; + + // seeking capabilities + DWORD m_dwSeekingCaps; + + CCritSec * m_pLock; +}; + + +// Base classes supporting Deferred commands. + +// Deferred commands are queued by calls to methods on the IQueueCommand +// interface, exposed by the filtergraph and by some filters. A successful +// call to one of these methods will return an IDeferredCommand interface +// representing the queued command. +// +// A CDeferredCommand object represents a single deferred command, and exposes +// the IDeferredCommand interface as well as other methods permitting time +// checks and actual execution. It contains a reference to the CCommandQueue +// object on which it is queued. +// +// CCommandQueue is a base class providing a queue of CDeferredCommand +// objects, and methods to add, remove, check status and invoke the queued +// commands. A CCommandQueue object would be part of an object that +// implemented IQueueCommand. + +class CCmdQueue; + +// take a copy of the params and store them. Release any allocated +// memory in destructor + +class CDispParams : public DISPPARAMS +{ +public: + CDispParams(UINT nArgs, __in_ecount(nArgs) VARIANT* pArgs, __inout_opt HRESULT *phr = NULL); + ~CDispParams(); +}; + + +// CDeferredCommand lifetime is controlled by refcounts. Caller of +// InvokeAt.. gets a refcounted interface pointer, and the CCmdQueue +// object also holds a refcount on us. Calling Cancel or Invoke takes +// us off the CCmdQueue and thus reduces the refcount by 1. Once taken +// off the queue we cannot be put back on the queue. + +class CDeferredCommand + : public CUnknown, + public IDeferredCommand +{ +public: + + CDeferredCommand( + __inout CCmdQueue * pQ, + __in_opt LPUNKNOWN pUnk, // aggregation outer unk + __inout HRESULT * phr, + __in LPUNKNOWN pUnkExecutor, // object that will execute this cmd + REFTIME time, + __in GUID* iid, + long dispidMethod, + short wFlags, + long cArgs, + __in_ecount(cArgs) VARIANT* pDispParams, + __out VARIANT* pvarResult, + __out short* puArgErr, + BOOL bStream + ); + + DECLARE_IUNKNOWN + + // override this to publicise our interfaces + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __out void **ppv); + + // IDeferredCommand methods + STDMETHODIMP Cancel(); + STDMETHODIMP Confidence( + __out LONG* pConfidence); + STDMETHODIMP Postpone( + REFTIME newtime); + STDMETHODIMP GetHResult( + __out HRESULT* phrResult); + + // other public methods + + HRESULT Invoke(); + + // access methods + + // returns TRUE if streamtime, FALSE if presentation time + BOOL IsStreamTime() { + return m_bStream; + }; + + CRefTime GetTime() { + return m_time; + }; + + REFIID GetIID() { + return *m_iid; + }; + + long GetMethod() { + return m_dispidMethod; + }; + + short GetFlags() { + return m_wFlags; + }; + + DISPPARAMS* GetParams() { + return &m_DispParams; + }; + + VARIANT* GetResult() { + return m_pvarResult; + }; + +protected: + + CCmdQueue* m_pQueue; + + // pUnk for the interface that we will execute the command on + LPUNKNOWN m_pUnk; + + // stored command data + REFERENCE_TIME m_time; + GUID* m_iid; + long m_dispidMethod; + short m_wFlags; + VARIANT* m_pvarResult; + BOOL m_bStream; + CDispParams m_DispParams; + DISPID m_DispId; // For get and put + + // we use this for ITypeInfo access + CBaseDispatch m_Dispatch; + + // save retval here + HRESULT m_hrResult; +}; + + +// a list of CDeferredCommand objects. this is a base class providing +// the basics of access to the list. If you want to use CDeferredCommand +// objects then your queue needs to be derived from this class. + +class AM_NOVTABLE CCmdQueue +{ +public: + CCmdQueue(__inout_opt HRESULT *phr = NULL); + virtual ~CCmdQueue(); + + // returns a new CDeferredCommand object that will be initialised with + // the parameters and will be added to the queue during construction. + // returns S_OK if successfully created otherwise an error and + // no object has been queued. + virtual HRESULT New( + __out CDeferredCommand **ppCmd, + __in LPUNKNOWN pUnk, + REFTIME time, + __in GUID* iid, + long dispidMethod, + short wFlags, + long cArgs, + __in_ecount(cArgs) VARIANT* pDispParams, + __out VARIANT* pvarResult, + __out short* puArgErr, + BOOL bStream + ); + + // called by the CDeferredCommand object to add and remove itself + // from the queue + virtual HRESULT Insert(__in CDeferredCommand* pCmd); + virtual HRESULT Remove(__in CDeferredCommand* pCmd); + + // Command-Due Checking + // + // There are two schemes of synchronisation: coarse and accurate. In + // coarse mode, you wait till the time arrives and then execute the cmd. + // In accurate mode, you wait until you are processing the sample that + // will appear at the time, and then execute the command. It's up to the + // filter which one it will implement. The filtergraph will always + // implement coarse mode for commands queued at the filtergraph. + // + // If you want coarse sync, you probably want to wait until there is a + // command due, and then execute it. You can do this by calling + // GetDueCommand. If you have several things to wait for, get the + // event handle from GetDueHandle() and when this is signalled then call + // GetDueCommand. Stream time will only advance between calls to Run and + // EndRun. Note that to avoid an extra thread there is no guarantee that + // if the handle is set there will be a command ready. Each time the + // event is signalled, call GetDueCommand (probably with a 0 timeout); + // This may return E_ABORT. + // + // If you want accurate sync, you must call GetCommandDueFor, passing + // as a parameter the stream time of the samples you are about to process. + // This will return: + // -- a stream-time command due at or before that stream time + // -- a presentation-time command due at or before the + // time that stream time will be presented (only between Run + // and EndRun calls, since outside of this, the mapping from + // stream time to presentation time is not known. + // -- any presentation-time command due now. + // This means that if you want accurate synchronisation on samples that + // might be processed during Paused mode, you need to use + // stream-time commands. + // + // In all cases, commands remain queued until Invoked or Cancelled. The + // setting and resetting of the event handle is managed entirely by this + // queue object. + + // set the clock used for timing + virtual HRESULT SetSyncSource(__in_opt IReferenceClock*); + + // switch to run mode. Streamtime to Presentation time mapping known. + virtual HRESULT Run(REFERENCE_TIME tStreamTimeOffset); + + // switch to Stopped or Paused mode. Time mapping not known. + virtual HRESULT EndRun(); + + // return a pointer to the next due command. Blocks for msTimeout + // milliseconds until there is a due command. + // Stream-time commands will only become due between Run and Endrun calls. + // The command remains queued until invoked or cancelled. + // Returns E_ABORT if timeout occurs, otherwise S_OK (or other error). + // Returns an AddRef-ed object + virtual HRESULT GetDueCommand(__out CDeferredCommand ** ppCmd, long msTimeout); + + // return the event handle that will be signalled whenever + // there are deferred commands due for execution (when GetDueCommand + // will not block). + HANDLE GetDueHandle() { + return HANDLE(m_evDue); + }; + + // return a pointer to a command that will be due for a given time. + // Pass in a stream time here. The stream time offset will be passed + // in via the Run method. + // Commands remain queued until invoked or cancelled. + // This method will not block. It will report VFW_E_NOT_FOUND if there + // are no commands due yet. + // Returns an AddRef-ed object + virtual HRESULT GetCommandDueFor(REFERENCE_TIME tStream, __out CDeferredCommand**ppCmd); + + // check if a given time is due (TRUE if it is due yet) + BOOL CheckTime(CRefTime time, BOOL bStream) { + + // if no clock, nothing is due! + if (!m_pClock) { + return FALSE; + } + + // stream time + if (bStream) { + + // not valid if not running + if (!m_bRunning) { + return FALSE; + } + // add on known stream time offset to get presentation time + time += m_StreamTimeOffset; + } + + CRefTime Now; + m_pClock->GetTime((REFERENCE_TIME*)&Now); + return (time <= Now); + }; + +protected: + + // protect access to lists etc + CCritSec m_Lock; + + // commands queued in presentation time are stored here + CGenericList<CDeferredCommand> m_listPresentation; + + // commands queued in stream time are stored here + CGenericList<CDeferredCommand> m_listStream; + + // set when any commands are due + CAMEvent m_evDue; + + // creates an advise for the earliest time required, if any + void SetTimeAdvise(void); + + // advise id from reference clock (0 if no outstanding advise) + DWORD_PTR m_dwAdvise; + + // advise time is for this presentation time + CRefTime m_tCurrentAdvise; + + // the reference clock we are using (addrefed) + IReferenceClock* m_pClock; + + // true when running + BOOL m_bRunning; + + // contains stream time offset when m_bRunning is true + CRefTime m_StreamTimeOffset; +}; + +#endif // __CTLUTIL__ diff --git a/Src/Plugins/Input/in_dshow/base/ddmm.cpp b/Src/Plugins/Input/in_dshow/base/ddmm.cpp new file mode 100644 index 00000000..50c30dd4 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/ddmm.cpp @@ -0,0 +1,129 @@ +//------------------------------------------------------------------------------ +// File: DDMM.cpp +// +// Desc: DirectShow base classes - implements routines for using DirectDraw +// on a multimonitor system. +// +// Copyright (c) 1995-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> +#include <ddraw.h> +#include "ddmm.h" + +/* + * FindDeviceCallback + */ +typedef struct { + LPSTR szDevice; + GUID* lpGUID; + GUID GUID; + BOOL fFound; +} FindDeviceData; + +BOOL CALLBACK FindDeviceCallback(__in_opt GUID* lpGUID, __in LPSTR szName, __in LPSTR szDevice, __in LPVOID lParam) +{ + FindDeviceData *p = (FindDeviceData*)lParam; + + if (lstrcmpiA(p->szDevice, szDevice) == 0) { + if (lpGUID) { + p->GUID = *lpGUID; + p->lpGUID = &p->GUID; + } else { + p->lpGUID = NULL; + } + p->fFound = TRUE; + return FALSE; + } + return TRUE; +} + + +BOOL CALLBACK FindDeviceCallbackEx(__in_opt GUID* lpGUID, __in LPSTR szName, __in LPSTR szDevice, __in LPVOID lParam, HMONITOR hMonitor) +{ + FindDeviceData *p = (FindDeviceData*)lParam; + + if (lstrcmpiA(p->szDevice, szDevice) == 0) { + if (lpGUID) { + p->GUID = *lpGUID; + p->lpGUID = &p->GUID; + } else { + p->lpGUID = NULL; + } + p->fFound = TRUE; + return FALSE; + } + return TRUE; +} + + +/* + * DirectDrawCreateFromDevice + * + * create a DirectDraw object for a particular device + */ +IDirectDraw * DirectDrawCreateFromDevice(__in_opt LPSTR szDevice, PDRAWCREATE DirectDrawCreateP, PDRAWENUM DirectDrawEnumerateP) +{ + IDirectDraw* pdd = NULL; + FindDeviceData find; + + if (szDevice == NULL) { + DirectDrawCreateP(NULL, &pdd, NULL); + return pdd; + } + + find.szDevice = szDevice; + find.fFound = FALSE; + DirectDrawEnumerateP(FindDeviceCallback, (LPVOID)&find); + + if (find.fFound) + { + // + // In 4bpp mode the following DDraw call causes a message box to be popped + // up by DDraw (!?!). It's DDraw's fault, but we don't like it. So we + // make sure it doesn't happen. + // + UINT ErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); + DirectDrawCreateP(find.lpGUID, &pdd, NULL); + SetErrorMode(ErrorMode); + } + + return pdd; +} + + +/* + * DirectDrawCreateFromDeviceEx + * + * create a DirectDraw object for a particular device + */ +IDirectDraw * DirectDrawCreateFromDeviceEx(__in_opt LPSTR szDevice, PDRAWCREATE DirectDrawCreateP, LPDIRECTDRAWENUMERATEEXA DirectDrawEnumerateExP) +{ + IDirectDraw* pdd = NULL; + FindDeviceData find; + + if (szDevice == NULL) { + DirectDrawCreateP(NULL, &pdd, NULL); + return pdd; + } + + find.szDevice = szDevice; + find.fFound = FALSE; + DirectDrawEnumerateExP(FindDeviceCallbackEx, (LPVOID)&find, + DDENUM_ATTACHEDSECONDARYDEVICES); + + if (find.fFound) + { + // + // In 4bpp mode the following DDraw call causes a message box to be popped + // up by DDraw (!?!). It's DDraw's fault, but we don't like it. So we + // make sure it doesn't happen. + // + UINT ErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); + DirectDrawCreateP(find.lpGUID, &pdd, NULL); + SetErrorMode(ErrorMode); + } + + return pdd; +} diff --git a/Src/Plugins/Input/in_dshow/base/ddmm.h b/Src/Plugins/Input/in_dshow/base/ddmm.h new file mode 100644 index 00000000..c773d588 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/ddmm.h @@ -0,0 +1,28 @@ +//------------------------------------------------------------------------------ +// File: DDMM.h +// +// Desc: DirectShow base classes - efines routines for using DirectDraw +// on a multimonitor system. +// +// Copyright (c) 1995-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifdef __cplusplus +extern "C" { /* Assume C declarations for C++ */ +#endif /* __cplusplus */ + +// DDRAW.H might not include these +#ifndef DDENUM_ATTACHEDSECONDARYDEVICES +#define DDENUM_ATTACHEDSECONDARYDEVICES 0x00000001L +#endif + +typedef HRESULT (*PDRAWCREATE)(IID *,LPDIRECTDRAW *,LPUNKNOWN); +typedef HRESULT (*PDRAWENUM)(LPDDENUMCALLBACKA, LPVOID); + +IDirectDraw * DirectDrawCreateFromDevice(__in_opt LPSTR, PDRAWCREATE, PDRAWENUM); +IDirectDraw * DirectDrawCreateFromDeviceEx(__in_opt LPSTR, PDRAWCREATE, LPDIRECTDRAWENUMERATEEXA); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ diff --git a/Src/Plugins/Input/in_dshow/base/dllentry.cpp b/Src/Plugins/Input/in_dshow/base/dllentry.cpp new file mode 100644 index 00000000..acde9fe3 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/dllentry.cpp @@ -0,0 +1,373 @@ +//------------------------------------------------------------------------------ +// File: DlleEntry.cpp +// +// Desc: DirectShow base classes - implements classes used to support dll +// entry points for COM objects. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> +#include <initguid.h> +#include "combase.h" + +#ifdef DEBUG +#ifdef UNICODE +#ifndef _UNICODE +#define _UNICODE +#endif // _UNICODE +#endif // UNICODE + +#include <tchar.h> +#endif // DEBUG +#include <strsafe.h> + + +//extern int g_cTemplates; +//extern CFactoryTemplate g_Templates[]; + +HINSTANCE g_hInst; +DWORD g_amPlatform; // VER_PLATFORM_WIN32_WINDOWS etc... (from GetVersionEx) +OSVERSIONINFO g_osInfo; + +// +// an instance of this is created by the DLLGetClassObject entrypoint +// it uses the CFactoryTemplate object it is given to support the +// IClassFactory interface + +class CClassFactory : public IClassFactory, public CBaseObject +{ + +private: + const CFactoryTemplate *const m_pTemplate; + + ULONG m_cRef; + + static int m_cLocked; +public: + CClassFactory(const CFactoryTemplate *); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, __deref_out void ** ppv); + STDMETHODIMP_(ULONG)AddRef(); + STDMETHODIMP_(ULONG)Release(); + + // IClassFactory + STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, __deref_out void **pv); + STDMETHODIMP LockServer(BOOL fLock); + + // allow DLLGetClassObject to know about global server lock status + static BOOL IsLocked() { + return (m_cLocked > 0); + }; +}; + +// process-wide dll locked state +int CClassFactory::m_cLocked = 0; + +CClassFactory::CClassFactory(const CFactoryTemplate *pTemplate) +: CBaseObject(NAME("Class Factory")) +, m_cRef(0) +, m_pTemplate(pTemplate) +{ +} + + +STDMETHODIMP +CClassFactory::QueryInterface(REFIID riid,__deref_out void **ppv) +{ + CheckPointer(ppv,E_POINTER) + ValidateReadWritePtr(ppv,sizeof(PVOID)); + *ppv = NULL; + + // any interface on this object is the object pointer. + if ((riid == IID_IUnknown) || (riid == IID_IClassFactory)) { + *ppv = (LPVOID) this; + // AddRef returned interface pointer + ((LPUNKNOWN) *ppv)->AddRef(); + return NOERROR; + } + + return ResultFromScode(E_NOINTERFACE); +} + + +STDMETHODIMP_(ULONG) +CClassFactory::AddRef() +{ + return ++m_cRef; +} + +STDMETHODIMP_(ULONG) +CClassFactory::Release() +{ + LONG lRef = InterlockedDecrement((volatile LONG *)&m_cRef); + if (lRef == 0) { + delete this; + return 0; + } else { + return lRef; + } +} + +STDMETHODIMP +CClassFactory::CreateInstance( + LPUNKNOWN pUnkOuter, + REFIID riid, + __deref_out void **pv) +{ + CheckPointer(pv,E_POINTER) + ValidateReadWritePtr(pv,sizeof(void *)); + *pv = NULL; + + /* Enforce the normal OLE rules regarding interfaces and delegation */ + + if (pUnkOuter != NULL) { + if (IsEqualIID(riid,IID_IUnknown) == FALSE) { + *pv = NULL; + return ResultFromScode(E_NOINTERFACE); + } + } + + /* Create the new object through the derived class's create function */ + + HRESULT hr = NOERROR; + CUnknown *pObj = m_pTemplate->CreateInstance(pUnkOuter, &hr); + + if (pObj == NULL) { + *pv = NULL; + if (SUCCEEDED(hr)) { + hr = E_OUTOFMEMORY; + } + return hr; + } + + /* Delete the object if we got a construction error */ + + if (FAILED(hr)) { + delete pObj; + *pv = NULL; + return hr; + } + + /* Get a reference counted interface on the object */ + + /* We wrap the non-delegating QI with NDAddRef & NDRelease. */ + /* This protects any outer object from being prematurely */ + /* released by an inner object that may have to be created */ + /* in order to supply the requested interface. */ + pObj->NonDelegatingAddRef(); + hr = pObj->NonDelegatingQueryInterface(riid, pv); + pObj->NonDelegatingRelease(); + /* Note that if NonDelegatingQueryInterface fails, it will */ + /* not increment the ref count, so the NonDelegatingRelease */ + /* will drop the ref back to zero and the object will "self-*/ + /* destruct". Hence we don't need additional tidy-up code */ + /* to cope with NonDelegatingQueryInterface failing. */ + + if (SUCCEEDED(hr)) { + ASSERT(*pv); + } + + return hr; +} + +STDMETHODIMP +CClassFactory::LockServer(BOOL fLock) +{ + if (fLock) { + m_cLocked++; + } else { + m_cLocked--; + } + return NOERROR; +} + + +// --- COM entrypoints ----------------------------------------- + +//called by COM to get the class factory object for a given class +/* +__control_entrypoint(DllExport) STDAPI +DllGetClassObject( + __in REFCLSID rClsID, + __in REFIID riid, + __deref_out void **pv) +{ + *pv = NULL; + if (!(riid == IID_IUnknown) && !(riid == IID_IClassFactory)) { + return E_NOINTERFACE; + } + + // traverse the array of templates looking for one with this + // class id + for (int i = 0; i < g_cTemplates; i++) { + const CFactoryTemplate * pT = &g_Templates[i]; + if (pT->IsClassID(rClsID)) { + + // found a template - make a class factory based on this + // template + + *pv = (LPVOID) (LPUNKNOWN) new CClassFactory(pT); + if (*pv == NULL) { + return E_OUTOFMEMORY; + } + ((LPUNKNOWN)*pv)->AddRef(); + return NOERROR; + } + } + return CLASS_E_CLASSNOTAVAILABLE; +} +*/ + +// +// Call any initialization routines +// +/* +void +DllInitClasses(BOOL bLoading) +{ + int i; + + // traverse the array of templates calling the init routine + // if they have one + for (i = 0; i < g_cTemplates; i++) { + const CFactoryTemplate * pT = &g_Templates[i]; + if (pT->m_lpfnInit != NULL) { + (*pT->m_lpfnInit)(bLoading, pT->m_ClsID); + } + } + +} +*/ + +// called by COM to determine if this dll can be unloaded +// return ok unless there are outstanding objects or a lock requested +// by IClassFactory::LockServer +// +// CClassFactory has a static function that can tell us about the locks, +// and CCOMObject has a static function that can tell us about the active +// object count +STDAPI +DllCanUnloadNow() +{ + DbgLog((LOG_MEMORY,2,TEXT("DLLCanUnloadNow called - IsLocked = %d, Active objects = %d"), + CClassFactory::IsLocked(), + CBaseObject::ObjectsActive())); + + if (CClassFactory::IsLocked() || CBaseObject::ObjectsActive()) { + return S_FALSE; + } else { + return S_OK; + } +} + + +// --- standard WIN32 entrypoints -------------------------------------- +/* + +extern "C" void __cdecl __security_init_cookie(void); +extern "C" BOOL WINAPI _DllEntryPoint(HINSTANCE, ULONG, __inout_opt LPVOID); +#pragma comment(linker, "/merge:.CRT=.rdata") + +extern "C" +DECLSPEC_NOINLINE +BOOL +WINAPI +DllEntryPoint( + HINSTANCE hInstance, + ULONG ulReason, + __inout_opt LPVOID pv + ) +{ + if ( ulReason == DLL_PROCESS_ATTACH ) { + // Must happen before any other code is executed. Thankfully - it's re-entrant + __security_init_cookie(); + } + return _DllEntryPoint(hInstance, ulReason, pv); +} + + +DECLSPEC_NOINLINE +BOOL +WINAPI +_DllEntryPoint( + HINSTANCE hInstance, + ULONG ulReason, + __inout_opt LPVOID pv + ) +{ +#ifdef DEBUG + extern bool g_fDbgInDllEntryPoint; + g_fDbgInDllEntryPoint = true; +#endif + + switch (ulReason) + { + + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hInstance); + DbgInitialise(hInstance); + + { + // The platform identifier is used to work out whether + // full unicode support is available or not. Hence the + // default will be the lowest common denominator - i.e. N/A + g_amPlatform = VER_PLATFORM_WIN32_WINDOWS; // win95 assumed in case GetVersionEx fails + + g_osInfo.dwOSVersionInfoSize = sizeof(g_osInfo); + if (GetVersionEx(&g_osInfo)) { + g_amPlatform = g_osInfo.dwPlatformId; + } else { + DbgLog((LOG_ERROR, 1, TEXT("Failed to get the OS platform, assuming Win95"))); + } + } + + g_hInst = hInstance; + DllInitClasses(TRUE); + break; + + case DLL_PROCESS_DETACH: + DllInitClasses(FALSE); + +#ifdef DEBUG + if (CBaseObject::ObjectsActive()) { + DbgSetModuleLevel(LOG_MEMORY, 2); + TCHAR szInfo[512]; + extern TCHAR m_ModuleName[]; // Cut down module name + + TCHAR FullName[_MAX_PATH]; // Load the full path and module name + TCHAR *pName; // Searches from the end for a backslash + + GetModuleFileName(NULL,FullName,_MAX_PATH); + pName = _tcsrchr(FullName,'\\'); + if (pName == NULL) { + pName = FullName; + } else { + pName++; + } + + (void)StringCchPrintf(szInfo, NUMELMS(szInfo), TEXT("Executable: %s Pid %x Tid %x. "), + pName, GetCurrentProcessId(), GetCurrentThreadId()); + + (void)StringCchPrintf(szInfo+lstrlen(szInfo), NUMELMS(szInfo) - lstrlen(szInfo), TEXT("Module %s, %d objects left active!"), + m_ModuleName, CBaseObject::ObjectsActive()); + DbgAssert(szInfo, TEXT(__FILE__),__LINE__); + + // If running remotely wait for the Assert to be acknowledged + // before dumping out the object register + DbgDumpObjectRegister(); + } + DbgTerminate(); +#endif + break; + } + +#ifdef DEBUG + g_fDbgInDllEntryPoint = false; +#endif + return TRUE; +} + +*/ diff --git a/Src/Plugins/Input/in_dshow/base/dllsetup.cpp b/Src/Plugins/Input/in_dshow/base/dllsetup.cpp new file mode 100644 index 00000000..a79be442 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/dllsetup.cpp @@ -0,0 +1,694 @@ +//------------------------------------------------------------------------------ +// File: DllSetup.cpp +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> +#include <strsafe.h> +#include "combase.h" + +//--------------------------------------------------------------------------- +// defines + +#define MAX_KEY_LEN 260 + + +//--------------------------------------------------------------------------- +// externally defined functions/variable + +//extern int g_cTemplates; +//extern CFactoryTemplate g_Templates[]; + +//--------------------------------------------------------------------------- +// +// EliminateSubKey +// +// Try to enumerate all keys under this one. +// if we find anything, delete it completely. +// Otherwise just delete it. +// +// note - this was pinched/duplicated from +// Filgraph\Mapper.cpp - so should it be in +// a lib somewhere? +// +//--------------------------------------------------------------------------- + +STDAPI +EliminateSubKey( HKEY hkey, LPCTSTR strSubKey ) +{ + HKEY hk; + if (0 == lstrlen(strSubKey) ) { + // defensive approach + return E_FAIL; + } + + LONG lreturn = RegOpenKeyEx( hkey + , strSubKey + , 0 + , MAXIMUM_ALLOWED + , &hk ); + + ASSERT( lreturn == ERROR_SUCCESS + || lreturn == ERROR_FILE_NOT_FOUND + || lreturn == ERROR_INVALID_HANDLE ); + + if( ERROR_SUCCESS == lreturn ) + { + // Keep on enumerating the first (zero-th) + // key and deleting that + + for( ; ; ) + { + TCHAR Buffer[MAX_KEY_LEN]; + DWORD dw = MAX_KEY_LEN; + FILETIME ft; + + lreturn = RegEnumKeyEx( hk + , 0 + , Buffer + , &dw + , NULL + , NULL + , NULL + , &ft); + + ASSERT( lreturn == ERROR_SUCCESS + || lreturn == ERROR_NO_MORE_ITEMS ); + + if( ERROR_SUCCESS == lreturn ) + { + EliminateSubKey(hk, Buffer); + } + else + { + break; + } + } + + RegCloseKey(hk); + RegDeleteKey(hkey, strSubKey); + } + + return NOERROR; +} + + +//--------------------------------------------------------------------------- +// +// AMovieSetupRegisterServer() +// +// registers specfied file "szFileName" as server for +// CLSID "clsServer". A description is also required. +// The ThreadingModel and ServerType are optional, as +// they default to InprocServer32 (i.e. dll) and Both. +// +//--------------------------------------------------------------------------- + +STDAPI +AMovieSetupRegisterServer( CLSID clsServer + , LPCWSTR szDescription + , LPCWSTR szFileName + , LPCWSTR szThreadingModel = L"Both" + , LPCWSTR szServerType = L"InprocServer32" ) +{ + // temp buffer + // + TCHAR achTemp[MAX_PATH]; + + // convert CLSID uuid to string and write + // out subkey as string - CLSID\{} + // + OLECHAR szCLSID[CHARS_IN_GUID]; + HRESULT hr = StringFromGUID2( clsServer + , szCLSID + , CHARS_IN_GUID ); + ASSERT( SUCCEEDED(hr) ); + + // create key + // + HKEY hkey; + (void)StringCchPrintf( achTemp, NUMELMS(achTemp), TEXT("CLSID\\%ls"), szCLSID ); + LONG lreturn = RegCreateKey( HKEY_CLASSES_ROOT + , (LPCTSTR)achTemp + , &hkey ); + if( ERROR_SUCCESS != lreturn ) + { + return AmHresultFromWin32(lreturn); + } + + // set description string + // + + (void)StringCchPrintf( achTemp, NUMELMS(achTemp), TEXT("%ls"), szDescription ); + lreturn = RegSetValue( hkey + , (LPCTSTR)NULL + , REG_SZ + , achTemp + , sizeof(achTemp) ); + if( ERROR_SUCCESS != lreturn ) + { + RegCloseKey( hkey ); + return AmHresultFromWin32(lreturn); + } + + // create CLSID\\{"CLSID"}\\"ServerType" key, + // using key to CLSID\\{"CLSID"} passed back by + // last call to RegCreateKey(). + // + HKEY hsubkey; + + (void)StringCchPrintf( achTemp, NUMELMS(achTemp), TEXT("%ls"), szServerType ); + lreturn = RegCreateKey( hkey + , achTemp + , &hsubkey ); + if( ERROR_SUCCESS != lreturn ) + { + RegCloseKey( hkey ); + return AmHresultFromWin32(lreturn); + } + + // set Server string + // + (void)StringCchPrintf( achTemp, NUMELMS(achTemp), TEXT("%ls"), szFileName ); + lreturn = RegSetValue( hsubkey + , (LPCTSTR)NULL + , REG_SZ + , (LPCTSTR)achTemp + , sizeof(TCHAR) * (lstrlen(achTemp)+1) ); + if( ERROR_SUCCESS != lreturn ) + { + RegCloseKey( hkey ); + RegCloseKey( hsubkey ); + return AmHresultFromWin32(lreturn); + } + + (void)StringCchPrintf( achTemp, NUMELMS(achTemp), TEXT("%ls"), szThreadingModel ); + lreturn = RegSetValueEx( hsubkey + , TEXT("ThreadingModel") + , 0L + , REG_SZ + , (CONST BYTE *)achTemp + , sizeof(TCHAR) * (lstrlen(achTemp)+1) ); + + // close hkeys + // + RegCloseKey( hkey ); + RegCloseKey( hsubkey ); + + // and return + // + return HRESULT_FROM_WIN32(lreturn); + +} + + +//--------------------------------------------------------------------------- +// +// AMovieSetupUnregisterServer() +// +// default ActiveMovie dll setup function +// - to use must be called from an exported +// function named DllRegisterServer() +// +//--------------------------------------------------------------------------- + +STDAPI +AMovieSetupUnregisterServer( CLSID clsServer ) +{ + // convert CLSID uuid to string and write + // out subkey CLSID\{} + // + OLECHAR szCLSID[CHARS_IN_GUID]; + HRESULT hr = StringFromGUID2( clsServer + , szCLSID + , CHARS_IN_GUID ); + ASSERT( SUCCEEDED(hr) ); + + TCHAR achBuffer[MAX_KEY_LEN]; + (void)StringCchPrintf( achBuffer, NUMELMS(achBuffer), TEXT("CLSID\\%ls"), szCLSID ); + + // delete subkey + // + + hr = EliminateSubKey( HKEY_CLASSES_ROOT, achBuffer ); + ASSERT( SUCCEEDED(hr) ); + + // return + // + return NOERROR; +} + + +//--------------------------------------------------------------------------- +// +// AMovieSetupRegisterFilter through IFilterMapper2 +// +//--------------------------------------------------------------------------- + +STDAPI +AMovieSetupRegisterFilter2( const AMOVIESETUP_FILTER * const psetupdata + , IFilterMapper2 * pIFM2 + , BOOL bRegister ) +{ + DbgLog((LOG_TRACE, 3, TEXT("= AMovieSetupRegisterFilter"))); + + // check we've got data + // + if( NULL == psetupdata ) return S_FALSE; + + + // unregister filter + // (as pins are subkeys of filter's CLSID key + // they do not need to be removed separately). + // + DbgLog((LOG_TRACE, 3, TEXT("= = unregister filter"))); + HRESULT hr = pIFM2->UnregisterFilter( + 0, // default category + 0, // default instance name + *psetupdata->clsID ); + + + if( bRegister ) + { + REGFILTER2 rf2; + rf2.dwVersion = 1; + rf2.dwMerit = psetupdata->dwMerit; + rf2.cPins = psetupdata->nPins; + rf2.rgPins = psetupdata->lpPin; + + // register filter + // + DbgLog((LOG_TRACE, 3, TEXT("= = register filter"))); + hr = pIFM2->RegisterFilter(*psetupdata->clsID + , psetupdata->strName + , 0 // moniker + , 0 // category + , NULL // instance + , &rf2); + } + + // handle one acceptable "error" - that + // of filter not being registered! + // (couldn't find a suitable #define'd + // name for the error!) + // + if( 0x80070002 == hr) + return NOERROR; + else + return hr; +} + + +//--------------------------------------------------------------------------- +// +// RegisterAllServers() +// +//--------------------------------------------------------------------------- +/* +STDAPI +RegisterAllServers( LPCWSTR szFileName, BOOL bRegister ) +{ + HRESULT hr = NOERROR; + + for( int i = 0; i < g_cTemplates; i++ ) + { + // get i'th template + // + const CFactoryTemplate *pT = &g_Templates[i]; + + DbgLog((LOG_TRACE, 2, TEXT("- - register %ls"), + (LPCWSTR)pT->m_Name )); + + // register CLSID and InprocServer32 + // + if( bRegister ) + { + hr = AMovieSetupRegisterServer( *(pT->m_ClsID) + , (LPCWSTR)pT->m_Name + , szFileName ); + } + else + { + hr = AMovieSetupUnregisterServer( *(pT->m_ClsID) ); + } + + // check final error for this pass + // and break loop if we failed + // + if( FAILED(hr) ) + break; + } + + return hr; +} +*/ + +//--------------------------------------------------------------------------- +// +// AMovieDllRegisterServer2() +// +// default ActiveMovie dll setup function +// - to use must be called from an exported +// function named DllRegisterServer() +// +// this function is table driven using the +// static members of the CFactoryTemplate +// class defined in the dll. +// +// it registers the Dll as the InprocServer32 +// and then calls the IAMovieSetup.Register +// method. +// +//--------------------------------------------------------------------------- + +//STDAPI +//AMovieDllRegisterServer2( BOOL bRegister ) +//{ +// HRESULT hr = NOERROR; +// +// DbgLog((LOG_TRACE, 2, TEXT("AMovieDllRegisterServer2()"))); +// +// // get file name (where g_hInst is the +// // instance handle of the filter dll) +// // +// WCHAR achFileName[MAX_PATH]; +// +// // WIN95 doesn't support GetModuleFileNameW +// // +// { +// char achTemp[MAX_PATH]; +// +// DbgLog((LOG_TRACE, 2, TEXT("- get module file name"))); +// +// // g_hInst handle is set in our dll entry point. Make sure +// // DllEntryPoint in dllentry.cpp is called +// ASSERT(g_hInst != 0); +// +// if( 0 == GetModuleFileNameA( g_hInst +// , achTemp +// , sizeof(achTemp) ) ) +// { +// // we've failed! +// DWORD dwerr = GetLastError(); +// return AmHresultFromWin32(dwerr); +// } +// +// MultiByteToWideChar( CP_ACP +// , 0L +// , achTemp +// , lstrlenA(achTemp) + 1 +// , achFileName +// , NUMELMS(achFileName) ); +// } +// +// // +// // first registering, register all OLE servers +// // +// if( bRegister ) +// { +// DbgLog((LOG_TRACE, 2, TEXT("- register OLE Servers"))); +// hr = RegisterAllServers( achFileName, TRUE ); +// } +// +// // +// // next, register/unregister all filters +// // +// +// if( SUCCEEDED(hr) ) +// { +// // init is ref counted so call just in case +// // we're being called cold. +// // +// DbgLog((LOG_TRACE, 2, TEXT("- CoInitialize"))); +// hr = CoInitialize( (LPVOID)NULL ); +// ASSERT( SUCCEEDED(hr) ); +// +// // get hold of IFilterMapper2 +// // +// DbgLog((LOG_TRACE, 2, TEXT("- obtain IFilterMapper2"))); +// IFilterMapper2 *pIFM2 = 0; +// IFilterMapper *pIFM = 0; +// hr = CoCreateInstance( CLSID_FilterMapper2 +// , NULL +// , CLSCTX_INPROC_SERVER +// , IID_IFilterMapper2 +// , (void **)&pIFM2 ); +// if(FAILED(hr)) +// { +// DbgLog((LOG_TRACE, 2, TEXT("- trying IFilterMapper instead"))); +// +// hr = CoCreateInstance( +// CLSID_FilterMapper, +// NULL, +// CLSCTX_INPROC_SERVER, +// IID_IFilterMapper, +// (void **)&pIFM); +// } +// if( SUCCEEDED(hr) ) +// { +// // scan through array of CFactoryTemplates +// // registering servers and filters. +// // +// DbgLog((LOG_TRACE, 2, TEXT("- register Filters"))); +// for( int i = 0; i < g_cTemplates; i++ ) +// { +// // get i'th template +// // +// const CFactoryTemplate *pT = &g_Templates[i]; +// +// if( NULL != pT->m_pAMovieSetup_Filter ) +// { +// DbgLog((LOG_TRACE, 2, TEXT("- - register %ls"), (LPCWSTR)pT->m_Name )); +// +// if(pIFM2) +// { +// hr = AMovieSetupRegisterFilter2( pT->m_pAMovieSetup_Filter, pIFM2, bRegister ); +// } +// else +// { +// hr = AMovieSetupRegisterFilter( pT->m_pAMovieSetup_Filter, pIFM, bRegister ); +// } +// } +// +// // check final error for this pass +// // and break loop if we failed +// // +// if( FAILED(hr) ) +// break; +// } +// +// // release interface +// // +// if(pIFM2) +// pIFM2->Release(); +// else +// pIFM->Release(); +// +// } +// +// // and clear up +// // +// CoFreeUnusedLibraries(); +// CoUninitialize(); +// } +// +// // +// // if unregistering, unregister all OLE servers +// // +// if( SUCCEEDED(hr) && !bRegister ) +// { +// DbgLog((LOG_TRACE, 2, TEXT("- register OLE Servers"))); +// hr = RegisterAllServers( achFileName, FALSE ); +// } +// +// DbgLog((LOG_TRACE, 2, TEXT("- return %0x"), hr)); +// return hr; +//} + + +//--------------------------------------------------------------------------- +// +// AMovieDllRegisterServer() +// +// default ActiveMovie dll setup function +// - to use must be called from an exported +// function named DllRegisterServer() +// +// this function is table driven using the +// static members of the CFactoryTemplate +// class defined in the dll. +// +// it registers the Dll as the InprocServer32 +// and then calls the IAMovieSetup.Register +// method. +// +//--------------------------------------------------------------------------- + + +//STDAPI +//AMovieDllRegisterServer( void ) +//{ +// HRESULT hr = NOERROR; +// +// // get file name (where g_hInst is the +// // instance handle of the filter dll) +// // +// WCHAR achFileName[MAX_PATH]; +// +// { +// // WIN95 doesn't support GetModuleFileNameW +// // +// char achTemp[MAX_PATH]; +// +// if( 0 == GetModuleFileNameA( g_hInst +// , achTemp +// , sizeof(achTemp) ) ) +// { +// // we've failed! +// DWORD dwerr = GetLastError(); +// return AmHresultFromWin32(dwerr); +// } +// +// MultiByteToWideChar( CP_ACP +// , 0L +// , achTemp +// , lstrlenA(achTemp) + 1 +// , achFileName +// , NUMELMS(achFileName) ); +// } +// +// // scan through array of CFactoryTemplates +// // registering servers and filters. +// // +// for( int i = 0; i < g_cTemplates; i++ ) +// { +// // get i'th template +// // +// const CFactoryTemplate *pT = &g_Templates[i]; +// +// // register CLSID and InprocServer32 +// // +// hr = AMovieSetupRegisterServer( *(pT->m_ClsID) +// , (LPCWSTR)pT->m_Name +// , achFileName ); +// +// // instantiate all servers and get hold of +// // IAMovieSetup, if implemented, and call +// // IAMovieSetup.Register() method +// // +// if( SUCCEEDED(hr) && (NULL != pT->m_lpfnNew) ) +// { +// // instantiate object +// // +// PAMOVIESETUP psetup; +// hr = CoCreateInstance( *(pT->m_ClsID) +// , 0 +// , CLSCTX_INPROC_SERVER +// , IID_IAMovieSetup +// , reinterpret_cast<void**>(&psetup) ); +// if( SUCCEEDED(hr) ) +// { +// hr = psetup->Unregister(); +// if( SUCCEEDED(hr) ) +// hr = psetup->Register(); +// psetup->Release(); +// } +// else +// { +// if( (E_NOINTERFACE == hr ) +// || (VFW_E_NEED_OWNER == hr ) ) +// hr = NOERROR; +// } +// } +// +// // check final error for this pass +// // and break loop if we failed +// // +// if( FAILED(hr) ) +// break; +// +// } // end-for +// +// return hr; +//} + + +//--------------------------------------------------------------------------- +// +// AMovieDllUnregisterServer() +// +// default ActiveMovie dll uninstall function +// - to use must be called from an exported +// function named DllRegisterServer() +// +// this function is table driven using the +// static members of the CFactoryTemplate +// class defined in the dll. +// +// it calls the IAMovieSetup.Unregister +// method and then unregisters the Dll +// as the InprocServer32 +// +//--------------------------------------------------------------------------- + +//STDAPI +//AMovieDllUnregisterServer() +//{ +// // initialize return code +// // +// HRESULT hr = NOERROR; +// +// // scan through CFactory template and unregister +// // all OLE servers and filters. +// // +// for( int i = g_cTemplates; i--; ) +// { +// // get i'th template +// // +// const CFactoryTemplate *pT = &g_Templates[i]; +// +// // check method exists +// // +// if( NULL != pT->m_lpfnNew ) +// { +// // instantiate object +// // +// PAMOVIESETUP psetup; +// hr = CoCreateInstance( *(pT->m_ClsID) +// , 0 +// , CLSCTX_INPROC_SERVER +// , IID_IAMovieSetup +// , reinterpret_cast<void**>(&psetup) ); +// if( SUCCEEDED(hr) ) +// { +// hr = psetup->Unregister(); +// psetup->Release(); +// } +// else +// { +// if( (E_NOINTERFACE == hr ) +// || (VFW_E_NEED_OWNER == hr ) ) +// hr = NOERROR; +// } +// } +// +// // unregister CLSID and InprocServer32 +// // +// if( SUCCEEDED(hr) ) +// { +// hr = AMovieSetupUnregisterServer( *(pT->m_ClsID) ); +// } +// +// // check final error for this pass +// // and break loop if we failed +// // +// if( FAILED(hr) ) +// break; +// } +// +// return hr; +//} diff --git a/Src/Plugins/Input/in_dshow/base/dllsetup.h b/Src/Plugins/Input/in_dshow/base/dllsetup.h new file mode 100644 index 00000000..aaac2ec5 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/dllsetup.h @@ -0,0 +1,46 @@ +//------------------------------------------------------------------------------ +// File: DllSetup.h +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// To be self registering, OLE servers must +// export functions named DllRegisterServer +// and DllUnregisterServer. To allow use of +// custom and default implementations the +// defaults are named AMovieDllRegisterServer +// and AMovieDllUnregisterServer. +// +// To the use the default implementation you +// must provide stub functions. +// +// i.e. STDAPI DllRegisterServer() +// { +// return AMovieDllRegisterServer(); +// } +// +// STDAPI DllUnregisterServer() +// { +// return AMovieDllUnregisterServer(); +// } +// +// +// AMovieDllRegisterServer calls IAMovieSetup.Register(), and +// AMovieDllUnregisterServer calls IAMovieSetup.Unregister(). + +STDAPI AMovieDllRegisterServer2( BOOL ); +STDAPI AMovieDllRegisterServer(); +STDAPI AMovieDllUnregisterServer(); + +// helper functions +STDAPI EliminateSubKey( HKEY, LPCTSTR ); + + +STDAPI +AMovieSetupRegisterFilter2( const AMOVIESETUP_FILTER * const psetupdata + , IFilterMapper2 * pIFM2 + , BOOL bRegister ); + diff --git a/Src/Plugins/Input/in_dshow/base/dxmperf.h b/Src/Plugins/Input/in_dshow/base/dxmperf.h new file mode 100644 index 00000000..dc58ad72 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/dxmperf.h @@ -0,0 +1,250 @@ +//------------------------------------------------------------------------------ +// File: DXMPerf.h +// +// Desc: Macros for DirectShow performance logging. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef _DXMPERF_H_ +#define _DXMPERF_H_ + +#include <perfstruct.h> +#include "perflog.h" + +#ifdef _IA64_ +extern "C" unsigned __int64 __getReg( int whichReg ); +#pragma intrinsic(__getReg) +#endif // _IA64_ + + +inline ULONGLONG _RDTSC( void ) { +#ifdef _X86_ + LARGE_INTEGER li; + __asm { + _emit 0x0F + _emit 0x31 + mov li.LowPart,eax + mov li.HighPart,edx + } + return li.QuadPart; + +#if 0 // This isn't tested yet + +#elif defined (_IA64_) + +#define INL_REGID_APITC 3116 + return __getReg( INL_REGID_APITC ); + +#endif // 0 + +#else // unsupported platform + // not implemented on non x86/IA64 platforms + return 0; +#endif // _X86_/_IA64_ +} + +#define DXMPERF_VIDEOREND 0x00000001 +#define DXMPERF_AUDIOGLITCH 0x00000002 +//#define GETTIME_BIT 0x00000001 +//#define AUDIOREND_BIT 0x00000004 +//#define FRAMEDROP_BIT 0x00000008 +#define AUDIOBREAK_BIT 0x00000010 +#define DXMPERF_AUDIORECV 0x00000020 +#define DXMPERF_AUDIOSLAVE 0x00000040 +#define DXMPERF_AUDIOBREAK 0x00000080 + +#define PERFLOG_CTOR( name, iface ) +#define PERFLOG_DTOR( name, iface ) +#define PERFLOG_DELIVER( name, source, dest, sample, pmt ) +#define PERFLOG_RECEIVE( name, source, dest, sample, pmt ) +#define PERFLOG_RUN( name, iface, time, oldstate ) +#define PERFLOG_PAUSE( name, iface, oldstate ) +#define PERFLOG_STOP( name, iface, oldstate ) +#define PERFLOG_JOINGRAPH( name, iface, graph ) +#define PERFLOG_GETBUFFER( allocator, sample ) +#define PERFLOG_RELBUFFER( allocator, sample ) +#define PERFLOG_CONNECT( connector, connectee, status, pmt ) +#define PERFLOG_RXCONNECT( connector, connectee, status, pmt ) +#define PERFLOG_DISCONNECT( disconnector, disconnectee, status ) + +#define PERFLOG_GETTIME( clock, time ) /*{ \ + PERFINFO_WMI_GETTIME perfData; \ + if (NULL != g_pTraceEvent) { \ + memset( &perfData, 0, sizeof( perfData ) ); \ + perfData.header.Size = sizeof( perfData ); \ + perfData.header.Flags = WNODE_FLAG_TRACED_GUID; \ + perfData.header.Guid = GUID_GETTIME; \ + perfData.data.cycleCounter = _RDTSC(); \ + perfData.data.dshowClock = (ULONGLONG) (time); \ + if (g_perfMasks[GETTIME_INDEX] & GETTIME_BIT) \ + (*g_pTraceEvent)( g_traceHandle, (PEVENT_TRACE_HEADER) &perfData ); \ + } \ + }*/ + +#define PERFLOG_AUDIOREND( clocktime, sampletime, psample, bytetime, cbytes ) /*{ \ + PERFINFO_WMI_AVREND perfData; \ + if (NULL != g_pTraceEvent) { \ + memset( &perfData, 0, sizeof( perfData ) ); \ + perfData.header.Size = sizeof( perfData ); \ + perfData.header.Flags = WNODE_FLAG_TRACED_GUID; \ + perfData.header.Guid = GUID_AUDIOREND; \ + perfData.data.cycleCounter = _RDTSC(); \ + perfData.data.dshowClock = (clocktime); \ + perfData.data.sampleTime = (sampletime); \ + if (g_perfMasks[AUDIOREND_INDEX] & AUDIOREND_BIT) \ + (*g_pTraceEvent)( g_traceHandle, (PEVENT_TRACE_HEADER) &perfData ); \ + } \ + }*/ + +#define PERFLOG_AUDIORECV(StreamTime,SampleStart,SampleStop,Discontinuity,Duration) \ + if (PerflogEnableFlags & DXMPERF_AUDIORECV) { \ + PERFINFO_WMI_AUDIORECV perfData; \ + memset( &perfData, 0, sizeof( perfData ) ); \ + perfData.header.Size = sizeof( perfData ); \ + perfData.header.Flags = WNODE_FLAG_TRACED_GUID; \ + perfData.header.Guid = GUID_AUDIORECV; \ + perfData.data.streamTime = StreamTime; \ + perfData.data.sampleStart = SampleStart; \ + perfData.data.sampleStop = SampleStop; \ + perfData.data.discontinuity = Discontinuity; \ + perfData.data.hwduration = Duration; \ + PerflogTraceEvent((PEVENT_TRACE_HEADER) &perfData); \ + } + +#define PERFLOG_AUDIOSLAVE(MasterClock,SlaveClock,ErrorAccum,LastHighErrorSeen,LastLowErrorSeen) \ + if (PerflogEnableFlags & DXMPERF_AUDIOSLAVE) { \ + PERFINFO_WMI_AUDIOSLAVE perfData; \ + memset( &perfData, 0, sizeof( perfData ) ); \ + perfData.header.Size = sizeof( perfData ); \ + perfData.header.Flags = WNODE_FLAG_TRACED_GUID; \ + perfData.header.Guid = GUID_AUDIOSLAVE; \ + perfData.data.masterClock = MasterClock; \ + perfData.data.slaveClock = SlaveClock; \ + perfData.data.errorAccum = ErrorAccum; \ + perfData.data.lastHighErrorSeen = LastHighErrorSeen;\ + perfData.data.lastLowErrorSeen = LastLowErrorSeen; \ + PerflogTraceEvent((PEVENT_TRACE_HEADER) &perfData); \ + } + +#define PERFLOG_AUDIOADDBREAK(IterNextWrite,OffsetNextWrite,IterWrite,OffsetWrite) \ + if (PerflogEnableFlags & DXMPERF_AUDIOBREAK) { \ + PERFINFO_WMI_AUDIOADDBREAK perfData; \ + memset( &perfData, 0, sizeof( perfData ) ); \ + perfData.header.Size = sizeof( perfData ); \ + perfData.header.Flags = WNODE_FLAG_TRACED_GUID; \ + perfData.header.Guid = GUID_AUDIOADDBREAK; \ + perfData.data.iterNextWrite = IterNextWrite; \ + perfData.data.offsetNextWrite = OffsetNextWrite; \ + perfData.data.iterWrite = IterWrite; \ + perfData.data.offsetWrite = OffsetWrite; \ + PerflogTraceEvent((PEVENT_TRACE_HEADER) &perfData); \ + } + +#define PERFLOG_VIDEOREND( sampletime, clocktime, psample ) \ + if (PerflogEnableFlags & DXMPERF_VIDEOREND) { \ + PERFINFO_WMI_AVREND perfData; \ + memset( &perfData, 0, sizeof( perfData ) ); \ + perfData.header.Size = sizeof( perfData ); \ + perfData.header.Flags = WNODE_FLAG_TRACED_GUID; \ + perfData.header.Guid = GUID_VIDEOREND; \ + perfData.data.cycleCounter = _RDTSC(); \ + perfData.data.dshowClock = (clocktime); \ + perfData.data.sampleTime = (sampletime); \ + PerflogTraceEvent ((PEVENT_TRACE_HEADER) &perfData); \ + } + +#define PERFLOG_AUDIOGLITCH( instance, glitchtype, currenttime, previoustime ) \ + if (PerflogEnableFlags & DXMPERF_AUDIOGLITCH) { \ + PERFINFO_WMI_AUDIOGLITCH perfData; \ + memset( &perfData, 0, sizeof( perfData ) ); \ + perfData.header.Size = sizeof( perfData ); \ + perfData.header.Flags = WNODE_FLAG_TRACED_GUID; \ + perfData.header.Guid = GUID_DSOUNDGLITCH; \ + perfData.data.cycleCounter = _RDTSC(); \ + perfData.data.glitchType = (glitchtype); \ + perfData.data.sampleTime = (currenttime); \ + perfData.data.previousTime = (previoustime); \ + perfData.data.instanceId = (instance); \ + PerflogTraceEvent ((PEVENT_TRACE_HEADER) &perfData); \ + } + +#define PERFLOG_FRAMEDROP( sampletime, clocktime, psample, renderer ) /*{ \ + PERFINFO_WMI_FRAMEDROP perfData; \ + if (NULL != g_pTraceEvent) { \ + memset( &perfData, 0, sizeof( perfData ) ); \ + perfData.header.Size = sizeof( perfData ); \ + perfData.header.Flags = WNODE_FLAG_TRACED_GUID; \ + perfData.header.Guid = GUID_FRAMEDROP; \ + perfData.data.cycleCounter = _RDTSC(); \ + perfData.data.dshowClock = (clocktime); \ + perfData.data.frameTime = (sampletime); \ + if (g_perfMasks[FRAMEDROP_INDEX] & FRAMEDROP_BIT) \ + (*g_pTraceEvent)( g_traceHandle, (PEVENT_TRACE_HEADER) &perfData ); \ + } \ + }*/ + +/* +#define PERFLOG_AUDIOBREAK( nextwrite, writepos, msecs ) { \ + PERFINFO_WMI_AUDIOBREAK perfData; \ + if (NULL != g_pTraceEvent) { \ + memset( &perfData, 0, sizeof( perfData ) ); \ + perfData.header.Size = sizeof( perfData ); \ + perfData.header.Flags = WNODE_FLAG_TRACED_GUID; \ + perfData.header.Guid = GUID_AUDIOBREAK; \ + perfData.data.cycleCounter = _RDTSC(); \ + perfData.data.dshowClock = (writepos); \ + perfData.data.sampleTime = (nextwrite); \ + perfData.data.sampleDuration = (msecs); \ + if (g_perfMasks[AUDIOBREAK_INDEX] & AUDIOBREAK_BIT) \ + (*g_pTraceEvent)( g_traceHandle, (PEVENT_TRACE_HEADER) &perfData ); \ + } \ + } +*/ + +#define PERFLOG_AUDIOBREAK( nextwrite, writepos, msecs ) \ + if (PerflogEnableFlags & AUDIOBREAK_BIT) { \ + PERFINFO_WMI_AUDIOBREAK perfData; \ + memset( &perfData, 0, sizeof( perfData ) ); \ + perfData.header.Size = sizeof( perfData ); \ + perfData.header.Flags = WNODE_FLAG_TRACED_GUID; \ + perfData.header.Guid = GUID_AUDIOBREAK; \ + perfData.data.cycleCounter = _RDTSC(); \ + perfData.data.dshowClock = (writepos); \ + perfData.data.sampleTime = (nextwrite); \ + perfData.data.sampleDuration = (msecs); \ + PerflogTraceEvent ((PEVENT_TRACE_HEADER) &perfData); \ + } \ + + +inline +VOID PERFLOG_STREAMTRACE( + ULONG Level, + ULONG Id, + ULONGLONG DShowClock, + ULONGLONG Data1, + ULONGLONG Data2, + ULONGLONG Data3, + ULONGLONG Data4 + ) +{ + if (Level <= PerflogModuleLevel) + { + PERFINFO_WMI_STREAMTRACE perfData; + memset( &perfData, 0, sizeof( perfData ) ); + perfData.header.Size = sizeof( perfData ); + perfData.header.Flags = WNODE_FLAG_TRACED_GUID; + perfData.header.Guid = GUID_STREAMTRACE; + perfData.data.dshowClock = DShowClock; + perfData.data.id = Id; + perfData.data.data[0] = Data1; + perfData.data.data[1] = Data2; + perfData.data.data[2] = Data3; + perfData.data.data[3] = Data4; + PerflogTraceEvent((PEVENT_TRACE_HEADER) &perfData); + } +} + + +#endif // _DXMPERF_H_ diff --git a/Src/Plugins/Input/in_dshow/base/fourcc.h b/Src/Plugins/Input/in_dshow/base/fourcc.h new file mode 100644 index 00000000..f4f71e96 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/fourcc.h @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// File: FourCC.h +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// FOURCCMap +// +// provides a mapping between old-style multimedia format DWORDs +// and new-style GUIDs. +// +// A range of 4 billion GUIDs has been allocated to ensure that this +// mapping can be done straightforwardly one-to-one in both directions. +// +// January 95 + + +#ifndef __FOURCC__ +#define __FOURCC__ + + +// Multimedia format types are marked with DWORDs built from four 8-bit +// chars and known as FOURCCs. New multimedia AM_MEDIA_TYPE definitions include +// a subtype GUID. In order to simplify the mapping, GUIDs in the range: +// XXXXXXXX-0000-0010-8000-00AA00389B71 +// are reserved for FOURCCs. + +class FOURCCMap : public GUID +{ + +public: + FOURCCMap(); + FOURCCMap(DWORD Fourcc); + FOURCCMap(const GUID *); + + + DWORD GetFOURCC(void); + void SetFOURCC(DWORD fourcc); + void SetFOURCC(const GUID *); + +private: + void InitGUID(); +}; + +#define GUID_Data2 0 +#define GUID_Data3 0x10 +#define GUID_Data4_1 0xaa000080 +#define GUID_Data4_2 0x719b3800 + +inline void +FOURCCMap::InitGUID() { + Data2 = GUID_Data2; + Data3 = GUID_Data3; + ((DWORD *)Data4)[0] = GUID_Data4_1; + ((DWORD *)Data4)[1] = GUID_Data4_2; +} + +inline +FOURCCMap::FOURCCMap() { + InitGUID(); + SetFOURCC( DWORD(0)); +} + +inline +FOURCCMap::FOURCCMap(DWORD fourcc) +{ + InitGUID(); + SetFOURCC(fourcc); +} + +inline +FOURCCMap::FOURCCMap(const GUID * pGuid) +{ + InitGUID(); + SetFOURCC(pGuid); +} + +inline void +FOURCCMap::SetFOURCC(const GUID * pGuid) +{ + FOURCCMap * p = (FOURCCMap*) pGuid; + SetFOURCC(p->GetFOURCC()); +} + +inline void +FOURCCMap::SetFOURCC(DWORD fourcc) +{ + Data1 = fourcc; +} + +inline DWORD +FOURCCMap::GetFOURCC(void) +{ + return Data1; +} + +#endif /* __FOURCC__ */ + diff --git a/Src/Plugins/Input/in_dshow/base/measure.h b/Src/Plugins/Input/in_dshow/base/measure.h new file mode 100644 index 00000000..0babc860 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/measure.h @@ -0,0 +1,222 @@ +//------------------------------------------------------------------------------ +// File: Measure.h +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +/* + The idea is to pepper the source code with interesting measurements and + have the last few thousand of these recorded in a circular buffer that + can be post-processed to give interesting numbers. + + WHAT THE LOG LOOKS LIKE: + + Time (sec) Type Delta Incident_Name + 0.055,41 NOTE -. Incident Nine - Another note + 0.055,42 NOTE 0.000,01 Incident Nine - Another note + 0.055,44 NOTE 0.000,02 Incident Nine - Another note + 0.055,45 STOP -. Incident Eight - Also random + 0.055,47 START -. Incident Seven - Random + 0.055,49 NOTE 0.000,05 Incident Nine - Another note + ------- <etc. there is a lot of this> ---------------- + 0.125,60 STOP 0.000,03 Msr_Stop + 0.125,62 START -. Msr_Start + 0.125,63 START -. Incident Two - Start/Stop + 0.125,65 STOP 0.000,03 Msr_Start + 0.125,66 START -. Msr_Stop + 0.125,68 STOP 0.000,05 Incident Two - Start/Stop + 0.125,70 STOP 0.000,04 Msr_Stop + 0.125,72 START -. Msr_Start + 0.125,73 START -. Incident Two - Start/Stop + 0.125,75 STOP 0.000,03 Msr_Start + 0.125,77 START -. Msr_Stop + 0.125,78 STOP 0.000,05 Incident Two - Start/Stop + 0.125,80 STOP 0.000,03 Msr_Stop + 0.125,81 NOTE -. Incident Three - single Note + 0.125,83 START -. Incident Four - Start, no stop + 0.125,85 START -. Incident Five - Single Start/Stop + 0.125,87 STOP 0.000,02 Incident Five - Single Start/Stop + +Number Average StdDev Smallest Largest Incident_Name + 10 0.000,58 0.000,10 0.000,55 0.000,85 Incident One - Note + 50 0.000,05 0.000,00 0.000,05 0.000,05 Incident Two - Start/Stop + 1 -. -. -. -. Incident Three - single Note + 0 -. -. -. -. Incident Four - Start, no stop + 1 0.000,02 -. 0.000,02 0.000,02 Incident Five - Single Start/Stop + 0 -. -. -. -. Incident Six - zero occurrences + 100 0.000,25 0.000,12 0.000,02 0.000,62 Incident Seven - Random + 100 0.000,79 0.000,48 0.000,02 0.001,92 Incident Eight - Also random + 5895 0.000,01 0.000,01 0.000,01 0.000,56 Incident Nine - Another note + 10 0.000,03 0.000,00 0.000,03 0.000,04 Msr_Note + 50 0.000,03 0.000,00 0.000,03 0.000,04 Msr_Start + 50 0.000,04 0.000,03 0.000,03 0.000,31 Msr_Stop + + WHAT IT MEANS: + The log shows what happened and when. Each line shows the time at which + something happened (see WHAT YOU CODE below) what it was that happened + and (if approporate) the time since the corresponding previous event + (that's the delta column). + + The statistics show how many times each event occurred, what the average + delta time was, also the standard deviation, largest and smalles delta. + + WHAT YOU CODE: + + Before anything else executes: - register your ids + + int id1 = Msr_Register("Incident One - Note"); + int id2 = Msr_Register("Incident Two - Start/Stop"); + int id3 = Msr_Register("Incident Three - single Note"); + etc. + + At interesting moments: + + // To measure a repetitive event - e.g. end of bitblt to screen + Msr_Note(Id9); // e.g. "video frame hiting the screen NOW!" + + or + + // To measure an elapsed time e.g. time taken to decode an MPEG B-frame + Msr_Start(Id2); // e.g. "Starting to decode MPEG B-frame" + . . . + MsrStop(Id2); // "Finished MPEG decode" + + At the end: + + HANDLE hFile; + hFile = CreateFile("Perf.log", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); + Msr_Dump(hFile); // This writes the log out to the file + CloseHandle(hFile); + + or + + Msr_Dump(NULL); // This writes it to DbgLog((LOG_TRACE,0, ... )); + // but if you are writing it out to the debugger + // then the times are probably all garbage because + // the debugger can make things run awfully slow. + + A given id should be used either for start / stop or Note calls. If Notes + are mixed in with Starts and Stops their statistics will be gibberish. + + If you code the calls in upper case i.e. MSR_START(idMunge); then you get + macros which will turn into nothing unless PERF is defined. + + You can reset the statistical counts for a given id by calling Reset(Id). + They are reset by default at the start. + It logs Reset as a special incident, so you can see it in the log. + + The log is a circular buffer in storage (to try to minimise disk I/O). + It overwrites the oldest entries once full. The statistics include ALL + incidents since the last Reset, whether still visible in the log or not. +*/ + +#ifndef __MEASURE__ +#define __MEASURE__ + +#ifdef PERF +#define MSR_INIT() Msr_Init() +#define MSR_TERMINATE() Msr_Terminate() +#define MSR_REGISTER(a) Msr_Register(a) +#define MSR_RESET(a) Msr_Reset(a) +#define MSR_CONTROL(a) Msr_Control(a) +#define MSR_START(a) Msr_Start(a) +#define MSR_STOP(a) Msr_Stop(a) +#define MSR_NOTE(a) Msr_Note(a) +#define MSR_INTEGER(a,b) Msr_Integer(a,b) +#define MSR_DUMP(a) Msr_Dump(a) +#define MSR_DUMPSTATS(a) Msr_DumpStats(a) +#else +#define MSR_INIT() ((void)0) +#define MSR_TERMINATE() ((void)0) +#define MSR_REGISTER(a) 0 +#define MSR_RESET(a) ((void)0) +#define MSR_CONTROL(a) ((void)0) +#define MSR_START(a) ((void)0) +#define MSR_STOP(a) ((void)0) +#define MSR_NOTE(a) ((void)0) +#define MSR_INTEGER(a,b) ((void)0) +#define MSR_DUMP(a) ((void)0) +#define MSR_DUMPSTATS(a) ((void)0) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// This must be called first - (called by the DllEntry) + +void WINAPI Msr_Init(void); + + +// Call this last to clean up (or just let it fall off the end - who cares?) + +void WINAPI Msr_Terminate(void); + + +// Call this to get an Id for an "incident" that you can pass to Start, Stop or Note +// everything that's logged is called an "incident". + +int WINAPI Msr_Register(__in LPTSTR Incident); + + +// Reset the statistical counts for an incident + +void WINAPI Msr_Reset(int Id); + + +// Reset all the counts for all incidents +#define MSR_RESET_ALL 0 +#define MSR_PAUSE 1 +#define MSR_RUN 2 + +void WINAPI Msr_Control(int iAction); + + +// log the start of an operation + +void WINAPI Msr_Start(int Id); + + +// log the end of an operation + +void WINAPI Msr_Stop(int Id); + + +// log a one-off or repetitive operation + +void WINAPI Msr_Note(int Id); + + +// log an integer (on which we can see statistics later) +void WINAPI Msr_Integer(int Id, int n); + + +// print out all the vaialable log (it may have wrapped) and then the statistics. +// When the log wraps you lose log but the statistics are still complete. +// hFIle==NULL => use DbgLog +// otherwise hFile must have come from CreateFile or OpenFile. + +void WINAPI Msr_Dump(HANDLE hFile); + + +// just dump the statistics - never mind the log + +void WINAPI Msr_DumpStats(HANDLE hFile); + +// Type definitions in case you want to declare a pointer to the dump functions +// (makes it a trifle easier to do dynamic linking +// i.e. LoadModule, GetProcAddress and call that) + +// Typedefs so can declare MSR_DUMPPROC *MsrDumpStats; or whatever +typedef void WINAPI MSR_DUMPPROC(HANDLE hFile); +typedef void WINAPI MSR_CONTROLPROC(int iAction); + + +#ifdef __cplusplus +} +#endif + +#endif // __MEASURE__ diff --git a/Src/Plugins/Input/in_dshow/base/msgthrd.h b/Src/Plugins/Input/in_dshow/base/msgthrd.h new file mode 100644 index 00000000..208f03c8 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/msgthrd.h @@ -0,0 +1,120 @@ +//------------------------------------------------------------------------------ +// File: MsgThrd.h +// +// Desc: DirectShow base classes - provides support for a worker thread +// class to which one can asynchronously post messages. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// Message class - really just a structure. +// +class CMsg { +public: + UINT uMsg; + DWORD dwFlags; + LPVOID lpParam; + CAMEvent *pEvent; + + CMsg(UINT u, DWORD dw, __inout_opt LPVOID lp, __in_opt CAMEvent *pEvnt) + : uMsg(u), dwFlags(dw), lpParam(lp), pEvent(pEvnt) {} + + CMsg() + : uMsg(0), dwFlags(0L), lpParam(NULL), pEvent(NULL) {} +}; + +// This is the actual thread class. It exports all the usual thread control +// functions. The created thread is different from a normal WIN32 thread in +// that it is prompted to perform particaular tasks by responding to messages +// posted to its message queue. +// +class AM_NOVTABLE CMsgThread { +private: + static DWORD WINAPI DefaultThreadProc(__inout LPVOID lpParam); + DWORD m_ThreadId; + HANDLE m_hThread; + +protected: + + // if you want to override GetThreadMsg to block on other things + // as well as this queue, you need access to this + CGenericList<CMsg> m_ThreadQueue; + CCritSec m_Lock; + HANDLE m_hSem; + LONG m_lWaiting; + +public: + CMsgThread() + : m_ThreadId(0), + m_hThread(NULL), + m_lWaiting(0), + m_hSem(NULL), + // make a list with a cache of 5 items + m_ThreadQueue(NAME("MsgThread list"), 5) + { + } + + ~CMsgThread(); + // override this if you want to block on other things as well + // as the message loop + void virtual GetThreadMsg(__out CMsg *msg); + + // override this if you want to do something on thread startup + virtual void OnThreadInit() { + }; + + BOOL CreateThread(); + + BOOL WaitForThreadExit(__out LPDWORD lpdwExitCode) { + if (m_hThread != NULL) { + WaitForSingleObject(m_hThread, INFINITE); + return GetExitCodeThread(m_hThread, lpdwExitCode); + } + return FALSE; + } + + DWORD ResumeThread() { + return ::ResumeThread(m_hThread); + } + + DWORD SuspendThread() { + return ::SuspendThread(m_hThread); + } + + int GetThreadPriority() { + return ::GetThreadPriority(m_hThread); + } + + BOOL SetThreadPriority(int nPriority) { + return ::SetThreadPriority(m_hThread, nPriority); + } + + HANDLE GetThreadHandle() { + return m_hThread; + } + + DWORD GetThreadId() { + return m_ThreadId; + } + + + void PutThreadMsg(UINT uMsg, DWORD dwMsgFlags, + __in_opt LPVOID lpMsgParam, __in_opt CAMEvent *pEvent = NULL) { + CAutoLock lck(&m_Lock); + CMsg* pMsg = new CMsg(uMsg, dwMsgFlags, lpMsgParam, pEvent); + m_ThreadQueue.AddTail(pMsg); + if (m_lWaiting != 0) { + ReleaseSemaphore(m_hSem, m_lWaiting, 0); + m_lWaiting = 0; + } + } + + // This is the function prototype of the function that the client + // supplies. It is always called on the created thread, never on + // the creator thread. + // + virtual LRESULT ThreadMessageProc( + UINT uMsg, DWORD dwFlags, __inout_opt LPVOID lpParam, __in_opt CAMEvent *pEvent) = 0; +}; + diff --git a/Src/Plugins/Input/in_dshow/base/mtype.cpp b/Src/Plugins/Input/in_dshow/base/mtype.cpp new file mode 100644 index 00000000..fa0e39ed --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/mtype.cpp @@ -0,0 +1,478 @@ +//------------------------------------------------------------------------------ +// File: MType.cpp +// +// Desc: DirectShow base classes - implements a class that holds and +// manages media type information. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// helper class that derived pin objects can use to compare media +// types etc. Has same data members as the struct AM_MEDIA_TYPE defined +// in the streams IDL file, but also has (non-virtual) functions + +#include <streams.h> +#include <mmreg.h> + +CMediaType::~CMediaType(){ + FreeMediaType(*this); +} + + +CMediaType::CMediaType() +{ + InitMediaType(); +} + + +CMediaType::CMediaType(const GUID * type) +{ + InitMediaType(); + majortype = *type; +} + + +// copy constructor does a deep copy of the format block + +CMediaType::CMediaType(const AM_MEDIA_TYPE& rt, __out_opt HRESULT* phr) +{ + HRESULT hr = CopyMediaType(this, &rt); + if (FAILED(hr) && (NULL != phr)) { + *phr = hr; + } +} + + +CMediaType::CMediaType(const CMediaType& rt, __out_opt HRESULT* phr) +{ + HRESULT hr = CopyMediaType(this, &rt); + if (FAILED(hr) && (NULL != phr)) { + *phr = hr; + } +} + + +// this class inherits publicly from AM_MEDIA_TYPE so the compiler could generate +// the following assignment operator itself, however it could introduce some +// memory conflicts and leaks in the process because the structure contains +// a dynamically allocated block (pbFormat) which it will not copy correctly + +CMediaType& +CMediaType::operator=(const AM_MEDIA_TYPE& rt) +{ + Set(rt); + return *this; +} + +CMediaType& +CMediaType::operator=(const CMediaType& rt) +{ + *this = (AM_MEDIA_TYPE &) rt; + return *this; +} + +BOOL +CMediaType::operator == (const CMediaType& rt) const +{ + // I don't believe we need to check sample size or + // temporal compression flags, since I think these must + // be represented in the type, subtype and format somehow. They + // are pulled out as separate flags so that people who don't understand + // the particular format representation can still see them, but + // they should duplicate information in the format block. + + return ((IsEqualGUID(majortype,rt.majortype) == TRUE) && + (IsEqualGUID(subtype,rt.subtype) == TRUE) && + (IsEqualGUID(formattype,rt.formattype) == TRUE) && + (cbFormat == rt.cbFormat) && + ( (cbFormat == 0) || + pbFormat != NULL && rt.pbFormat != NULL && + (memcmp(pbFormat, rt.pbFormat, cbFormat) == 0))); +} + + +BOOL +CMediaType::operator != (const CMediaType& rt) const +{ + /* Check to see if they are equal */ + + if (*this == rt) { + return FALSE; + } + return TRUE; +} + + +HRESULT +CMediaType::Set(const CMediaType& rt) +{ + return Set((AM_MEDIA_TYPE &) rt); +} + + +HRESULT +CMediaType::Set(const AM_MEDIA_TYPE& rt) +{ + if (&rt != this) { + FreeMediaType(*this); + HRESULT hr = CopyMediaType(this, &rt); + if (FAILED(hr)) { + return E_OUTOFMEMORY; + } + } + + return S_OK; +} + + +BOOL +CMediaType::IsValid() const +{ + return (!IsEqualGUID(majortype,GUID_NULL)); +} + + +void +CMediaType::SetType(const GUID* ptype) +{ + majortype = *ptype; +} + + +void +CMediaType::SetSubtype(const GUID* ptype) +{ + subtype = *ptype; +} + + +ULONG +CMediaType::GetSampleSize() const { + if (IsFixedSize()) { + return lSampleSize; + } else { + return 0; + } +} + + +void +CMediaType::SetSampleSize(ULONG sz) { + if (sz == 0) { + SetVariableSize(); + } else { + bFixedSizeSamples = TRUE; + lSampleSize = sz; + } +} + + +void +CMediaType::SetVariableSize() { + bFixedSizeSamples = FALSE; +} + + +void +CMediaType::SetTemporalCompression(BOOL bCompressed) { + bTemporalCompression = bCompressed; +} + +BOOL +CMediaType::SetFormat(__in_bcount(cb) BYTE * pformat, ULONG cb) +{ + if (NULL == AllocFormatBuffer(cb)) + return(FALSE); + + ASSERT(pbFormat); + memcpy(pbFormat, pformat, cb); + return(TRUE); +} + + +// set the type of the media type format block, this type defines what you +// will actually find in the format pointer. For example FORMAT_VideoInfo or +// FORMAT_WaveFormatEx. In the future this may be an interface pointer to a +// property set. Before sending out media types this should be filled in. + +void +CMediaType::SetFormatType(const GUID *pformattype) +{ + formattype = *pformattype; +} + + +// reset the format buffer + +void CMediaType::ResetFormatBuffer() +{ + if (cbFormat) { + CoTaskMemFree((PVOID)pbFormat); + } + cbFormat = 0; + pbFormat = NULL; +} + + +// allocate length bytes for the format and return a read/write pointer +// If we cannot allocate the new block of memory we return NULL leaving +// the original block of memory untouched (as does ReallocFormatBuffer) + +BYTE* +CMediaType::AllocFormatBuffer(ULONG length) +{ + ASSERT(length); + + // do the types have the same buffer size + + if (cbFormat == length) { + return pbFormat; + } + + // allocate the new format buffer + + BYTE *pNewFormat = (PBYTE)CoTaskMemAlloc(length); + if (pNewFormat == NULL) { + if (length <= cbFormat) return pbFormat; //reuse the old block anyway. + return NULL; + } + + // delete the old format + + if (cbFormat != 0) { + ASSERT(pbFormat); + CoTaskMemFree((PVOID)pbFormat); + } + + cbFormat = length; + pbFormat = pNewFormat; + return pbFormat; +} + + +// reallocate length bytes for the format and return a read/write pointer +// to it. We keep as much information as we can given the new buffer size +// if this fails the original format buffer is left untouched. The caller +// is responsible for ensuring the size of memory required is non zero + +BYTE* +CMediaType::ReallocFormatBuffer(ULONG length) +{ + ASSERT(length); + + // do the types have the same buffer size + + if (cbFormat == length) { + return pbFormat; + } + + // allocate the new format buffer + + BYTE *pNewFormat = (PBYTE)CoTaskMemAlloc(length); + if (pNewFormat == NULL) { + if (length <= cbFormat) return pbFormat; //reuse the old block anyway. + return NULL; + } + + // copy any previous format (or part of if new is smaller) + // delete the old format and replace with the new one + + if (cbFormat != 0) { + ASSERT(pbFormat); + memcpy(pNewFormat,pbFormat,min(length,cbFormat)); + CoTaskMemFree((PVOID)pbFormat); + } + + cbFormat = length; + pbFormat = pNewFormat; + return pNewFormat; +} + +// initialise a media type structure + +void CMediaType::InitMediaType() +{ + ZeroMemory((PVOID)this, sizeof(*this)); + lSampleSize = 1; + bFixedSizeSamples = TRUE; +} + + +// a partially specified media type can be passed to IPin::Connect +// as a constraint on the media type used in the connection. +// the type, subtype or format type can be null. +BOOL +CMediaType::IsPartiallySpecified(void) const +{ + if ((majortype == GUID_NULL) || + (formattype == GUID_NULL)) { + return TRUE; + } else { + return FALSE; + } +} + +BOOL +CMediaType::MatchesPartial(const CMediaType* ppartial) const +{ + if ((ppartial->majortype != GUID_NULL) && + (majortype != ppartial->majortype)) { + return FALSE; + } + if ((ppartial->subtype != GUID_NULL) && + (subtype != ppartial->subtype)) { + return FALSE; + } + + if (ppartial->formattype != GUID_NULL) { + // if the format block is specified then it must match exactly + if (formattype != ppartial->formattype) { + return FALSE; + } + if (cbFormat != ppartial->cbFormat) { + return FALSE; + } + if ((cbFormat != 0) && + (memcmp(pbFormat, ppartial->pbFormat, cbFormat) != 0)) { + return FALSE; + } + } + + return TRUE; + +} + + + +// general purpose function to delete a heap allocated AM_MEDIA_TYPE structure +// which is useful when calling IEnumMediaTypes::Next as the interface +// implementation allocates the structures which you must later delete +// the format block may also be a pointer to an interface to release + +void WINAPI DeleteMediaType(__inout_opt AM_MEDIA_TYPE *pmt) +{ + // allow NULL pointers for coding simplicity + + if (pmt == NULL) { + return; + } + + FreeMediaType(*pmt); + CoTaskMemFree((PVOID)pmt); +} + + +// this also comes in useful when using the IEnumMediaTypes interface so +// that you can copy a media type, you can do nearly the same by creating +// a CMediaType object but as soon as it goes out of scope the destructor +// will delete the memory it allocated (this takes a copy of the memory) + +AM_MEDIA_TYPE * WINAPI CreateMediaType(AM_MEDIA_TYPE const *pSrc) +{ + ASSERT(pSrc); + + // Allocate a block of memory for the media type + + AM_MEDIA_TYPE *pMediaType = + (AM_MEDIA_TYPE *)CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE)); + + if (pMediaType == NULL) { + return NULL; + } + // Copy the variable length format block + + HRESULT hr = CopyMediaType(pMediaType,pSrc); + if (FAILED(hr)) { + CoTaskMemFree((PVOID)pMediaType); + return NULL; + } + + return pMediaType; +} + + +// Copy 1 media type to another + +HRESULT WINAPI CopyMediaType(__out AM_MEDIA_TYPE *pmtTarget, const AM_MEDIA_TYPE *pmtSource) +{ + // We'll leak if we copy onto one that already exists - there's one + // case we can check like that - copying to itself. + ASSERT(pmtSource != pmtTarget); + *pmtTarget = *pmtSource; + if (pmtSource->cbFormat != 0) { + ASSERT(pmtSource->pbFormat != NULL); + pmtTarget->pbFormat = (PBYTE)CoTaskMemAlloc(pmtSource->cbFormat); + if (pmtTarget->pbFormat == NULL) { + pmtTarget->cbFormat = 0; + return E_OUTOFMEMORY; + } else { + CopyMemory((PVOID)pmtTarget->pbFormat, (PVOID)pmtSource->pbFormat, + pmtTarget->cbFormat); + } + } + if (pmtTarget->pUnk != NULL) { + pmtTarget->pUnk->AddRef(); + } + + return S_OK; +} + +// Free an existing media type (ie free resources it holds) + +void WINAPI FreeMediaType(__inout AM_MEDIA_TYPE& mt) +{ + if (mt.cbFormat != 0) { + CoTaskMemFree((PVOID)mt.pbFormat); + + // Strictly unnecessary but tidier + mt.cbFormat = 0; + mt.pbFormat = NULL; + } + if (mt.pUnk != NULL) { + mt.pUnk->Release(); + mt.pUnk = NULL; + } +} + +// Initialize a media type from a WAVEFORMATEX + +STDAPI CreateAudioMediaType( + const WAVEFORMATEX *pwfx, + __out AM_MEDIA_TYPE *pmt, + BOOL bSetFormat +) +{ + pmt->majortype = MEDIATYPE_Audio; + if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + pmt->subtype = ((PWAVEFORMATEXTENSIBLE)pwfx)->SubFormat; + } else { + pmt->subtype = FOURCCMap(pwfx->wFormatTag); + } + pmt->formattype = FORMAT_WaveFormatEx; + pmt->bFixedSizeSamples = TRUE; + pmt->bTemporalCompression = FALSE; + pmt->lSampleSize = pwfx->nBlockAlign; + pmt->pUnk = NULL; + if (bSetFormat) { + if (pwfx->wFormatTag == WAVE_FORMAT_PCM) { + pmt->cbFormat = sizeof(WAVEFORMATEX); + } else { + pmt->cbFormat = sizeof(WAVEFORMATEX) + pwfx->cbSize; + } + pmt->pbFormat = (PBYTE)CoTaskMemAlloc(pmt->cbFormat); + if (pmt->pbFormat == NULL) { + return E_OUTOFMEMORY; + } + if (pwfx->wFormatTag == WAVE_FORMAT_PCM) { + CopyMemory(pmt->pbFormat, pwfx, sizeof(PCMWAVEFORMAT)); + ((WAVEFORMATEX *)pmt->pbFormat)->cbSize = 0; + } else { + CopyMemory(pmt->pbFormat, pwfx, pmt->cbFormat); + } + } + return S_OK; +} + +// eliminate very many spurious warnings from MS compiler +#pragma warning(disable:4514) diff --git a/Src/Plugins/Input/in_dshow/base/mtype.h b/Src/Plugins/Input/in_dshow/base/mtype.h new file mode 100644 index 00000000..9402f064 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/mtype.h @@ -0,0 +1,89 @@ +//------------------------------------------------------------------------------ +// File: MtType.h +// +// Desc: DirectShow base classes - defines a class that holds and manages +// media type information. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __MTYPE__ +#define __MTYPE__ + +/* Helper class that derived pin objects can use to compare media + types etc. Has same data members as the struct AM_MEDIA_TYPE defined + in the streams IDL file, but also has (non-virtual) functions */ + +class CMediaType : public _AMMediaType { + +public: + + ~CMediaType(); + CMediaType(); + CMediaType(const GUID * majortype); + CMediaType(const AM_MEDIA_TYPE&, __out_opt HRESULT* phr = NULL); + CMediaType(const CMediaType&, __out_opt HRESULT* phr = NULL); + + CMediaType& operator=(const CMediaType&); + CMediaType& operator=(const AM_MEDIA_TYPE&); + + BOOL operator == (const CMediaType&) const; + BOOL operator != (const CMediaType&) const; + + HRESULT Set(const CMediaType& rt); + HRESULT Set(const AM_MEDIA_TYPE& rt); + + BOOL IsValid() const; + + const GUID *Type() const { return &majortype;} ; + void SetType(const GUID *); + const GUID *Subtype() const { return &subtype;} ; + void SetSubtype(const GUID *); + + BOOL IsFixedSize() const {return bFixedSizeSamples; }; + BOOL IsTemporalCompressed() const {return bTemporalCompression; }; + ULONG GetSampleSize() const; + + void SetSampleSize(ULONG sz); + void SetVariableSize(); + void SetTemporalCompression(BOOL bCompressed); + + // read/write pointer to format - can't change length without + // calling SetFormat, AllocFormatBuffer or ReallocFormatBuffer + + BYTE* Format() const {return pbFormat; }; + ULONG FormatLength() const { return cbFormat; }; + + void SetFormatType(const GUID *); + const GUID *FormatType() const {return &formattype; }; + BOOL SetFormat(__in_bcount(length) BYTE *pFormat, ULONG length); + void ResetFormatBuffer(); + BYTE* AllocFormatBuffer(ULONG length); + BYTE* ReallocFormatBuffer(ULONG length); + + void InitMediaType(); + + BOOL MatchesPartial(const CMediaType* ppartial) const; + BOOL IsPartiallySpecified(void) const; +}; + + +/* General purpose functions to copy and delete a task allocated AM_MEDIA_TYPE + structure which is useful when using the IEnumMediaFormats interface as + the implementation allocates the structures which you must later delete */ + +void WINAPI DeleteMediaType(__inout_opt AM_MEDIA_TYPE *pmt); +AM_MEDIA_TYPE * WINAPI CreateMediaType(AM_MEDIA_TYPE const *pSrc); +HRESULT WINAPI CopyMediaType(__out AM_MEDIA_TYPE *pmtTarget, const AM_MEDIA_TYPE *pmtSource); +void WINAPI FreeMediaType(__inout AM_MEDIA_TYPE& mt); + +// Initialize a media type from a WAVEFORMATEX + +STDAPI CreateAudioMediaType( + const WAVEFORMATEX *pwfx, + __out AM_MEDIA_TYPE *pmt, + BOOL bSetFormat); + +#endif /* __MTYPE__ */ + diff --git a/Src/Plugins/Input/in_dshow/base/outputq.cpp b/Src/Plugins/Input/in_dshow/base/outputq.cpp new file mode 100644 index 00000000..5c1033d4 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/outputq.cpp @@ -0,0 +1,801 @@ +//------------------------------------------------------------------------------ +// File: OutputQ.cpp +// +// Desc: DirectShow base classes - implements COutputQueue class used by an +// output pin which may sometimes want to queue output samples on a +// separate thread and sometimes call Receive() directly on the input +// pin. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> + + +// +// COutputQueue Constructor : +// +// Determines if a thread is to be created and creates resources +// +// pInputPin - the downstream input pin we're queueing samples to +// +// phr - changed to a failure code if this function fails +// (otherwise unchanges) +// +// bAuto - Ask pInputPin if it can block in Receive by calling +// its ReceiveCanBlock method and create a thread if +// it can block, otherwise not. +// +// bQueue - if bAuto == FALSE then we create a thread if and only +// if bQueue == TRUE +// +// lBatchSize - work in batches of lBatchSize +// +// bBatchEact - Use exact batch sizes so don't send until the +// batch is full or SendAnyway() is called +// +// lListSize - If we create a thread make the list of samples queued +// to the thread have this size cache +// +// dwPriority - If we create a thread set its priority to this +// +COutputQueue::COutputQueue( + IPin *pInputPin, // Pin to send stuff to + __inout HRESULT *phr, // 'Return code' + BOOL bAuto, // Ask pin if queue or not + BOOL bQueue, // Send through queue + LONG lBatchSize, // Batch + BOOL bBatchExact, // Batch exactly to BatchSize + LONG lListSize, + DWORD dwPriority, + bool bFlushingOpt // flushing optimization + ) : m_lBatchSize(lBatchSize), + m_bBatchExact(bBatchExact && (lBatchSize > 1)), + m_hThread(NULL), + m_hSem(NULL), + m_List(NULL), + m_pPin(pInputPin), + m_ppSamples(NULL), + m_lWaiting(0), + m_evFlushComplete(FALSE, phr), + m_pInputPin(NULL), + m_bSendAnyway(FALSE), + m_nBatched(0), + m_bFlushing(FALSE), + m_bFlushed(TRUE), + m_bFlushingOpt(bFlushingOpt), + m_bTerminate(FALSE), + m_hEventPop(NULL), + m_hr(S_OK) +{ + ASSERT(m_lBatchSize > 0); + + + if (FAILED(*phr)) { + return; + } + + // Check the input pin is OK and cache its IMemInputPin interface + + *phr = pInputPin->QueryInterface(IID_IMemInputPin, (void **)&m_pInputPin); + if (FAILED(*phr)) { + return; + } + + // See if we should ask the downstream pin + + if (bAuto) { + HRESULT hr = m_pInputPin->ReceiveCanBlock(); + if (SUCCEEDED(hr)) { + bQueue = hr == S_OK; + } + } + + // Create our sample batch + + m_ppSamples = new PMEDIASAMPLE[m_lBatchSize]; + if (m_ppSamples == NULL) { + *phr = E_OUTOFMEMORY; + return; + } + + // If we're queueing allocate resources + + if (bQueue) { + DbgLog((LOG_TRACE, 2, TEXT("Creating thread for output pin"))); + m_hSem = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL); + if (m_hSem == NULL) { + DWORD dwError = GetLastError(); + *phr = AmHresultFromWin32(dwError); + return; + } + m_List = new CSampleList(NAME("Sample Queue List"), + lListSize, + FALSE // No lock + ); + if (m_List == NULL) { + *phr = E_OUTOFMEMORY; + return; + } + + + DWORD dwThreadId; + m_hThread = CreateThread(NULL, + 0, + InitialThreadProc, + (LPVOID)this, + 0, + &dwThreadId); + if (m_hThread == NULL) { + DWORD dwError = GetLastError(); + *phr = AmHresultFromWin32(dwError); + return; + } + SetThreadPriority(m_hThread, dwPriority); + } else { + DbgLog((LOG_TRACE, 2, TEXT("Calling input pin directly - no thread"))); + } +} + +// +// COutputQueuee Destructor : +// +// Free all resources - +// +// Thread, +// Batched samples +// +COutputQueue::~COutputQueue() +{ + DbgLog((LOG_TRACE, 3, TEXT("COutputQueue::~COutputQueue"))); + /* Free our pointer */ + if (m_pInputPin != NULL) { + m_pInputPin->Release(); + } + if (m_hThread != NULL) { + { + CAutoLock lck(this); + m_bTerminate = TRUE; + m_hr = S_FALSE; + NotifyThread(); + } + DbgWaitForSingleObject(m_hThread); + EXECUTE_ASSERT(CloseHandle(m_hThread)); + + // The thread frees the samples when asked to terminate + + ASSERT(m_List->GetCount() == 0); + delete m_List; + } else { + FreeSamples(); + } + if (m_hSem != NULL) { + EXECUTE_ASSERT(CloseHandle(m_hSem)); + } + delete [] m_ppSamples; +} + +// +// Call the real thread proc as a member function +// +DWORD WINAPI COutputQueue::InitialThreadProc(__in LPVOID pv) +{ + HRESULT hrCoInit = CAMThread::CoInitializeHelper(); + + COutputQueue *pSampleQueue = (COutputQueue *)pv; + DWORD dwReturn = pSampleQueue->ThreadProc(); + + if(hrCoInit == S_OK) { + CoUninitialize(); + } + + return dwReturn; +} + +// +// Thread sending the samples downstream : +// +// When there is nothing to do the thread sets m_lWaiting (while +// holding the critical section) and then waits for m_hSem to be +// set (not holding the critical section) +// +DWORD COutputQueue::ThreadProc() +{ + while (TRUE) { + BOOL bWait = FALSE; + IMediaSample *pSample; + LONG lNumberToSend; // Local copy + NewSegmentPacket* ppacket; + + // + // Get a batch of samples and send it if possible + // In any case exit the loop if there is a control action + // requested + // + { + CAutoLock lck(this); + while (TRUE) { + + if (m_bTerminate) { + FreeSamples(); + return 0; + } + if (m_bFlushing) { + FreeSamples(); + SetEvent(m_evFlushComplete); + } + + // Get a sample off the list + + pSample = m_List->RemoveHead(); + // inform derived class we took something off the queue + if (m_hEventPop) { + //DbgLog((LOG_TRACE,3,TEXT("Queue: Delivered SET EVENT"))); + SetEvent(m_hEventPop); + } + + if (pSample != NULL && + !IsSpecialSample(pSample)) { + + // If its just a regular sample just add it to the batch + // and exit the loop if the batch is full + + m_ppSamples[m_nBatched++] = pSample; + if (m_nBatched == m_lBatchSize) { + break; + } + } else { + + // If there was nothing in the queue and there's nothing + // to send (either because there's nothing or the batch + // isn't full) then prepare to wait + + if (pSample == NULL && + (m_bBatchExact || m_nBatched == 0)) { + + // Tell other thread to set the event when there's + // something do to + + ASSERT(m_lWaiting == 0); + m_lWaiting++; + bWait = TRUE; + } else { + + // We break out of the loop on SEND_PACKET unless + // there's nothing to send + + if (pSample == SEND_PACKET && m_nBatched == 0) { + continue; + } + + if (pSample == NEW_SEGMENT) { + // now we need the parameters - we are + // guaranteed that the next packet contains them + ppacket = (NewSegmentPacket *) m_List->RemoveHead(); + // we took something off the queue + if (m_hEventPop) { + //DbgLog((LOG_TRACE,3,TEXT("Queue: Delivered SET EVENT"))); + SetEvent(m_hEventPop); + } + + ASSERT(ppacket); + } + // EOS_PACKET falls through here and we exit the loop + // In this way it acts like SEND_PACKET + } + break; + } + } + if (!bWait) { + // We look at m_nBatched from the client side so keep + // it up to date inside the critical section + lNumberToSend = m_nBatched; // Local copy + m_nBatched = 0; + } + } + + // Wait for some more data + + if (bWait) { + DbgWaitForSingleObject(m_hSem); + continue; + } + + + + // OK - send it if there's anything to send + // We DON'T check m_bBatchExact here because either we've got + // a full batch or we dropped through because we got + // SEND_PACKET or EOS_PACKET - both of which imply we should + // flush our batch + + if (lNumberToSend != 0) { + long nProcessed; + if (m_hr == S_OK) { + ASSERT(!m_bFlushed); + HRESULT hr = m_pInputPin->ReceiveMultiple(m_ppSamples, + lNumberToSend, + &nProcessed); + /* Don't overwrite a flushing state HRESULT */ + CAutoLock lck(this); + if (m_hr == S_OK) { + m_hr = hr; + } + ASSERT(!m_bFlushed); + } + while (lNumberToSend != 0) { + m_ppSamples[--lNumberToSend]->Release(); + } + if (m_hr != S_OK) { + + // In any case wait for more data - S_OK just + // means there wasn't an error + + DbgLog((LOG_ERROR, 2, TEXT("ReceiveMultiple returned %8.8X"), + m_hr)); + } + } + + // Check for end of stream + + if (pSample == EOS_PACKET) { + + // We don't send even end of stream on if we've previously + // returned something other than S_OK + // This is because in that case the pin which returned + // something other than S_OK should have either sent + // EndOfStream() or notified the filter graph + + if (m_hr == S_OK) { + DbgLog((LOG_TRACE, 2, TEXT("COutputQueue sending EndOfStream()"))); + HRESULT hr = m_pPin->EndOfStream(); + if (FAILED(hr)) { + DbgLog((LOG_ERROR, 2, TEXT("COutputQueue got code 0x%8.8X from EndOfStream()"))); + } + } + } + + // Data from a new source + + if (pSample == RESET_PACKET) { + m_hr = S_OK; + SetEvent(m_evFlushComplete); + } + + if (pSample == NEW_SEGMENT) { + m_pPin->NewSegment(ppacket->tStart, ppacket->tStop, ppacket->dRate); + delete ppacket; + } + } +} + +// Send batched stuff anyway +void COutputQueue::SendAnyway() +{ + if (!IsQueued()) { + + // m_bSendAnyway is a private parameter checked in ReceiveMultiple + + m_bSendAnyway = TRUE; + LONG nProcessed; + ReceiveMultiple(NULL, 0, &nProcessed); + m_bSendAnyway = FALSE; + + } else { + CAutoLock lck(this); + QueueSample(SEND_PACKET); + NotifyThread(); + } +} + +void +COutputQueue::NewSegment( + REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate) +{ + if (!IsQueued()) { + if (S_OK == m_hr) { + if (m_bBatchExact) { + SendAnyway(); + } + m_pPin->NewSegment(tStart, tStop, dRate); + } + } else { + if (m_hr == S_OK) { + // + // we need to queue the new segment to appear in order in the + // data, but we need to pass parameters to it. Rather than + // take the hit of wrapping every single sample so we can tell + // special ones apart, we queue special pointers to indicate + // special packets, and we guarantee (by holding the + // critical section) that the packet immediately following a + // NEW_SEGMENT value is a NewSegmentPacket containing the + // parameters. + NewSegmentPacket * ppack = new NewSegmentPacket; + if (ppack == NULL) { + return; + } + ppack->tStart = tStart; + ppack->tStop = tStop; + ppack->dRate = dRate; + + CAutoLock lck(this); + QueueSample(NEW_SEGMENT); + QueueSample( (IMediaSample*) ppack); + NotifyThread(); + } + } +} + + +// +// End of Stream is queued to output device +// +void COutputQueue::EOS() +{ + CAutoLock lck(this); + if (!IsQueued()) { + if (m_bBatchExact) { + SendAnyway(); + } + if (m_hr == S_OK) { + DbgLog((LOG_TRACE, 2, TEXT("COutputQueue sending EndOfStream()"))); + m_bFlushed = FALSE; + HRESULT hr = m_pPin->EndOfStream(); + if (FAILED(hr)) { + DbgLog((LOG_ERROR, 2, TEXT("COutputQueue got code 0x%8.8X from EndOfStream()"))); + } + } + } else { + if (m_hr == S_OK) { + m_bFlushed = FALSE; + QueueSample(EOS_PACKET); + NotifyThread(); + } + } +} + +// +// Flush all the samples in the queue +// +void COutputQueue::BeginFlush() +{ + if (IsQueued()) { + { + CAutoLock lck(this); + + // block receives -- we assume this is done by the + // filter in which we are a component + + // discard all queued data + + m_bFlushing = TRUE; + + // Make sure we discard all samples from now on + + if (m_hr == S_OK) { + m_hr = S_FALSE; + } + + // Optimize so we don't keep calling downstream all the time + + if (m_bFlushed && m_bFlushingOpt) { + return; + } + + // Make sure we really wait for the flush to complete + m_evFlushComplete.Reset(); + + NotifyThread(); + } + + // pass this downstream + + m_pPin->BeginFlush(); + } else { + // pass downstream first to avoid deadlocks + m_pPin->BeginFlush(); + CAutoLock lck(this); + // discard all queued data + + m_bFlushing = TRUE; + + // Make sure we discard all samples from now on + + if (m_hr == S_OK) { + m_hr = S_FALSE; + } + } + +} + +// +// leave flush mode - pass this downstream +void COutputQueue::EndFlush() +{ + { + CAutoLock lck(this); + ASSERT(m_bFlushing); + if (m_bFlushingOpt && m_bFlushed && IsQueued()) { + m_bFlushing = FALSE; + m_hr = S_OK; + return; + } + } + + // sync with pushing thread -- done in BeginFlush + // ensure no more data to go downstream -- done in BeginFlush + // + // Because we are synching here there is no need to hold the critical + // section (in fact we'd deadlock if we did!) + + if (IsQueued()) { + m_evFlushComplete.Wait(); + } else { + FreeSamples(); + } + + // Be daring - the caller has guaranteed no samples will arrive + // before EndFlush() returns + + m_bFlushing = FALSE; + m_bFlushed = TRUE; + + // call EndFlush on downstream pins + + m_pPin->EndFlush(); + + m_hr = S_OK; +} + +// COutputQueue::QueueSample +// +// private method to Send a sample to the output queue +// The critical section MUST be held when this is called + +void COutputQueue::QueueSample(IMediaSample *pSample) +{ + if (NULL == m_List->AddTail(pSample)) { + if (!IsSpecialSample(pSample)) { + pSample->Release(); + } + } +} + +// +// COutputQueue::Receive() +// +// Send a single sample by the multiple sample route +// (NOTE - this could be optimized if necessary) +// +// On return the sample will have been Release()'d +// + +HRESULT COutputQueue::Receive(IMediaSample *pSample) +{ + LONG nProcessed; + return ReceiveMultiple(&pSample, 1, &nProcessed); +} + +// +// COutputQueue::ReceiveMultiple() +// +// Send a set of samples to the downstream pin +// +// ppSamples - array of samples +// nSamples - how many +// nSamplesProcessed - How many were processed +// +// On return all samples will have been Release()'d +// + +HRESULT COutputQueue::ReceiveMultiple ( + __in_ecount(nSamples) IMediaSample **ppSamples, + long nSamples, + __out long *nSamplesProcessed) +{ + if (nSamples < 0) { + return E_INVALIDARG; + } + + CAutoLock lck(this); + // Either call directly or queue up the samples + + if (!IsQueued()) { + + // If we already had a bad return code then just return + + if (S_OK != m_hr) { + + // If we've never received anything since the last Flush() + // and the sticky return code is not S_OK we must be + // flushing + // ((!A || B) is equivalent to A implies B) + ASSERT(!m_bFlushed || m_bFlushing); + + // We're supposed to Release() them anyway! + *nSamplesProcessed = 0; + for (int i = 0; i < nSamples; i++) { + DbgLog((LOG_TRACE, 3, TEXT("COutputQueue (direct) : Discarding %d samples code 0x%8.8X"), + nSamples, m_hr)); + ppSamples[i]->Release(); + } + + return m_hr; + } + // + // If we're flushing the sticky return code should be S_FALSE + // + ASSERT(!m_bFlushing); + m_bFlushed = FALSE; + + ASSERT(m_nBatched < m_lBatchSize); + ASSERT(m_nBatched == 0 || m_bBatchExact); + + // Loop processing the samples in batches + + LONG iLost = 0; + long iDone = 0; + for (iDone = 0; + iDone < nSamples || (m_nBatched != 0 && m_bSendAnyway); + ) { + +//pragma message (REMIND("Implement threshold scheme")) + ASSERT(m_nBatched < m_lBatchSize); + if (iDone < nSamples) { + m_ppSamples[m_nBatched++] = ppSamples[iDone++]; + } + if (m_nBatched == m_lBatchSize || + nSamples == 0 && (m_bSendAnyway || !m_bBatchExact)) { + LONG nDone; + DbgLog((LOG_TRACE, 4, TEXT("Batching %d samples"), + m_nBatched)); + + if (m_hr == S_OK) { + m_hr = m_pInputPin->ReceiveMultiple(m_ppSamples, + m_nBatched, + &nDone); + } else { + nDone = 0; + } + iLost += m_nBatched - nDone; + for (LONG i = 0; i < m_nBatched; i++) { + m_ppSamples[i]->Release(); + } + m_nBatched = 0; + } + } + *nSamplesProcessed = iDone - iLost; + if (*nSamplesProcessed < 0) { + *nSamplesProcessed = 0; + } + return m_hr; + } else { + /* We're sending to our thread */ + + if (m_hr != S_OK) { + *nSamplesProcessed = 0; + DbgLog((LOG_TRACE, 3, TEXT("COutputQueue (queued) : Discarding %d samples code 0x%8.8X"), + nSamples, m_hr)); + for (int i = 0; i < nSamples; i++) { + ppSamples[i]->Release(); + } + return m_hr; + } + m_bFlushed = FALSE; + for (long i = 0; i < nSamples; i++) { + QueueSample(ppSamples[i]); + } + *nSamplesProcessed = nSamples; + if (!m_bBatchExact || + m_nBatched + m_List->GetCount() >= m_lBatchSize) { + NotifyThread(); + } + return S_OK; + } +} + +// Get ready for new data - cancels sticky m_hr +void COutputQueue::Reset() +{ + if (!IsQueued()) { + m_hr = S_OK; + } else { + { + CAutoLock lck(this); + QueueSample(RESET_PACKET); + NotifyThread(); + } + m_evFlushComplete.Wait(); + } +} + +// Remove and Release() all queued and Batched samples +void COutputQueue::FreeSamples() +{ + CAutoLock lck(this); + if (IsQueued()) { + while (TRUE) { + IMediaSample *pSample = m_List->RemoveHead(); + // inform derived class we took something off the queue + if (m_hEventPop) { + //DbgLog((LOG_TRACE,3,TEXT("Queue: Delivered SET EVENT"))); + SetEvent(m_hEventPop); + } + + if (pSample == NULL) { + break; + } + if (!IsSpecialSample(pSample)) { + pSample->Release(); + } else { + if (pSample == NEW_SEGMENT) { + // Free NEW_SEGMENT packet + NewSegmentPacket *ppacket = + (NewSegmentPacket *) m_List->RemoveHead(); + // inform derived class we took something off the queue + if (m_hEventPop) { + //DbgLog((LOG_TRACE,3,TEXT("Queue: Delivered SET EVENT"))); + SetEvent(m_hEventPop); + } + + ASSERT(ppacket != NULL); + delete ppacket; + } + } + } + } + for (int i = 0; i < m_nBatched; i++) { + m_ppSamples[i]->Release(); + } + m_nBatched = 0; +} + +// Notify the thread if there is something to do +// +// The critical section MUST be held when this is called +void COutputQueue::NotifyThread() +{ + // Optimize - no need to signal if it's not waiting + ASSERT(IsQueued()); + if (m_lWaiting) { + ReleaseSemaphore(m_hSem, m_lWaiting, NULL); + m_lWaiting = 0; + } +} + +// See if there's any work to do +// Returns +// TRUE if there is nothing on the queue and nothing in the batch +// and all data has been sent +// FALSE otherwise +// +BOOL COutputQueue::IsIdle() +{ + CAutoLock lck(this); + + // We're idle if + // there is no thread (!IsQueued()) OR + // the thread is waiting for more work (m_lWaiting != 0) + // AND + // there's nothing in the current batch (m_nBatched == 0) + + if (IsQueued() && m_lWaiting == 0 || m_nBatched != 0) { + return FALSE; + } else { + + // If we're idle it shouldn't be possible for there + // to be anything on the work queue + + ASSERT(!IsQueued() || m_List->GetCount() == 0); + return TRUE; + } +} + + +void COutputQueue::SetPopEvent(HANDLE hEvent) +{ + m_hEventPop = hEvent; +} diff --git a/Src/Plugins/Input/in_dshow/base/outputq.h b/Src/Plugins/Input/in_dshow/base/outputq.h new file mode 100644 index 00000000..7e60b53a --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/outputq.h @@ -0,0 +1,137 @@ +//------------------------------------------------------------------------------ +// File: OutputQ.h +// +// Desc: DirectShow base classes - defines the COutputQueue class, which +// makes a queue of samples and sends them to an output pin. The +// class will optionally send the samples to the pin directly. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +typedef CGenericList<IMediaSample> CSampleList; + +class COutputQueue : public CCritSec +{ +public: + // Constructor + COutputQueue(IPin *pInputPin, // Pin to send stuff to + __inout HRESULT *phr, // 'Return code' + BOOL bAuto = TRUE, // Ask pin if blocks + BOOL bQueue = TRUE, // Send through queue (ignored if + // bAuto set) + LONG lBatchSize = 1, // Batch + BOOL bBatchExact = FALSE,// Batch exactly to BatchSize + LONG lListSize = // Likely number in the list + DEFAULTCACHE, + DWORD dwPriority = // Priority of thread to create + THREAD_PRIORITY_NORMAL, + bool bFlushingOpt = false // flushing optimization + ); + ~COutputQueue(); + + // enter flush state - discard all data + void BeginFlush(); // Begin flushing samples + + // re-enable receives (pass this downstream) + void EndFlush(); // Complete flush of samples - downstream + // pin guaranteed not to block at this stage + + void EOS(); // Call this on End of stream + + void SendAnyway(); // Send batched samples anyway (if bBatchExact set) + + void NewSegment( + REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate); + + HRESULT Receive(IMediaSample *pSample); + + // do something with these media samples + HRESULT ReceiveMultiple ( + __in_ecount(nSamples) IMediaSample **pSamples, + long nSamples, + __out long *nSamplesProcessed); + + void Reset(); // Reset m_hr ready for more data + + // See if its idle or not + BOOL IsIdle(); + + // give the class an event to fire after everything removed from the queue + void SetPopEvent(HANDLE hEvent); + +protected: + static DWORD WINAPI InitialThreadProc(__in LPVOID pv); + DWORD ThreadProc(); + BOOL IsQueued() + { + return m_List != NULL; + }; + + // The critical section MUST be held when this is called + void QueueSample(IMediaSample *pSample); + + BOOL IsSpecialSample(IMediaSample *pSample) + { + return (DWORD_PTR)pSample > (DWORD_PTR)(LONG_PTR)(-16); + }; + + // Remove and Release() batched and queued samples + void FreeSamples(); + + // Notify the thread there is something to do + void NotifyThread(); + + +protected: + // Queue 'messages' + #define SEND_PACKET ((IMediaSample *)(LONG_PTR)(-2)) // Send batch + #define EOS_PACKET ((IMediaSample *)(LONG_PTR)(-3)) // End of stream + #define RESET_PACKET ((IMediaSample *)(LONG_PTR)(-4)) // Reset m_hr + #define NEW_SEGMENT ((IMediaSample *)(LONG_PTR)(-5)) // send NewSegment + + // new segment packet is always followed by one of these + struct NewSegmentPacket { + REFERENCE_TIME tStart; + REFERENCE_TIME tStop; + double dRate; + }; + + // Remember input stuff + IPin * const m_pPin; + IMemInputPin * m_pInputPin; + BOOL const m_bBatchExact; + LONG const m_lBatchSize; + + CSampleList * m_List; + HANDLE m_hSem; + CAMEvent m_evFlushComplete; + HANDLE m_hThread; + __field_ecount_opt(m_lBatchSize) IMediaSample ** m_ppSamples; + __range(0, m_lBatchSize) LONG m_nBatched; + + // Wait optimization + LONG m_lWaiting; + // Flush synchronization + BOOL m_bFlushing; + + // flushing optimization. some downstream filters have trouble + // with the queue's flushing optimization. other rely on it + BOOL m_bFlushed; + bool m_bFlushingOpt; + + // Terminate now + BOOL m_bTerminate; + + // Send anyway flag for batching + BOOL m_bSendAnyway; + + // Deferred 'return code' + HRESULT volatile m_hr; + + // an event that can be fired after every deliver + HANDLE m_hEventPop; +}; + diff --git a/Src/Plugins/Input/in_dshow/base/perflog.cpp b/Src/Plugins/Input/in_dshow/base/perflog.cpp new file mode 100644 index 00000000..b2ff24df --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/perflog.cpp @@ -0,0 +1,347 @@ +//------------------------------------------------------------------------------ +// File: perflog.cpp +// +// Desc: Macros for DirectShow performance logging. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + +#pragma warning (disable:4201) + +#include <streams.h> +#include <windows.h> +#include <tchar.h> +#include <winperf.h> +#include <wmistr.h> +#include <evntrace.h> +#include <strsafe.h> +#include "perflog.h" + +// +// Local function prototypes. +// + +ULONG +WINAPI +PerflogCallback ( + WMIDPREQUESTCODE RequestCode, + __in PVOID Context, + __out ULONG* BufferSize, + __in PVOID Buffer + ); + +// +// Event tracing function pointers. +// We have to do this to run on down-level platforms. +// + +#ifdef UNICODE + +ULONG +(__stdcall * _RegisterTraceGuids) ( + __in IN WMIDPREQUEST RequestAddress, + __in IN PVOID RequestContext, + IN LPCGUID ControlGuid, + IN ULONG GuidCount, + __in IN PTRACE_GUID_REGISTRATION TraceGuidReg, + IN LPCWSTR MofImagePath, + IN LPCWSTR MofResourceName, + OUT PTRACEHANDLE RegistrationHandle + ); + +#define REGISTERTRACEGUIDS_NAME "RegisterTraceGuidsW" + +#else + +ULONG +(__stdcall * _RegisterTraceGuids) ( + __in IN WMIDPREQUEST RequestAddress, + __in IN PVOID RequestContext, + IN LPCGUID ControlGuid, + IN ULONG GuidCount, + __in IN PTRACE_GUID_REGISTRATION TraceGuidReg, + IN LPCSTR MofImagePath, + IN LPCSTR MofResourceName, + __out OUT PTRACEHANDLE RegistrationHandle + ); + +#define REGISTERTRACEGUIDS_NAME "RegisterTraceGuidsA" + +#endif + +ULONG +(__stdcall * _UnregisterTraceGuids) ( + TRACEHANDLE RegistrationHandle + ); + +TRACEHANDLE +(__stdcall * _GetTraceLoggerHandle) ( + __in PVOID Buffer + ); + +UCHAR +(__stdcall * _GetTraceEnableLevel) ( + TRACEHANDLE TraceHandle + ); + +ULONG +(__stdcall * _GetTraceEnableFlags) ( + TRACEHANDLE TraceHandle + ); + +ULONG +(__stdcall * _TraceEvent) ( + TRACEHANDLE TraceHandle, + __in PEVENT_TRACE_HEADER EventTrace + ); + +HINSTANCE _Advapi32; + +// +// Global variables. +// + +BOOL EventTracingAvailable=FALSE; +ULONG PerflogEnableFlags; +UCHAR PerflogEnableLevel; +ULONG PerflogModuleLevel = 0; +void (*OnStateChanged)(void); +TRACEHANDLE PerflogTraceHandle=NULL; +TRACEHANDLE PerflogRegHandle; + +// The Win32 wsprintf() function writes a maximum of 1024 characters to it's output buffer. +// See the documentation for wsprintf()'s lpOut parameter for more information. +const INT iDEBUGINFO = 1024; // Used to format strings + +// +// This routine initializes performance logging. +// It should be called from DllMain(). +// + + +VOID +PerflogReadModuleLevel( + HINSTANCE hInstance + ) +{ + LONG lReturn; // Create key return value + TCHAR szInfo[iDEBUGINFO]; // Constructs key names + TCHAR szFullName[iDEBUGINFO]; // Load the full path and module name + HKEY hModuleKey; // Module key handle + LPTSTR pName; // Searches from the end for a backslash + DWORD dwKeySize, dwKeyType, dwKeyValue; + + DWORD dwSize = GetModuleFileName( + (hInstance ? hInstance : GetModuleHandle( NULL )), + szFullName, + iDEBUGINFO ); + + if (0 == dwSize || iDEBUGINFO == dwSize) { + return; + } + + pName = _tcsrchr(szFullName,'\\'); + if (pName == NULL) { + pName = szFullName; + } else { + pName++; + } + + /* Construct the base key name */ + (void)StringCchPrintf(szInfo,NUMELMS(szInfo),TEXT("SOFTWARE\\Debug\\%s"),pName); + + /* Open the key for this module */ + lReturn = + RegOpenKeyEx( + HKEY_LOCAL_MACHINE, // Handle of an open key + szInfo, // Address of subkey name + (DWORD) 0, // Reserved value + KEY_QUERY_VALUE, // Desired security access + &hModuleKey ); // Opened handle buffer + + if (lReturn != ERROR_SUCCESS) { + return; + } + + dwKeySize = sizeof(DWORD); + lReturn = RegQueryValueEx( + hModuleKey, // Handle to an open key + TEXT("PERFLOG"), + NULL, // Reserved field + &dwKeyType, // Returns the field type + (LPBYTE) &dwKeyValue, // Returns the field's value + &dwKeySize ); // Number of bytes transferred + + if ((lReturn == ERROR_SUCCESS) && (dwKeyType == REG_DWORD)) + { + PerflogModuleLevel = dwKeyValue; + } + + RegCloseKey(hModuleKey); +} + +BOOL PerflogInitIfEnabled( + IN HINSTANCE hInstance, + __in IN PPERFLOG_LOGGING_PARAMS LogParams + ) +{ + PerflogReadModuleLevel( hInstance ); + if (PerflogModuleLevel) + { + return PerflogInitialize( LogParams ); + } + else + { + return FALSE; + } +} + +BOOL +PerflogInitialize ( + __in IN PPERFLOG_LOGGING_PARAMS LogParams + ) +{ + ULONG status; + + // + // If we're running on a recent-enough platform, this will get + // pointers to the event tracing routines. + // + + _Advapi32 = GetModuleHandle (_T("ADVAPI32.DLL")); + if (_Advapi32 == NULL) { + return FALSE; + } + + *((FARPROC*) &_RegisterTraceGuids) = GetProcAddress (_Advapi32, REGISTERTRACEGUIDS_NAME); + *((FARPROC*) &_UnregisterTraceGuids) = GetProcAddress (_Advapi32, "UnregisterTraceGuids"); + *((FARPROC*) &_GetTraceLoggerHandle) = GetProcAddress (_Advapi32, "GetTraceLoggerHandle"); + *((FARPROC*) &_GetTraceEnableLevel) = GetProcAddress (_Advapi32, "GetTraceEnableLevel"); + *((FARPROC*) &_GetTraceEnableFlags) = GetProcAddress (_Advapi32, "GetTraceEnableFlags"); + *((FARPROC*) &_TraceEvent) = GetProcAddress (_Advapi32, "TraceEvent"); + + if (_RegisterTraceGuids == NULL || + _UnregisterTraceGuids == NULL || + _GetTraceEnableLevel == NULL || + _GetTraceEnableFlags == NULL || + _TraceEvent == NULL) { + + return FALSE; + } + + EventTracingAvailable = TRUE; + + OnStateChanged = LogParams->OnStateChanged; + + // + // Register our GUIDs. + // + + status = _RegisterTraceGuids (PerflogCallback, + LogParams, + &LogParams->ControlGuid, + LogParams->NumberOfTraceGuids, + LogParams->TraceGuids, + NULL, + NULL, + &PerflogRegHandle); + + return (status == ERROR_SUCCESS); +} + +// +// This routine shuts down performance logging. +// + +VOID +PerflogShutdown ( + VOID + ) +{ + if (!EventTracingAvailable) { + return; + } + + _UnregisterTraceGuids (PerflogRegHandle); + PerflogRegHandle = NULL; + PerflogTraceHandle = NULL; +} + +// +// Event tracing callback routine. +// It's called when controllers call event tracing control functions. +// + +ULONG +WINAPI +PerflogCallback ( + WMIDPREQUESTCODE RequestCode, + __in PVOID Context, + __out ULONG* BufferSize, + __in PVOID Buffer + ) +{ + ULONG status; + + UNREFERENCED_PARAMETER (Context); + + ASSERT (EventTracingAvailable); + + status = ERROR_SUCCESS; + + switch (RequestCode) { + + case WMI_ENABLE_EVENTS: + PerflogTraceHandle = _GetTraceLoggerHandle (Buffer); + PerflogEnableFlags = _GetTraceEnableFlags (PerflogTraceHandle); + PerflogEnableLevel = _GetTraceEnableLevel (PerflogTraceHandle); + break; + + case WMI_DISABLE_EVENTS: + PerflogTraceHandle = NULL; + PerflogEnableFlags = 0; + PerflogEnableLevel = 0; + break; + + default: + status = ERROR_INVALID_PARAMETER; + } + + if (OnStateChanged != NULL) { + OnStateChanged(); + } + + *BufferSize = 0; + return status; +} + +// +// Logging routine. +// + +VOID +PerflogTraceEvent ( + __in PEVENT_TRACE_HEADER Event + ) +{ + if (!EventTracingAvailable) { + return; + } + + _TraceEvent (PerflogTraceHandle, Event); +} + +VOID +PerflogTraceEventLevel( + ULONG Level, + __in PEVENT_TRACE_HEADER Event + ) +{ + if ((!EventTracingAvailable) || (Level <= PerflogModuleLevel)) { + return; + } + + _TraceEvent (PerflogTraceHandle, Event); +} + + diff --git a/Src/Plugins/Input/in_dshow/base/perflog.h b/Src/Plugins/Input/in_dshow/base/perflog.h new file mode 100644 index 00000000..05d6404f --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/perflog.h @@ -0,0 +1,56 @@ +//------------------------------------------------------------------------------ +// File: perflog.h +// +// Desc: Performance logging framework. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + +typedef struct _PERFLOG_LOGGING_PARAMS { + GUID ControlGuid; + void (*OnStateChanged)(void); + ULONG NumberOfTraceGuids; + TRACE_GUID_REGISTRATION TraceGuids[ANYSIZE_ARRAY]; +} PERFLOG_LOGGING_PARAMS, *PPERFLOG_LOGGING_PARAMS; + +BOOL +PerflogInitIfEnabled( + IN HINSTANCE hInstance, + __in PPERFLOG_LOGGING_PARAMS LogParams + ); + +BOOL +PerflogInitialize ( + __in PPERFLOG_LOGGING_PARAMS LogParams + ); + +VOID +PerflogShutdown ( + VOID + ); + +VOID +PerflogTraceEvent ( + __in PEVENT_TRACE_HEADER Event + ); + +extern ULONG PerflogEnableFlags; +extern UCHAR PerflogEnableLevel; +extern ULONG PerflogModuleLevel; +extern TRACEHANDLE PerflogTraceHandle; +extern TRACEHANDLE PerflogRegHandle; + +#define PerflogTracingEnabled() (PerflogTraceHandle != 0) + +#define PerflogEvent( _x_ ) PerflogTraceEventLevel _x_ + +VOID +PerflogTraceEventLevel( + ULONG Level, + __in PEVENT_TRACE_HEADER Event + ); + +VOID +PerflogTraceEvent ( + __in PEVENT_TRACE_HEADER Event + ); diff --git a/Src/Plugins/Input/in_dshow/base/perfstruct.h b/Src/Plugins/Input/in_dshow/base/perfstruct.h new file mode 100644 index 00000000..b57657c8 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/perfstruct.h @@ -0,0 +1,194 @@ +//------------------------------------------------------------------------------ +// File: PerfStruct.h +// +// Desc: Structures for DirectShow performance logging. +// +// Copyright (c) 2000-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef _PERFSTRUCT_H_ +#define _PERFSTRUCT_H_ + +#include <wmistr.h> +#include <evntrace.h> + +// {28CF047A-2437-4b24-B653-B9446A419A69} +DEFINE_GUID(GUID_DSHOW_CTL, +0x28cf047a, 0x2437, 0x4b24, 0xb6, 0x53, 0xb9, 0x44, 0x6a, 0x41, 0x9a, 0x69); + +// {D0DA7AD6-AE80-4de5-AAFC-C126711E7593} +DEFINE_GUID(GUID_VIDEOREND, +0xd0da7ad6, 0xae80, 0x4de5, 0xaa, 0xfc, 0xc1, 0x26, 0x71, 0x1e, 0x75, 0x93); + +// {DC70AC3E-93E5-48db-88AB-E42064EC276A} +DEFINE_GUID(GUID_DSOUNDGLITCH, +0xdc70ac3e, 0x93e5, 0x48db, 0x88, 0xab, 0xe4, 0x20, 0x64, 0xec, 0x27, 0x6a); + +// {3d7e7d93-2fc8-4a07-a719-e0922ff2899} +DEFINE_GUID(GUID_STREAMTRACE, +0x3d7e7d93, 0x2fc8, 0x4a07, 0xa7, 0x19, 0xe0, 0x92, 0x2f, 0xf2, 0x89, 0x9e); + +// AZFIX: the following GUIDs aren't useful right now. + +// {3C33F7F5-EE54-493c-BA25-1656539C05AC} +DEFINE_GUID(GUID_GETTIME, +0x3c33f7f5, 0xee54, 0x493c, 0xba, 0x25, 0x16, 0x56, 0x53, 0x9c, 0x5, 0xac); + +// {CC44B44D-8169-4952-9E4A-A4E13295E492} +DEFINE_GUID(GUID_AUDIOREND, +0xcc44b44d, 0x8169, 0x4952, 0x9e, 0x4a, 0xa4, 0xe1, 0x32, 0x95, 0xe4, 0x92); + +// {775D19BF-4D8B-4de6-8DC9-66BAC7B310A2} +DEFINE_GUID(GUID_FRAMEDROP, +0x775d19bf, 0x4d8b, 0x4de6, 0x8d, 0xc9, 0x66, 0xba, 0xc7, 0xb3, 0x10, 0xa2); + +// {56D29065-EFBE-42dc-8C29-E325DC9C27D5} +DEFINE_GUID(GUID_AUDIOBREAK, +0x56d29065, 0xefbe, 0x42dc, 0x8c, 0x29, 0xe3, 0x25, 0xdc, 0x9c, 0x27, 0xd5); + +// {E1E6EA87-95A8-497e-BFBA-0295AEBCC707} +DEFINE_GUID(GUID_AUDIORECV, +0xe1e6ea87, 0x95a8, 0x497e, 0xbf, 0xba, 0x2, 0x95, 0xae, 0xbc, 0xc7, 0x7); + +// {10F7768A-B1E7-4242-AD90-A2D44683D9F0} +DEFINE_GUID(GUID_AUDIOSLAVE, +0x10f7768a, 0xb1e7, 0x4242, 0xad, 0x90, 0xa2, 0xd4, 0x46, 0x83, 0xd9, 0xf0); + +// {8983803D-691A-49bc-8FF6-962A39C0198F} +DEFINE_GUID(GUID_AUDIOADDBREAK, +0x8983803d, 0x691a, 0x49bc, 0x8f, 0xf6, 0x96, 0x2a, 0x39, 0xc0, 0x19, 0x8f); + +#define GLITCHTYPE_DSOUNDFIRSTGOOD 0 +#define GLITCHTYPE_DSOUNDFIRSTBAD 1 + +typedef struct PERFINFO_DSHOW_AUDIOGLITCH { + ULONGLONG cycleCounter; + DWORD glitchType; + LONGLONG sampleTime; + LONGLONG previousTime; + ULONG_PTR instanceId; +} PERFINFO_DSHOW_AUDIOGLITCH, *PPERFINFO_DSHOW_AUDIOGLITCH; + +typedef struct PERFINFO_WMI_AUDIOGLITCH { + EVENT_TRACE_HEADER header; + PERFINFO_DSHOW_AUDIOGLITCH data; +} PERFINFO_WMI_AUDIO_GLITCH, *PPERFINFO_WMI_AUDIOGLITCH; + +typedef struct PERFINFO_DSHOW_GETTIME { + ULONGLONG cycleCounter; + ULONGLONG dshowClock; +} PERFINFO_DSHOW_GETTIME, *PPERFINFO_DSHOW_GETTIME; + +typedef struct PERFINFO_WMI_GETTIME { + EVENT_TRACE_HEADER header; + PERFINFO_DSHOW_GETTIME data; +} PERFINFO_WMI_GETTIME, *PPERFINFO_WMI_GETTIME; + +typedef struct PERFINFO_DSHOW_AVREND { + ULONGLONG cycleCounter; + ULONGLONG dshowClock; + ULONGLONG sampleTime; +} PERFINFO_DSHOW_AVREND, *PPERFINFO_DSHOW_AVREND; + +typedef struct PERFINFO_WMI_AVREND { + EVENT_TRACE_HEADER header; + PERFINFO_DSHOW_AVREND data; +} PERFINFO_WMI_AVREND, *PPERFINFO_WMI_AVREND; + +typedef struct PERFINFO_DSHOW_AUDIOBREAK { + ULONGLONG cycleCounter; + ULONGLONG dshowClock; + ULONGLONG sampleTime; + ULONGLONG sampleDuration; +} PERFINFO_DSHOW_AUDIOBREAK, *PPERFINFO_DSHOW_AUDIOBREAK; + +typedef struct PERFINFO_WMI_AUDIOBREAK { + EVENT_TRACE_HEADER header; + PERFINFO_DSHOW_AUDIOBREAK data; +} PERFINFO_WMI_AUDIOBREAK, *PPERFINFO_WMI_AUDIOBREAK; + +typedef struct PERFINFO_DSHOW_FRAMEDROP { + ULONGLONG cycleCounter; + ULONGLONG dshowClock; + ULONGLONG frameTime; +} PERFINFO_DSHOW_FRAMEDROP, *PPERFINFO_DSHOW_FRAMEDROP; + +typedef struct PERFINFO_WMI_FRAMEDROP { + EVENT_TRACE_HEADER header; + PERFINFO_DSHOW_FRAMEDROP data; +} PERFINFO_WMI_FRAMEDROP, *PPERFINFO_WMI_FRAMEDROP; + +#define PERFINFO_STREAMTRACE_MPEG2DEMUX_PTS_TRANSLATION 1 +#define PERFINFO_STREAMTRACE_MPEG2DEMUX_SAMPLE_RECEIVED 2 +#define PERFINFO_STREAMTRACE_VMR_BEGIN_ADVISE 3 +#define PERFINFO_STREAMTRACE_VMR_END_ADVISE 4 +#define PERFINFO_STREAMTRACE_VMR_RECEIVE 5 +#define PERFINFO_STREAMTRACE_VMR_BEGIN_DEINTERLACE 6 +#define PERFINFO_STREAMTRACE_VMR_END_DEINTERLACE 7 +#define PERFINFO_STREAMTRACE_VMR_BEGIN_DECODE 8 +#define PERFINFO_STREAMTRACE_VMR_END_DECODE 9 +#define PERFINFO_STREAMTRACE_VMR_DROPPED_FRAME 10 +#define PERFINFO_STREAMTRACE_ENCDEC_DTFILTERINPUT 11 +#define PERFINFO_STREAMTRACE_ENCDEC_DTFILTEROUTPUT 12 +#define PERFINFO_STREAMTRACE_ENCDEC_ETFILTERINPUT 13 +#define PERFINFO_STREAMTRACE_ENCDEC_ETFILTEROUTPUT 14 +#define PERFINFO_STREAMTRACE_ENCDEC_XDSCODECINPUT 15 +#define PERFINFO_STREAMTRACE_SBE_DVRANALYSISINPUT_RECEIVE 16 +#define PERFINFO_STREAMTRACE_SBE_DVRANALYSISINPUT_DELIVER 17 +#define PERFINFO_STREAMTRACE_SBE_DVRINPUTPIN_RECEIVE 18 +#define PERFINFO_STREAMTRACE_SBE_DVROUTPUTPIN_RECEIVE 19 +#define PERFINFO_STREAMTRACE_VMR_RENDER_TIME 20 + +typedef struct _PERFINFO_DSHOW_STREAMTRACE { + ULONG id; + ULONG reserved; + ULONGLONG dshowClock; + ULONGLONG data[ 4 ]; +} PERFINFO_DSHOW_STREAMTRACE, *PPERFINFO_DSHOW_STREAMTRACE; + +typedef struct _PERFINFO_WMI_STREAMTRACE { + EVENT_TRACE_HEADER header; + PERFINFO_DSHOW_STREAMTRACE data; +} PERFINFO_WMI_STREAMTRACE, *PPERFINFO_WMI_STREAMTRACE; + + +typedef struct PERFINFO_DSHOW_AUDIORECV { + LONGLONG streamTime ; + LONGLONG sampleStart ; + LONGLONG sampleStop ; + LONGLONG hwduration ; + BOOL discontinuity ; +} PERFINFO_DSHOW_AUDIORECV, *PPERFINFO_DSHOW_AUDIORECV; + +typedef struct PERFINFO_WMI_AUDIORECV { + EVENT_TRACE_HEADER header; + PERFINFO_DSHOW_AUDIORECV data; +} PERFINFO_WMI_AUDIORECV, *PPERFINFO_WMI_AUDIORECV; + +typedef struct PERFINFO_DSHOW_AUDIOSLAVE { + LONGLONG masterClock ; + LONGLONG slaveClock ; + LONGLONG errorAccum ; + LONGLONG lastHighErrorSeen ; + LONGLONG lastLowErrorSeen ; +} PERFINFO_DSHOW_AUDIOSLAVE, *PPERFINFO_DSHOW_AUDIOSLAVE; + +typedef struct PERFINFO_WMI_AUDIOSLAVE { + EVENT_TRACE_HEADER header; + PERFINFO_DSHOW_AUDIOSLAVE data; +} PERFINFO_WMI_AUDIOSLAVE, *PPERFINFO_WMI_AUDIOSLAVE; + +typedef struct PERFINFO_DSHOW_AUDIOADDBREAK { + DWORD iterNextWrite ; + DWORD offsetNextWrite ; + DWORD iterWrite ; + DWORD offsetWrite ; +} PERFINFO_DSHOW_AUDIOADDBREAK, *PPERFINFO_DSHOW_AUDIOADDBREAK; + +typedef struct PERFINFO_WMI_AUDIOADDBREAK { + EVENT_TRACE_HEADER header; + PERFINFO_DSHOW_AUDIOADDBREAK data; +} PERFINFO_WMI_AUDIOADDBREAK, *PPERFINFO_WMI_AUDIOADDBREAK; + +#endif // _PREFSTRUCT_H_ diff --git a/Src/Plugins/Input/in_dshow/base/pstream.cpp b/Src/Plugins/Input/in_dshow/base/pstream.cpp new file mode 100644 index 00000000..3f747f5b --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/pstream.cpp @@ -0,0 +1,197 @@ +//------------------------------------------------------------------------------ +// File: PStream.cpp +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> +#include <strsafe.h> + +#ifdef PERF +#include <measure.h> +#endif +// #include "pstream.h" in streams.h + +// +// Constructor +// +CPersistStream::CPersistStream(IUnknown *punk, __inout HRESULT *phr) + : mPS_fDirty(FALSE) +{ + mPS_dwFileVersion = GetSoftwareVersion(); +} + + +// +// Destructor +// +CPersistStream::~CPersistStream() { + // Nothing to do +} + +#if 0 +SAMPLE CODE TO COPY - not active at the moment + +// +// NonDelegatingQueryInterface +// +// This object supports IPersist & IPersistStream +STDMETHODIMP CPersistStream::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + if (riid == IID_IPersist) { + return GetInterface((IPersist *) this, ppv); // ??? + } + else if (riid == IID_IPersistStream) { + return GetInterface((IPersistStream *) this, ppv); + } + else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } +} +#endif + + +// +// WriteToStream +// +// Writes to the stream (default action is to write nothing) +HRESULT CPersistStream::WriteToStream(IStream *pStream) +{ + // You can override this to do things like + // hr = pStream->Write(MyStructure, sizeof(MyStructure), NULL); + + return NOERROR; +} + + + +HRESULT CPersistStream::ReadFromStream(IStream * pStream) +{ + // You can override this to do things like + // hr = pStream->Read(MyStructure, sizeof(MyStructure), NULL); + + return NOERROR; +} + + +// +// Load +// +// Load all the data from the given stream +STDMETHODIMP CPersistStream::Load(LPSTREAM pStm) +{ + HRESULT hr; + // Load the version number then the data + mPS_dwFileVersion = ReadInt(pStm, hr); + if (FAILED(hr)) { + return hr; + } + + return ReadFromStream(pStm); +} // Load + + + +// +// Save +// +// Save the contents of this Stream. +STDMETHODIMP CPersistStream::Save(LPSTREAM pStm, BOOL fClearDirty) +{ + + HRESULT hr = WriteInt(pStm, GetSoftwareVersion()); + if (FAILED(hr)) { + return hr; + } + + hr = WriteToStream(pStm); + if (FAILED(hr)) { + return hr; + } + + mPS_fDirty = !fClearDirty; + + return hr; +} // Save + + +// WriteInt +// +// Writes an integer to an IStream as 11 UNICODE characters followed by one space. +// You could use this for shorts or unsigneds or anything (up to 32 bits) +// where the value isn't actually truncated by squeezing it into 32 bits. +// Values such as (unsigned) 0x80000000 would come out as -2147483648 +// but would then load as 0x80000000 through ReadInt. Cast as you please. + +STDAPI WriteInt(IStream *pIStream, int n) +{ + WCHAR Buff[13]; // Allows for trailing null that we don't write + (void)StringCchPrintfW(Buff, NUMELMS(Buff),L"%011d ",n); + return pIStream->Write(&(Buff[0]), 12*sizeof(WCHAR), NULL); +} // WriteInt + + +// ReadInt +// +// Reads an integer from an IStream. +// Read as 4 bytes. You could use this for shorts or unsigneds or anything +// where the value isn't actually truncated by squeezing it into 32 bits +// Striped down subset of what sscanf can do (without dragging in the C runtime) + +STDAPI_(int) ReadInt(IStream *pIStream, __out HRESULT &hr) +{ + + int Sign = 1; + unsigned int n = 0; // result wil be n*Sign + WCHAR wch; + + hr = pIStream->Read( &wch, sizeof(wch), NULL); + if (FAILED(hr)) { + return 0; + } + + if (wch==L'-'){ + Sign = -1; + hr = pIStream->Read( &wch, sizeof(wch), NULL); + if (FAILED(hr)) { + return 0; + } + } + + for( ; ; ) { + if (wch>=L'0' && wch<=L'9') { + n = 10*n+(int)(wch-L'0'); + } else if ( wch == L' ' + || wch == L'\t' + || wch == L'\r' + || wch == L'\n' + || wch == L'\0' + ) { + break; + } else { + hr = VFW_E_INVALID_FILE_FORMAT; + return 0; + } + + hr = pIStream->Read( &wch, sizeof(wch), NULL); + if (FAILED(hr)) { + return 0; + } + } + + if (n==0x80000000 && Sign==-1) { + // This is the negative number that has no positive version! + return (int)n; + } + else return (int)n * Sign; +} // ReadInt + + +// The microsoft C/C++ compile generates level 4 warnings to the effect that +// a particular inline function (from some base class) was not needed. +// This line gets rid of hundreds of such unwanted messages and makes +// -W4 compilation feasible: +#pragma warning(disable: 4514) diff --git a/Src/Plugins/Input/in_dshow/base/pstream.h b/Src/Plugins/Input/in_dshow/base/pstream.h new file mode 100644 index 00000000..2e278abf --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/pstream.h @@ -0,0 +1,114 @@ +//------------------------------------------------------------------------------ +// File: PStream.h +// +// Desc: DirectShow base classes - defines a class for persistent properties +// of filters. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __PSTREAM__ +#define __PSTREAM__ + +// Base class for persistent properties of filters +// (i.e. filter properties in saved graphs) + +// The simplest way to use this is: +// 1. Arrange for your filter to inherit this class +// 2. Implement in your class WriteToStream and ReadFromStream +// These will override the "do nothing" functions here. +// 3. Change your NonDelegatingQueryInterface to handle IPersistStream +// 4. Implement SizeMax to return the number of bytes of data you save. +// If you save UNICODE data, don't forget a char is 2 bytes. +// 5. Whenever your data changes, call SetDirty() +// +// At some point you may decide to alter, or extend the format of your data. +// At that point you will wish that you had a version number in all the old +// saved graphs, so that you can tell, when you read them, whether they +// represent the old or new form. To assist you in this, this class +// writes and reads a version number. +// When it writes, it calls GetSoftwareVersion() to enquire what version +// of the software we have at the moment. (In effect this is a version number +// of the data layout in the file). It writes this as the first thing in the data. +// If you want to change the version, implement (override) GetSoftwareVersion(). +// It reads this from the file into mPS_dwFileVersion before calling ReadFromStream, +// so in ReadFromStream you can check mPS_dwFileVersion to see if you are reading +// an old version file. +// Normally you should accept files whose version is no newer than the software +// version that's reading them. + + +// CPersistStream +// +// Implements IPersistStream. +// See 'OLE Programmers Reference (Vol 1):Structured Storage Overview' for +// more implementation information. +class CPersistStream : public IPersistStream { + private: + + // Internal state: + + protected: + DWORD mPS_dwFileVersion; // version number of file (being read) + BOOL mPS_fDirty; + + public: + + // IPersistStream methods + + STDMETHODIMP IsDirty() + {return (mPS_fDirty ? S_OK : S_FALSE);} // note FALSE means clean + STDMETHODIMP Load(LPSTREAM pStm); + STDMETHODIMP Save(LPSTREAM pStm, BOOL fClearDirty); + STDMETHODIMP GetSizeMax(__out ULARGE_INTEGER * pcbSize) + // Allow 24 bytes for version. + { pcbSize->QuadPart = 12*sizeof(WCHAR)+SizeMax(); return NOERROR; } + + // implementation + + CPersistStream(IUnknown *punk, __inout HRESULT *phr); + ~CPersistStream(); + + HRESULT SetDirty(BOOL fDirty) + { mPS_fDirty = fDirty; return NOERROR;} + + + // override to reveal IPersist & IPersistStream + // STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv); + + // --- IPersist --- + + // You must override this to provide your own class id + STDMETHODIMP GetClassID(__out CLSID *pClsid) PURE; + + // overrideable if you want + // file version number. Override it if you ever change format + virtual DWORD GetSoftwareVersion(void) { return 0; } + + + //========================================================================= + // OVERRIDE THESE to read and write your data + // OVERRIDE THESE to read and write your data + // OVERRIDE THESE to read and write your data + + virtual int SizeMax() {return 0;} + virtual HRESULT WriteToStream(IStream *pStream); + virtual HRESULT ReadFromStream(IStream *pStream); + //========================================================================= + + private: + +}; + + +// --- Useful helpers --- + + +// Writes an int to an IStream as UNICODE. +STDAPI WriteInt(IStream *pIStream, int n); + +// inverse of WriteInt +STDAPI_(int) ReadInt(IStream *pIStream, __out HRESULT &hr); + +#endif // __PSTREAM__ diff --git a/Src/Plugins/Input/in_dshow/base/pullpin.cpp b/Src/Plugins/Input/in_dshow/base/pullpin.cpp new file mode 100644 index 00000000..ee205cf5 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/pullpin.cpp @@ -0,0 +1,588 @@ +//------------------------------------------------------------------------------ +// File: PullPin.cpp +// +// Desc: DirectShow base classes - implements CPullPin class that pulls data +// from IAsyncReader. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> +#include "pullpin.h" + +#ifdef DXMPERF +#include "dxmperf.h" +#endif // DXMPERF + + +CPullPin::CPullPin() + : m_pReader(NULL), + m_pAlloc(NULL), + m_State(TM_Exit) +{ +#ifdef DXMPERF + PERFLOG_CTOR( L"CPullPin", this ); +#endif // DXMPERF + +} + +CPullPin::~CPullPin() +{ + Disconnect(); + +#ifdef DXMPERF + PERFLOG_DTOR( L"CPullPin", this ); +#endif // DXMPERF + +} + +// returns S_OK if successfully connected to an IAsyncReader interface +// from this object +// Optional allocator should be proposed as a preferred allocator if +// necessary +HRESULT +CPullPin::Connect(IUnknown* pUnk, IMemAllocator* pAlloc, BOOL bSync) +{ + CAutoLock lock(&m_AccessLock); + + if (m_pReader) { + return VFW_E_ALREADY_CONNECTED; + } + + HRESULT hr = pUnk->QueryInterface(IID_IAsyncReader, (void**)&m_pReader); + if (FAILED(hr)) { + +#ifdef DXMPERF + { + AM_MEDIA_TYPE * pmt = NULL; + PERFLOG_CONNECT( this, pUnk, hr, pmt ); + } +#endif // DXMPERF + + return(hr); + } + + hr = DecideAllocator(pAlloc, NULL); + if (FAILED(hr)) { + Disconnect(); + +#ifdef DXMPERF + { + AM_MEDIA_TYPE * pmt = NULL; + PERFLOG_CONNECT( this, pUnk, hr, pmt ); + } +#endif // DXMPERF + + return hr; + } + + LONGLONG llTotal, llAvail; + hr = m_pReader->Length(&llTotal, &llAvail); + if (FAILED(hr)) { + Disconnect(); + +#ifdef DXMPERF + { + AM_MEDIA_TYPE * pmt = NULL; + PERFLOG_CONNECT( this, pUnk, hr, pmt ); + } +#endif + + return hr; + } + + // convert from file position to reference time + m_tDuration = llTotal * UNITS; + m_tStop = m_tDuration; + m_tStart = 0; + + m_bSync = bSync; + +#ifdef DXMPERF + { + AM_MEDIA_TYPE * pmt = NULL; + PERFLOG_CONNECT( this, pUnk, S_OK, pmt ); + } +#endif // DXMPERF + + + return S_OK; +} + +// disconnect any connection made in Connect +HRESULT +CPullPin::Disconnect() +{ + CAutoLock lock(&m_AccessLock); + + StopThread(); + + +#ifdef DXMPERF + PERFLOG_DISCONNECT( this, m_pReader, S_OK ); +#endif // DXMPERF + + + if (m_pReader) { + m_pReader->Release(); + m_pReader = NULL; + } + + if (m_pAlloc) { + m_pAlloc->Release(); + m_pAlloc = NULL; + } + + return S_OK; +} + +// agree an allocator using RequestAllocator - optional +// props param specifies your requirements (non-zero fields). +// returns an error code if fail to match requirements. +// optional IMemAllocator interface is offered as a preferred allocator +// but no error occurs if it can't be met. +HRESULT +CPullPin::DecideAllocator( + IMemAllocator * pAlloc, + __inout_opt ALLOCATOR_PROPERTIES * pProps) +{ + ALLOCATOR_PROPERTIES *pRequest; + ALLOCATOR_PROPERTIES Request; + if (pProps == NULL) { + Request.cBuffers = 3; + Request.cbBuffer = 64*1024; + Request.cbAlign = 0; + Request.cbPrefix = 0; + pRequest = &Request; + } else { + pRequest = pProps; + } + HRESULT hr = m_pReader->RequestAllocator( + pAlloc, + pRequest, + &m_pAlloc); + return hr; +} + +// start pulling data +HRESULT +CPullPin::Active(void) +{ + ASSERT(!ThreadExists()); + return StartThread(); +} + +// stop pulling data +HRESULT +CPullPin::Inactive(void) +{ + StopThread(); + + return S_OK; +} + +HRESULT +CPullPin::Seek(REFERENCE_TIME tStart, REFERENCE_TIME tStop) +{ + CAutoLock lock(&m_AccessLock); + + ThreadMsg AtStart = m_State; + + if (AtStart == TM_Start) { + BeginFlush(); + PauseThread(); + EndFlush(); + } + + m_tStart = tStart; + m_tStop = tStop; + + HRESULT hr = S_OK; + if (AtStart == TM_Start) { + hr = StartThread(); + } + + return hr; +} + +HRESULT +CPullPin::Duration(__out REFERENCE_TIME* ptDuration) +{ + *ptDuration = m_tDuration; + return S_OK; +} + + +HRESULT +CPullPin::StartThread() +{ + CAutoLock lock(&m_AccessLock); + + if (!m_pAlloc || !m_pReader) { + return E_UNEXPECTED; + } + + HRESULT hr; + if (!ThreadExists()) { + + // commit allocator + hr = m_pAlloc->Commit(); + if (FAILED(hr)) { + return hr; + } + + // start thread + if (!Create()) { + return E_FAIL; + } + } + + m_State = TM_Start; + hr = (HRESULT) CallWorker(m_State); + return hr; +} + +HRESULT +CPullPin::PauseThread() +{ + CAutoLock lock(&m_AccessLock); + + if (!ThreadExists()) { + return E_UNEXPECTED; + } + + // need to flush to ensure the thread is not blocked + // in WaitForNext + HRESULT hr = m_pReader->BeginFlush(); + if (FAILED(hr)) { + return hr; + } + + m_State = TM_Pause; + hr = CallWorker(TM_Pause); + + m_pReader->EndFlush(); + return hr; +} + +HRESULT +CPullPin::StopThread() +{ + CAutoLock lock(&m_AccessLock); + + if (!ThreadExists()) { + return S_FALSE; + } + + // need to flush to ensure the thread is not blocked + // in WaitForNext + HRESULT hr = m_pReader->BeginFlush(); + if (FAILED(hr)) { + return hr; + } + + m_State = TM_Exit; + hr = CallWorker(TM_Exit); + + m_pReader->EndFlush(); + + // wait for thread to completely exit + Close(); + + // decommit allocator + if (m_pAlloc) { + m_pAlloc->Decommit(); + } + + return S_OK; +} + + +DWORD +CPullPin::ThreadProc(void) +{ + while(1) { + DWORD cmd = GetRequest(); + switch(cmd) { + case TM_Exit: + Reply(S_OK); + return 0; + + case TM_Pause: + // we are paused already + Reply(S_OK); + break; + + case TM_Start: + Reply(S_OK); + Process(); + break; + } + + // at this point, there should be no outstanding requests on the + // upstream filter. + // We should force begin/endflush to ensure that this is true. + // !!!Note that we may currently be inside a BeginFlush/EndFlush pair + // on another thread, but the premature EndFlush will do no harm now + // that we are idle. + m_pReader->BeginFlush(); + CleanupCancelled(); + m_pReader->EndFlush(); + } +} + +HRESULT +CPullPin::QueueSample( + __inout REFERENCE_TIME& tCurrent, + REFERENCE_TIME tAlignStop, + BOOL bDiscontinuity + ) +{ + IMediaSample* pSample; + + HRESULT hr = m_pAlloc->GetBuffer(&pSample, NULL, NULL, 0); + if (FAILED(hr)) { + return hr; + } + + LONGLONG tStopThis = tCurrent + (pSample->GetSize() * UNITS); + if (tStopThis > tAlignStop) { + tStopThis = tAlignStop; + } + pSample->SetTime(&tCurrent, &tStopThis); + tCurrent = tStopThis; + + pSample->SetDiscontinuity(bDiscontinuity); + + hr = m_pReader->Request( + pSample, + 0); + if (FAILED(hr)) { + pSample->Release(); + + CleanupCancelled(); + OnError(hr); + } + return hr; +} + +HRESULT +CPullPin::CollectAndDeliver( + REFERENCE_TIME tStart, + REFERENCE_TIME tStop) +{ + IMediaSample* pSample = NULL; // better be sure pSample is set + DWORD_PTR dwUnused; + HRESULT hr = m_pReader->WaitForNext( + INFINITE, + &pSample, + &dwUnused); + if (FAILED(hr)) { + if (pSample) { + pSample->Release(); + } + } else { + hr = DeliverSample(pSample, tStart, tStop); + } + if (FAILED(hr)) { + CleanupCancelled(); + OnError(hr); + } + return hr; + +} + +HRESULT +CPullPin::DeliverSample( + IMediaSample* pSample, + REFERENCE_TIME tStart, + REFERENCE_TIME tStop + ) +{ + // fix up sample if past actual stop (for sector alignment) + REFERENCE_TIME t1, t2; + if (S_OK == pSample->GetTime(&t1, &t2)) { + if (t2 > tStop) { + t2 = tStop; + } + + // adjust times to be relative to (aligned) start time + t1 -= tStart; + t2 -= tStart; + HRESULT hr = pSample->SetTime(&t1, &t2); + if (FAILED(hr)) { + return hr; + } + } + +#ifdef DXMPERF + { + AM_MEDIA_TYPE * pmt = NULL; + pSample->GetMediaType( &pmt ); + PERFLOG_RECEIVE( L"CPullPin", m_pReader, this, pSample, pmt ); + } +#endif + + HRESULT hr = Receive(pSample); + pSample->Release(); + return hr; +} + +void +CPullPin::Process(void) +{ + // is there anything to do? + if (m_tStop <= m_tStart) { + EndOfStream(); + return; + } + + BOOL bDiscontinuity = TRUE; + + // if there is more than one sample at the allocator, + // then try to queue 2 at once in order to overlap. + // -- get buffer count and required alignment + ALLOCATOR_PROPERTIES Actual; + HRESULT hr = m_pAlloc->GetProperties(&Actual); + + // align the start position downwards + REFERENCE_TIME tStart = AlignDown(m_tStart / UNITS, Actual.cbAlign) * UNITS; + REFERENCE_TIME tCurrent = tStart; + + REFERENCE_TIME tStop = m_tStop; + if (tStop > m_tDuration) { + tStop = m_tDuration; + } + + // align the stop position - may be past stop, but that + // doesn't matter + REFERENCE_TIME tAlignStop = AlignUp(tStop / UNITS, Actual.cbAlign) * UNITS; + + + DWORD dwRequest; + + if (!m_bSync) { + + // Break out of the loop either if we get to the end or we're asked + // to do something else + while (tCurrent < tAlignStop) { + + // Break out without calling EndOfStream if we're asked to + // do something different + if (CheckRequest(&dwRequest)) { + return; + } + + // queue a first sample + if (Actual.cBuffers > 1) { + + hr = QueueSample(tCurrent, tAlignStop, TRUE); + bDiscontinuity = FALSE; + + if (FAILED(hr)) { + return; + } + } + + + + // loop queueing second and waiting for first.. + while (tCurrent < tAlignStop) { + + hr = QueueSample(tCurrent, tAlignStop, bDiscontinuity); + bDiscontinuity = FALSE; + + if (FAILED(hr)) { + return; + } + + hr = CollectAndDeliver(tStart, tStop); + if (S_OK != hr) { + + // stop if error, or if downstream filter said + // to stop. + return; + } + } + + if (Actual.cBuffers > 1) { + hr = CollectAndDeliver(tStart, tStop); + if (FAILED(hr)) { + return; + } + } + } + } else { + + // sync version of above loop + while (tCurrent < tAlignStop) { + + // Break out without calling EndOfStream if we're asked to + // do something different + if (CheckRequest(&dwRequest)) { + return; + } + + IMediaSample* pSample; + + hr = m_pAlloc->GetBuffer(&pSample, NULL, NULL, 0); + if (FAILED(hr)) { + OnError(hr); + return; + } + + LONGLONG tStopThis = tCurrent + (pSample->GetSize() * UNITS); + if (tStopThis > tAlignStop) { + tStopThis = tAlignStop; + } + pSample->SetTime(&tCurrent, &tStopThis); + tCurrent = tStopThis; + + if (bDiscontinuity) { + pSample->SetDiscontinuity(TRUE); + bDiscontinuity = FALSE; + } + + hr = m_pReader->SyncReadAligned(pSample); + + if (FAILED(hr)) { + pSample->Release(); + OnError(hr); + return; + } + + hr = DeliverSample(pSample, tStart, tStop); + if (hr != S_OK) { + if (FAILED(hr)) { + OnError(hr); + } + return; + } + } + } + + EndOfStream(); +} + +// after a flush, cancelled i/o will be waiting for collection +// and release +void +CPullPin::CleanupCancelled(void) +{ + while (1) { + IMediaSample * pSample; + DWORD_PTR dwUnused; + + HRESULT hr = m_pReader->WaitForNext( + 0, // no wait + &pSample, + &dwUnused); + if(pSample) { + pSample->Release(); + } else { + // no more samples + return; + } + } +} diff --git a/Src/Plugins/Input/in_dshow/base/pullpin.h b/Src/Plugins/Input/in_dshow/base/pullpin.h new file mode 100644 index 00000000..db4f4071 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/pullpin.h @@ -0,0 +1,152 @@ +//------------------------------------------------------------------------------ +// File: PullPin.h +// +// Desc: DirectShow base classes - defines CPullPin class. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __PULLPIN_H__ +#define __PULLPIN_H__ + +// +// CPullPin +// +// object supporting pulling data from an IAsyncReader interface. +// Given a start/stop position, calls a pure Receive method with each +// IMediaSample received. +// +// This is essentially for use in a MemInputPin when it finds itself +// connected to an IAsyncReader pin instead of a pushing pin. +// + +class CPullPin : public CAMThread +{ + IAsyncReader* m_pReader; + REFERENCE_TIME m_tStart; + REFERENCE_TIME m_tStop; + REFERENCE_TIME m_tDuration; + BOOL m_bSync; + + enum ThreadMsg { + TM_Pause, // stop pulling and wait for next message + TM_Start, // start pulling + TM_Exit, // stop and exit + }; + + ThreadMsg m_State; + + // override pure thread proc from CAMThread + DWORD ThreadProc(void); + + // running pull method (check m_bSync) + void Process(void); + + // clean up any cancelled i/o after a flush + void CleanupCancelled(void); + + // suspend thread from pulling, eg during seek + HRESULT PauseThread(); + + // start thread pulling - create thread if necy + HRESULT StartThread(); + + // stop and close thread + HRESULT StopThread(); + + // called from ProcessAsync to queue and collect requests + HRESULT QueueSample( + __inout REFERENCE_TIME& tCurrent, + REFERENCE_TIME tAlignStop, + BOOL bDiscontinuity); + + HRESULT CollectAndDeliver( + REFERENCE_TIME tStart, + REFERENCE_TIME tStop); + + HRESULT DeliverSample( + IMediaSample* pSample, + REFERENCE_TIME tStart, + REFERENCE_TIME tStop); + +protected: + IMemAllocator * m_pAlloc; + +public: + CPullPin(); + virtual ~CPullPin(); + + // returns S_OK if successfully connected to an IAsyncReader interface + // from this object + // Optional allocator should be proposed as a preferred allocator if + // necessary + // bSync is TRUE if we are to use sync reads instead of the + // async methods. + HRESULT Connect(IUnknown* pUnk, IMemAllocator* pAlloc, BOOL bSync); + + // disconnect any connection made in Connect + HRESULT Disconnect(); + + // agree an allocator using RequestAllocator - optional + // props param specifies your requirements (non-zero fields). + // returns an error code if fail to match requirements. + // optional IMemAllocator interface is offered as a preferred allocator + // but no error occurs if it can't be met. + virtual HRESULT DecideAllocator( + IMemAllocator* pAlloc, + __inout_opt ALLOCATOR_PROPERTIES * pProps); + + // set start and stop position. if active, will start immediately at + // the new position. Default is 0 to duration + HRESULT Seek(REFERENCE_TIME tStart, REFERENCE_TIME tStop); + + // return the total duration + HRESULT Duration(__out REFERENCE_TIME* ptDuration); + + // start pulling data + HRESULT Active(void); + + // stop pulling data + HRESULT Inactive(void); + + // helper functions + LONGLONG AlignDown(LONGLONG ll, LONG lAlign) { + // aligning downwards is just truncation + return ll & ~(lAlign-1); + }; + + LONGLONG AlignUp(LONGLONG ll, LONG lAlign) { + // align up: round up to next boundary + return (ll + (lAlign -1)) & ~(lAlign -1); + }; + + // GetReader returns the (addrefed) IAsyncReader interface + // for SyncRead etc + IAsyncReader* GetReader() { + m_pReader->AddRef(); + return m_pReader; + }; + + // -- pure -- + + // override this to handle data arrival + // return value other than S_OK will stop data + virtual HRESULT Receive(IMediaSample*) PURE; + + // override this to handle end-of-stream + virtual HRESULT EndOfStream(void) PURE; + + // called on runtime errors that will have caused pulling + // to stop + // these errors are all returned from the upstream filter, who + // will have already reported any errors to the filtergraph. + virtual void OnError(HRESULT hr) PURE; + + // flush this pin and all downstream + virtual HRESULT BeginFlush() PURE; + virtual HRESULT EndFlush() PURE; + +}; + +#endif //__PULLPIN_H__ diff --git a/Src/Plugins/Input/in_dshow/base/refclock.cpp b/Src/Plugins/Input/in_dshow/base/refclock.cpp new file mode 100644 index 00000000..42505eac --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/refclock.cpp @@ -0,0 +1,402 @@ +//------------------------------------------------------------------------------ +// File: RefClock.cpp +// +// Desc: DirectShow base classes - implements the IReferenceClock interface. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> +#include <limits.h> + +#ifdef DXMPERF +#include "dxmperf.h" +#endif // DXMPERF + + +// 'this' used in constructor list +#pragma warning(disable:4355) + + +STDMETHODIMP CBaseReferenceClock::NonDelegatingQueryInterface( + REFIID riid, + __deref_out void ** ppv) +{ + HRESULT hr; + + if (riid == IID_IReferenceClock) + { + hr = GetInterface((IReferenceClock *) this, ppv); + } + else if (riid == IID_IReferenceClockTimerControl) + { + hr = GetInterface((IReferenceClockTimerControl *) this, ppv); + } + else + { + hr = CUnknown::NonDelegatingQueryInterface(riid, ppv); + } + return hr; +} + +CBaseReferenceClock::~CBaseReferenceClock() +{ +#ifdef DXMPERF + PERFLOG_DTOR( L"CBaseReferenceClock", (IReferenceClock *) this ); +#endif // DXMPERF + + if (m_TimerResolution) timeEndPeriod(m_TimerResolution); + + if (m_pSchedule) + { + m_pSchedule->DumpLinkedList(); + } + + if (m_hThread) + { + m_bAbort = TRUE; + TriggerThread(); + WaitForSingleObject( m_hThread, INFINITE ); + EXECUTE_ASSERT( CloseHandle(m_hThread) ); + m_hThread = 0; + EXECUTE_ASSERT( CloseHandle(m_pSchedule->GetEvent()) ); + delete m_pSchedule; + } +} + +// A derived class may supply a hThreadEvent if it has its own thread that will take care +// of calling the schedulers Advise method. (Refere to CBaseReferenceClock::AdviseThread() +// to see what such a thread has to do.) +CBaseReferenceClock::CBaseReferenceClock( __in_opt LPCTSTR pName, + __inout_opt LPUNKNOWN pUnk, + __inout HRESULT *phr, + __inout_opt CAMSchedule * pShed ) +: CUnknown( pName, pUnk ) +, m_rtLastGotTime(0) +, m_TimerResolution(0) +, m_bAbort( FALSE ) +, m_pSchedule( pShed ? pShed : new CAMSchedule(CreateEvent(NULL, FALSE, FALSE, NULL)) ) +, m_hThread(0) +{ + +#ifdef DXMPERF + PERFLOG_CTOR( pName ? pName : L"CBaseReferenceClock", (IReferenceClock *) this ); +#endif // DXMPERF + + ASSERT(m_pSchedule); + if (!m_pSchedule) + { + *phr = E_OUTOFMEMORY; + } + else + { + // Set up the highest resolution timer we can manage + TIMECAPS tc; + m_TimerResolution = (TIMERR_NOERROR == timeGetDevCaps(&tc, sizeof(tc))) + ? tc.wPeriodMin + : 1; + + timeBeginPeriod(m_TimerResolution); + + /* Initialise our system times - the derived clock should set the right values */ + m_dwPrevSystemTime = timeGetTime(); + m_rtPrivateTime = (UNITS / MILLISECONDS) * m_dwPrevSystemTime; + + #ifdef PERF + m_idGetSystemTime = MSR_REGISTER(TEXT("CBaseReferenceClock::GetTime")); + #endif + + if ( !pShed ) + { + DWORD ThreadID; + m_hThread = ::CreateThread(NULL, // Security attributes + (DWORD) 0, // Initial stack size + AdviseThreadFunction, // Thread start address + (LPVOID) this, // Thread parameter + (DWORD) 0, // Creation flags + &ThreadID); // Thread identifier + + if (m_hThread) + { + SetThreadPriority( m_hThread, THREAD_PRIORITY_TIME_CRITICAL ); + } + else + { + *phr = E_FAIL; + EXECUTE_ASSERT( CloseHandle(m_pSchedule->GetEvent()) ); + delete m_pSchedule; + m_pSchedule = NULL; + } + } + } +} + +void CBaseReferenceClock::Restart (IN REFERENCE_TIME rtMinTime) +{ + Lock(); + m_rtLastGotTime = rtMinTime ; + Unlock(); +} + +STDMETHODIMP CBaseReferenceClock::GetTime(__out REFERENCE_TIME *pTime) +{ + HRESULT hr; + if (pTime) + { + REFERENCE_TIME rtNow; + Lock(); + rtNow = GetPrivateTime(); + if (rtNow > m_rtLastGotTime) + { + m_rtLastGotTime = rtNow; + hr = S_OK; + } + else + { + hr = S_FALSE; + } + *pTime = m_rtLastGotTime; + Unlock(); + MSR_INTEGER(m_idGetSystemTime, LONG((*pTime) / (UNITS/MILLISECONDS)) ); + +#ifdef DXMPERF + PERFLOG_GETTIME( (IReferenceClock *) this, *pTime ); +#endif // DXMPERF + + } + else hr = E_POINTER; + + return hr; +} + +/* Ask for an async notification that a time has elapsed */ + +STDMETHODIMP CBaseReferenceClock::AdviseTime( + REFERENCE_TIME baseTime, // base reference time + REFERENCE_TIME streamTime, // stream offset time + HEVENT hEvent, // advise via this event + __out DWORD_PTR *pdwAdviseCookie)// where your cookie goes +{ + CheckPointer(pdwAdviseCookie, E_POINTER); + *pdwAdviseCookie = 0; + + // Check that the event is not already set + ASSERT(WAIT_TIMEOUT == WaitForSingleObject(HANDLE(hEvent),0)); + + HRESULT hr; + + const REFERENCE_TIME lRefTime = baseTime + streamTime; + if ( lRefTime <= 0 || lRefTime == MAX_TIME ) + { + hr = E_INVALIDARG; + } + else + { + *pdwAdviseCookie = m_pSchedule->AddAdvisePacket( lRefTime, 0, HANDLE(hEvent), FALSE ); + hr = *pdwAdviseCookie ? NOERROR : E_OUTOFMEMORY; + } + return hr; +} + + +/* Ask for an asynchronous periodic notification that a time has elapsed */ + +STDMETHODIMP CBaseReferenceClock::AdvisePeriodic( + REFERENCE_TIME StartTime, // starting at this time + REFERENCE_TIME PeriodTime, // time between notifications + HSEMAPHORE hSemaphore, // advise via a semaphore + __out DWORD_PTR *pdwAdviseCookie) // where your cookie goes +{ + CheckPointer(pdwAdviseCookie, E_POINTER); + *pdwAdviseCookie = 0; + + HRESULT hr; + if (StartTime > 0 && PeriodTime > 0 && StartTime != MAX_TIME ) + { + *pdwAdviseCookie = m_pSchedule->AddAdvisePacket( StartTime, PeriodTime, HANDLE(hSemaphore), TRUE ); + hr = *pdwAdviseCookie ? NOERROR : E_OUTOFMEMORY; + } + else hr = E_INVALIDARG; + + return hr; +} + + +STDMETHODIMP CBaseReferenceClock::Unadvise(DWORD_PTR dwAdviseCookie) +{ + return m_pSchedule->Unadvise(dwAdviseCookie); +} + + +REFERENCE_TIME CBaseReferenceClock::GetPrivateTime() +{ + CAutoLock cObjectLock(this); + + + /* If the clock has wrapped then the current time will be less than + * the last time we were notified so add on the extra milliseconds + * + * The time period is long enough so that the likelihood of + * successive calls spanning the clock cycle is not considered. + */ + + DWORD dwTime = timeGetTime(); + { + m_rtPrivateTime += Int32x32To64(UNITS / MILLISECONDS, (DWORD)(dwTime - m_dwPrevSystemTime)); + m_dwPrevSystemTime = dwTime; + } + + return m_rtPrivateTime; +} + + +/* Adjust the current time by the input value. This allows an + external time source to work out some of the latency of the clock + system and adjust the "current" time accordingly. The intent is + that the time returned to the user is synchronised to a clock + source and allows drift to be catered for. + + For example: if the clock source detects a drift it can pass a delta + to the current time rather than having to set an explicit time. +*/ + +STDMETHODIMP CBaseReferenceClock::SetTimeDelta(const REFERENCE_TIME & TimeDelta) +{ +#ifdef DEBUG + + // Just break if passed an improper time delta value + LONGLONG llDelta = TimeDelta > 0 ? TimeDelta : -TimeDelta; + if (llDelta > UNITS * 1000) { + DbgLog((LOG_TRACE, 0, TEXT("Bad Time Delta"))); + //DebugBreak(); + } + + // We're going to calculate a "severity" for the time change. Max -1 + // min 8. We'll then use this as the debug logging level for a + // debug log message. + const LONG usDelta = LONG(TimeDelta/10); // Delta in micro-secs + + DWORD delta = abs(usDelta); // varying delta + // Severity == 8 - ceil(log<base 8>(abs( micro-secs delta))) + int Severity = 8; + while ( delta > 0 ) + { + delta >>= 3; // div 8 + Severity--; + } + + // Sev == 0 => > 2 second delta! + DbgLog((LOG_TIMING, Severity < 0 ? 0 : Severity, + TEXT("Sev %2i: CSystemClock::SetTimeDelta(%8ld us) %lu -> %lu ms."), + Severity, usDelta, DWORD(ConvertToMilliseconds(m_rtPrivateTime)), + DWORD(ConvertToMilliseconds(TimeDelta+m_rtPrivateTime)) )); + + // Don't want the DbgBreak to fire when running stress on debug-builds. + #ifdef BREAK_ON_SEVERE_TIME_DELTA + if (Severity < 0) + DbgBreakPoint(TEXT("SetTimeDelta > 16 seconds!"), + TEXT(__FILE__),__LINE__); + #endif + +#endif + + CAutoLock cObjectLock(this); + m_rtPrivateTime += TimeDelta; + // If time goes forwards, and we have advises, then we need to + // trigger the thread so that it can re-evaluate its wait time. + // Since we don't want the cost of the thread switches if the change + // is really small, only do it if clock goes forward by more than + // 0.5 millisecond. If the time goes backwards, the thread will + // wake up "early" (relativly speaking) and will re-evaluate at + // that time. + if ( TimeDelta > 5000 && m_pSchedule->GetAdviseCount() > 0 ) TriggerThread(); + return NOERROR; +} + +// Thread stuff + +DWORD __stdcall CBaseReferenceClock::AdviseThreadFunction(__in LPVOID p) +{ + return DWORD(reinterpret_cast<CBaseReferenceClock*>(p)->AdviseThread()); +} + +HRESULT CBaseReferenceClock::AdviseThread() +{ + DWORD dwWait = INFINITE; + + // The first thing we do is wait until something interesting happens + // (meaning a first advise or shutdown). This prevents us calling + // GetPrivateTime immediately which is goodness as that is a virtual + // routine and the derived class may not yet be constructed. (This + // thread is created in the base class constructor.) + + while ( !m_bAbort ) + { + // Wait for an interesting event to happen + DbgLog((LOG_TIMING, 3, TEXT("CBaseRefClock::AdviseThread() Delay: %lu ms"), dwWait )); + WaitForSingleObject(m_pSchedule->GetEvent(), dwWait); + if (m_bAbort) break; + + // There are several reasons why we need to work from the internal + // time, mainly to do with what happens when time goes backwards. + // Mainly, it stop us looping madly if an event is just about to + // expire when the clock goes backward (i.e. GetTime stop for a + // while). + const REFERENCE_TIME rtNow = GetPrivateTime(); + + DbgLog((LOG_TIMING, 3, + TEXT("CBaseRefClock::AdviseThread() Woke at = %lu ms"), + ConvertToMilliseconds(rtNow) )); + + // We must add in a millisecond, since this is the resolution of our + // WaitForSingleObject timer. Failure to do so will cause us to loop + // franticly for (approx) 1 a millisecond. + m_rtNextAdvise = m_pSchedule->Advise( 10000 + rtNow ); + LONGLONG llWait = m_rtNextAdvise - rtNow; + + ASSERT( llWait > 0 ); + + llWait = ConvertToMilliseconds(llWait); + // DON'T replace this with a max!! (The type's of these things is VERY important) + dwWait = (llWait > REFERENCE_TIME(UINT_MAX)) ? UINT_MAX : DWORD(llWait); + }; + return NOERROR; +} + +HRESULT CBaseReferenceClock::SetDefaultTimerResolution( + REFERENCE_TIME timerResolution // in 100ns + ) +{ + CAutoLock cObjectLock(this); + if( 0 == timerResolution ) { + if( m_TimerResolution ) { + timeEndPeriod( m_TimerResolution ); + m_TimerResolution = 0; + } + } else { + TIMECAPS tc; + DWORD dwMinResolution = (TIMERR_NOERROR == timeGetDevCaps(&tc, sizeof(tc))) + ? tc.wPeriodMin + : 1; + DWORD dwResolution = max( dwMinResolution, DWORD(timerResolution / 10000) ); + if( dwResolution != m_TimerResolution ) { + timeEndPeriod(m_TimerResolution); + m_TimerResolution = dwResolution; + timeBeginPeriod( m_TimerResolution ); + } + } + return S_OK; +} + +HRESULT CBaseReferenceClock::GetDefaultTimerResolution( + __out REFERENCE_TIME* pTimerResolution // in 100ns + ) +{ + if( !pTimerResolution ) { + return E_POINTER; + } + CAutoLock cObjectLock(this); + *pTimerResolution = m_TimerResolution * 10000; + return S_OK; +} diff --git a/Src/Plugins/Input/in_dshow/base/refclock.h b/Src/Plugins/Input/in_dshow/base/refclock.h new file mode 100644 index 00000000..df822e03 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/refclock.h @@ -0,0 +1,184 @@ +//------------------------------------------------------------------------------ +// File: RefClock.h +// +// Desc: DirectShow base classes - defines the IReferenceClock interface. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __BASEREFCLOCK__ +#define __BASEREFCLOCK__ + +#include <Schedule.h> + +const UINT RESOLUTION = 1; /* High resolution timer */ +const INT ADVISE_CACHE = 4; /* Default cache size */ +const LONGLONG MAX_TIME = 0x7FFFFFFFFFFFFFFF; /* Maximum LONGLONG value */ + +inline LONGLONG WINAPI ConvertToMilliseconds(const REFERENCE_TIME& RT) +{ + /* This converts an arbitrary value representing a reference time + into a MILLISECONDS value for use in subsequent system calls */ + + return (RT / (UNITS / MILLISECONDS)); +} + +/* This class hierarchy will support an IReferenceClock interface so + that an audio card (or other externally driven clock) can update the + system wide clock that everyone uses. + + The interface will be pretty thin with probably just one update method + This interface has not yet been defined. + */ + +/* This abstract base class implements the IReferenceClock + * interface. Classes that actually provide clock signals (from + * whatever source) have to be derived from this class. + * + * The abstract class provides implementations for: + * CUnknown support + * locking support (CCritSec) + * client advise code (creates a thread) + * + * Question: what can we do about quality? Change the timer + * resolution to lower the system load? Up the priority of the + * timer thread to force more responsive signals? + * + * During class construction we create a worker thread that is destroyed during + * destuction. This thread executes a series of WaitForSingleObject calls, + * waking up when a command is given to the thread or the next wake up point + * is reached. The wakeup points are determined by clients making Advise + * calls. + * + * Each advise call defines a point in time when they wish to be notified. A + * periodic advise is a series of these such events. We maintain a list of + * advise links and calculate when the nearest event notification is due for. + * We then call WaitForSingleObject with a timeout equal to this time. The + * handle we wait on is used by the class to signal that something has changed + * and that we must reschedule the next event. This typically happens when + * someone comes in and asks for an advise link while we are waiting for an + * event to timeout. + * + * While we are modifying the list of advise requests we + * are protected from interference through a critical section. Clients are NOT + * advised through callbacks. One shot clients have an event set, while + * periodic clients have a semaphore released for each event notification. A + * semaphore allows a client to be kept up to date with the number of events + * actually triggered and be assured that they can't miss multiple events being + * set. + * + * Keeping track of advises is taken care of by the CAMSchedule class. + */ + +class CBaseReferenceClock +: public CUnknown, public IReferenceClock, public CCritSec, public IReferenceClockTimerControl +{ +protected: + virtual ~CBaseReferenceClock(); // Don't let me be created on the stack! +public: + CBaseReferenceClock(__in_opt LPCTSTR pName, + __inout_opt LPUNKNOWN pUnk, + __inout HRESULT *phr, + __inout_opt CAMSchedule * pSched = 0 ); + + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void ** ppv); + + DECLARE_IUNKNOWN + + /* IReferenceClock methods */ + // Derived classes must implement GetPrivateTime(). All our GetTime + // does is call GetPrivateTime and then check so that time does not + // go backwards. A return code of S_FALSE implies that the internal + // clock has gone backwards and GetTime time has halted until internal + // time has caught up. (Don't know if this will be much use to folk, + // but it seems odd not to use the return code for something useful.) + STDMETHODIMP GetTime(__out REFERENCE_TIME *pTime); + // When this is called, it sets m_rtLastGotTime to the time it returns. + + /* Provide standard mechanisms for scheduling events */ + + /* Ask for an async notification that a time has elapsed */ + STDMETHODIMP AdviseTime( + REFERENCE_TIME baseTime, // base reference time + REFERENCE_TIME streamTime, // stream offset time + HEVENT hEvent, // advise via this event + __out DWORD_PTR *pdwAdviseCookie// where your cookie goes + ); + + /* Ask for an asynchronous periodic notification that a time has elapsed */ + STDMETHODIMP AdvisePeriodic( + REFERENCE_TIME StartTime, // starting at this time + REFERENCE_TIME PeriodTime, // time between notifications + HSEMAPHORE hSemaphore, // advise via a semaphore + __out DWORD_PTR *pdwAdviseCookie// where your cookie goes + ); + + /* Cancel a request for notification(s) - if the notification was + * a one shot timer then this function doesn't need to be called + * as the advise is automatically cancelled, however it does no + * harm to explicitly cancel a one-shot advise. It is REQUIRED that + * clients call Unadvise to clear a Periodic advise setting. + */ + + STDMETHODIMP Unadvise(DWORD_PTR dwAdviseCookie); + + /* Methods for the benefit of derived classes or outer objects */ + + // GetPrivateTime() is the REAL clock. GetTime is just a cover for + // it. Derived classes will probably override this method but not + // GetTime() itself. + // The important point about GetPrivateTime() is it's allowed to go + // backwards. Our GetTime() will keep returning the LastGotTime + // until GetPrivateTime() catches up. + virtual REFERENCE_TIME GetPrivateTime(); + + /* Provide a method for correcting drift */ + STDMETHODIMP SetTimeDelta( const REFERENCE_TIME& TimeDelta ); + + CAMSchedule * GetSchedule() const { return m_pSchedule; } + + // IReferenceClockTimerControl methods + // + // Setting a default of 0 disables the default of 1ms + STDMETHODIMP SetDefaultTimerResolution( + REFERENCE_TIME timerResolution // in 100ns + ); + STDMETHODIMP GetDefaultTimerResolution( + __out REFERENCE_TIME* pTimerResolution // in 100ns + ); + +private: + REFERENCE_TIME m_rtPrivateTime; // Current best estimate of time + DWORD m_dwPrevSystemTime; // Last vaule we got from timeGetTime + REFERENCE_TIME m_rtLastGotTime; // Last time returned by GetTime + REFERENCE_TIME m_rtNextAdvise; // Time of next advise + UINT m_TimerResolution; + +#ifdef PERF + int m_idGetSystemTime; +#endif + +// Thread stuff +public: + void TriggerThread() // Wakes thread up. Need to do this if + { // time to next advise needs reevaluating. + EXECUTE_ASSERT(SetEvent(m_pSchedule->GetEvent())); + } + + +private: + BOOL m_bAbort; // Flag used for thread shutdown + HANDLE m_hThread; // Thread handle + + HRESULT AdviseThread(); // Method in which the advise thread runs + static DWORD __stdcall AdviseThreadFunction(__in LPVOID); // Function used to get there + +protected: + CAMSchedule * m_pSchedule; + + void Restart (IN REFERENCE_TIME rtMinTime = 0I64) ; +}; + +#endif + diff --git a/Src/Plugins/Input/in_dshow/base/reftime.h b/Src/Plugins/Input/in_dshow/base/reftime.h new file mode 100644 index 00000000..0ed32f6e --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/reftime.h @@ -0,0 +1,116 @@ +//------------------------------------------------------------------------------ +// File: RefTime.h +// +// Desc: DirectShow base classes - defines CRefTime, a class that manages +// reference times. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// +// CRefTime +// +// Manage reference times. +// Shares same data layout as REFERENCE_TIME, but adds some (nonvirtual) +// functions providing simple comparison, conversion and arithmetic. +// +// A reference time (at the moment) is a unit of seconds represented in +// 100ns units as is used in the Win32 FILETIME structure. BUT the time +// a REFERENCE_TIME represents is NOT the time elapsed since 1/1/1601 it +// will either be stream time or reference time depending upon context +// +// This class provides simple arithmetic operations on reference times +// +// keep non-virtual otherwise the data layout will not be the same as +// REFERENCE_TIME + + +// ----- +// note that you are safe to cast a CRefTime* to a REFERENCE_TIME*, but +// you will need to do so explicitly +// ----- + + +#ifndef __REFTIME__ +#define __REFTIME__ + + +const LONGLONG MILLISECONDS = (1000); // 10 ^ 3 +const LONGLONG NANOSECONDS = (1000000000); // 10 ^ 9 +const LONGLONG UNITS = (NANOSECONDS / 100); // 10 ^ 7 + +/* Unfortunately an inline function here generates a call to __allmul + - even for constants! +*/ +#define MILLISECONDS_TO_100NS_UNITS(lMs) \ + Int32x32To64((lMs), (UNITS / MILLISECONDS)) + +class CRefTime +{ +public: + + // *MUST* be the only data member so that this class is exactly + // equivalent to a REFERENCE_TIME. + // Also, must be *no virtual functions* + + REFERENCE_TIME m_time; + + inline CRefTime() + { + // default to 0 time + m_time = 0; + }; + + inline CRefTime(LONG msecs) + { + m_time = MILLISECONDS_TO_100NS_UNITS(msecs); + }; + + inline CRefTime(REFERENCE_TIME rt) + { + m_time = rt; + }; + + inline operator REFERENCE_TIME() const + { + return m_time; + }; + + inline CRefTime& operator=(const CRefTime& rt) + { + m_time = rt.m_time; + return *this; + }; + + inline CRefTime& operator=(const LONGLONG ll) + { + m_time = ll; + return *this; + }; + + inline CRefTime& operator+=(const CRefTime& rt) + { + return (*this = *this + rt); + }; + + inline CRefTime& operator-=(const CRefTime& rt) + { + return (*this = *this - rt); + }; + + inline LONG Millisecs(void) + { + return (LONG)(m_time / (UNITS / MILLISECONDS)); + }; + + inline LONGLONG GetUnits(void) + { + return m_time; + }; +}; + +const LONGLONG TimeZero = 0; + +#endif /* __REFTIME__ */ + diff --git a/Src/Plugins/Input/in_dshow/base/renbase.cpp b/Src/Plugins/Input/in_dshow/base/renbase.cpp new file mode 100644 index 00000000..07a781e0 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/renbase.cpp @@ -0,0 +1,2858 @@ +//------------------------------------------------------------------------------ +// File: RenBase.cpp +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> // DirectShow base class definitions +#include <mmsystem.h> // Needed for definition of timeGetTime +#include <limits.h> // Standard data type limit definitions +#include <measure.h> // Used for time critical log functions + +#pragma warning(disable:4355) + +// Helper function for clamping time differences +int inline TimeDiff(REFERENCE_TIME rt) +{ + if (rt < - (50 * UNITS)) { + return -(50 * UNITS); + } else + if (rt > 50 * UNITS) { + return 50 * UNITS; + } else return (int)rt; +} + +// Implements the CBaseRenderer class + +CBaseRenderer::CBaseRenderer(REFCLSID RenderClass, // CLSID for this renderer + __in_opt LPCTSTR pName, // Debug ONLY description + __inout_opt LPUNKNOWN pUnk, // Aggregated owner object + __inout HRESULT *phr) : // General OLE return code + + CBaseFilter(pName,pUnk,&m_InterfaceLock,RenderClass), + m_evComplete(TRUE, phr), + m_RenderEvent(FALSE, phr), + m_bAbort(FALSE), + m_pPosition(NULL), + m_ThreadSignal(TRUE, phr), + m_bStreaming(FALSE), + m_bEOS(FALSE), + m_bEOSDelivered(FALSE), + m_pMediaSample(NULL), + m_dwAdvise(0), + m_pQSink(NULL), + m_pInputPin(NULL), + m_bRepaintStatus(TRUE), + m_SignalTime(0), + m_bInReceive(FALSE), + m_EndOfStreamTimer(0) +{ + // if (SUCCEEDED(*phr)) { + Ready(); +#ifdef PERF + m_idBaseStamp = MSR_REGISTER(TEXT("BaseRenderer: sample time stamp")); + m_idBaseRenderTime = MSR_REGISTER(TEXT("BaseRenderer: draw time (msec)")); + m_idBaseAccuracy = MSR_REGISTER(TEXT("BaseRenderer: Accuracy (msec)")); +#endif + // } +} + + +// Delete the dynamically allocated IMediaPosition and IMediaSeeking helper +// object. The object is created when somebody queries us. These are standard +// control interfaces for seeking and setting start/stop positions and rates. +// We will probably also have made an input pin based on CRendererInputPin +// that has to be deleted, it's created when an enumerator calls our GetPin + +CBaseRenderer::~CBaseRenderer() +{ + ASSERT(m_bStreaming == FALSE); + ASSERT(m_EndOfStreamTimer == 0); + StopStreaming(); + ClearPendingSample(); + + // Delete any IMediaPosition implementation + + if (m_pPosition) { + delete m_pPosition; + m_pPosition = NULL; + } + + // Delete any input pin created + + if (m_pInputPin) { + delete m_pInputPin; + m_pInputPin = NULL; + } + + // Release any Quality sink + + ASSERT(m_pQSink == NULL); +} + + +// This returns the IMediaPosition and IMediaSeeking interfaces + +HRESULT CBaseRenderer::GetMediaPositionInterface(REFIID riid, __deref_out void **ppv) +{ + CAutoLock cObjectCreationLock(&m_ObjectCreationLock); + if (m_pPosition) { + return m_pPosition->NonDelegatingQueryInterface(riid,ppv); + } + + CBasePin *pPin = GetPin(0); + if (NULL == pPin) { + return E_OUTOFMEMORY; + } + + HRESULT hr = NOERROR; + + // Create implementation of this dynamically since sometimes we may + // never try and do a seek. The helper object implements a position + // control interface (IMediaPosition) which in fact simply takes the + // calls normally from the filter graph and passes them upstream + + m_pPosition = new CRendererPosPassThru(NAME("Renderer CPosPassThru"), + CBaseFilter::GetOwner(), + (HRESULT *) &hr, + pPin); + if (m_pPosition == NULL) { + return E_OUTOFMEMORY; + } + + if (FAILED(hr)) { + delete m_pPosition; + m_pPosition = NULL; + return E_NOINTERFACE; + } + return GetMediaPositionInterface(riid,ppv); +} + + +// Overriden to say what interfaces we support and where + +STDMETHODIMP CBaseRenderer::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + // Do we have this interface + + if (riid == IID_IMediaPosition || riid == IID_IMediaSeeking) { + return GetMediaPositionInterface(riid,ppv); + } else { + return CBaseFilter::NonDelegatingQueryInterface(riid,ppv); + } +} + + +// This is called whenever we change states, we have a manual reset event that +// is signalled whenever we don't won't the source filter thread to wait in us +// (such as in a stopped state) and likewise is not signalled whenever it can +// wait (during paused and running) this function sets or resets the thread +// event. The event is used to stop source filter threads waiting in Receive + +HRESULT CBaseRenderer::SourceThreadCanWait(BOOL bCanWait) +{ + if (bCanWait == TRUE) { + m_ThreadSignal.Reset(); + } else { + m_ThreadSignal.Set(); + } + return NOERROR; +} + + +#ifdef DEBUG +// Dump the current renderer state to the debug terminal. The hardest part of +// the renderer is the window where we unlock everything to wait for a clock +// to signal it is time to draw or for the application to cancel everything +// by stopping the filter. If we get things wrong we can leave the thread in +// WaitForRenderTime with no way for it to ever get out and we will deadlock + +void CBaseRenderer::DisplayRendererState() +{ + DbgLog((LOG_TIMING, 1, TEXT("\nTimed out in WaitForRenderTime"))); + + // No way should this be signalled at this point + + BOOL bSignalled = m_ThreadSignal.Check(); + DbgLog((LOG_TIMING, 1, TEXT("Signal sanity check %d"),bSignalled)); + + // Now output the current renderer state variables + + DbgLog((LOG_TIMING, 1, TEXT("Filter state %d"),m_State)); + + DbgLog((LOG_TIMING, 1, TEXT("Abort flag %d"),m_bAbort)); + + DbgLog((LOG_TIMING, 1, TEXT("Streaming flag %d"),m_bStreaming)); + + DbgLog((LOG_TIMING, 1, TEXT("Clock advise link %d"),m_dwAdvise)); + + DbgLog((LOG_TIMING, 1, TEXT("Current media sample %x"),m_pMediaSample)); + + DbgLog((LOG_TIMING, 1, TEXT("EOS signalled %d"),m_bEOS)); + + DbgLog((LOG_TIMING, 1, TEXT("EOS delivered %d"),m_bEOSDelivered)); + + DbgLog((LOG_TIMING, 1, TEXT("Repaint status %d"),m_bRepaintStatus)); + + + // Output the delayed end of stream timer information + + DbgLog((LOG_TIMING, 1, TEXT("End of stream timer %x"),m_EndOfStreamTimer)); + + DbgLog((LOG_TIMING, 1, TEXT("Deliver time %s"),CDisp((LONGLONG)m_SignalTime))); + + + // Should never timeout during a flushing state + + BOOL bFlushing = m_pInputPin->IsFlushing(); + DbgLog((LOG_TIMING, 1, TEXT("Flushing sanity check %d"),bFlushing)); + + // Display the time we were told to start at + DbgLog((LOG_TIMING, 1, TEXT("Last run time %s"),CDisp((LONGLONG)m_tStart.m_time))); + + // Have we got a reference clock + if (m_pClock == NULL) return; + + // Get the current time from the wall clock + + CRefTime CurrentTime,StartTime,EndTime; + m_pClock->GetTime((REFERENCE_TIME*) &CurrentTime); + CRefTime Offset = CurrentTime - m_tStart; + + // Display the current time from the clock + + DbgLog((LOG_TIMING, 1, TEXT("Clock time %s"),CDisp((LONGLONG)CurrentTime.m_time))); + + DbgLog((LOG_TIMING, 1, TEXT("Time difference %dms"),Offset.Millisecs())); + + + // Do we have a sample ready to render + if (m_pMediaSample == NULL) return; + + m_pMediaSample->GetTime((REFERENCE_TIME*)&StartTime, (REFERENCE_TIME*)&EndTime); + DbgLog((LOG_TIMING, 1, TEXT("Next sample stream times (Start %d End %d ms)"), + StartTime.Millisecs(),EndTime.Millisecs())); + + // Calculate how long it is until it is due for rendering + CRefTime Wait = (m_tStart + StartTime) - CurrentTime; + DbgLog((LOG_TIMING, 1, TEXT("Wait required %d ms"),Wait.Millisecs())); +} +#endif + + +// Wait until the clock sets the timer event or we're otherwise signalled. We +// set an arbitrary timeout for this wait and if it fires then we display the +// current renderer state on the debugger. It will often fire if the filter's +// left paused in an application however it may also fire during stress tests +// if the synchronisation with application seeks and state changes is faulty + +#define RENDER_TIMEOUT 10000 + +HRESULT CBaseRenderer::WaitForRenderTime() +{ + HANDLE WaitObjects[] = { m_ThreadSignal, m_RenderEvent }; + DWORD Result = WAIT_TIMEOUT; + + // Wait for either the time to arrive or for us to be stopped + + OnWaitStart(); + while (Result == WAIT_TIMEOUT) { + Result = WaitForMultipleObjects(2,WaitObjects,FALSE,RENDER_TIMEOUT); + +#ifdef DEBUG + if (Result == WAIT_TIMEOUT) DisplayRendererState(); +#endif + + } + OnWaitEnd(); + + // We may have been awoken without the timer firing + + if (Result == WAIT_OBJECT_0) { + return VFW_E_STATE_CHANGED; + } + + SignalTimerFired(); + return NOERROR; +} + + +// Poll waiting for Receive to complete. This really matters when +// Receive may set the palette and cause window messages +// The problem is that if we don't really wait for a renderer to +// stop processing we can deadlock waiting for a transform which +// is calling the renderer's Receive() method because the transform's +// Stop method doesn't know to process window messages to unblock +// the renderer's Receive processing +void CBaseRenderer::WaitForReceiveToComplete() +{ + for (;;) { + if (!m_bInReceive) { + break; + } + + MSG msg; + // Receive all interthread snedmessages + PeekMessage(&msg, NULL, WM_NULL, WM_NULL, PM_NOREMOVE); + + Sleep(1); + } + + // If the wakebit for QS_POSTMESSAGE is set, the PeekMessage call + // above just cleared the changebit which will cause some messaging + // calls to block (waitMessage, MsgWaitFor...) now. + // Post a dummy message to set the QS_POSTMESSAGE bit again + if (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) & QS_POSTMESSAGE) { + // Send dummy message + PostThreadMessage(GetCurrentThreadId(), WM_NULL, 0, 0); + } +} + +// A filter can have four discrete states, namely Stopped, Running, Paused, +// Intermediate. We are in an intermediate state if we are currently trying +// to pause but haven't yet got the first sample (or if we have been flushed +// in paused state and therefore still have to wait for a sample to arrive) + +// This class contains an event called m_evComplete which is signalled when +// the current state is completed and is not signalled when we are waiting to +// complete the last state transition. As mentioned above the only time we +// use this at the moment is when we wait for a media sample in paused state +// If while we are waiting we receive an end of stream notification from the +// source filter then we know no data is imminent so we can reset the event +// This means that when we transition to paused the source filter must call +// end of stream on us or send us an image otherwise we'll hang indefinately + + +// Simple internal way of getting the real state + +FILTER_STATE CBaseRenderer::GetRealState() { + return m_State; +} + + +// The renderer doesn't complete the full transition to paused states until +// it has got one media sample to render. If you ask it for its state while +// it's waiting it will return the state along with VFW_S_STATE_INTERMEDIATE + +STDMETHODIMP CBaseRenderer::GetState(DWORD dwMSecs,FILTER_STATE *State) +{ + CheckPointer(State,E_POINTER); + + if (WaitDispatchingMessages(m_evComplete, dwMSecs) == WAIT_TIMEOUT) { + *State = m_State; + return VFW_S_STATE_INTERMEDIATE; + } + *State = m_State; + return NOERROR; +} + + +// If we're pausing and we have no samples we don't complete the transition +// to State_Paused and we return S_FALSE. However if the m_bAbort flag has +// been set then all samples are rejected so there is no point waiting for +// one. If we do have a sample then return NOERROR. We will only ever return +// VFW_S_STATE_INTERMEDIATE from GetState after being paused with no sample +// (calling GetState after either being stopped or Run will NOT return this) + +HRESULT CBaseRenderer::CompleteStateChange(FILTER_STATE OldState) +{ + // Allow us to be paused when disconnected + + if (m_pInputPin->IsConnected() == FALSE) { + Ready(); + return S_OK; + } + + // Have we run off the end of stream + + if (IsEndOfStream() == TRUE) { + Ready(); + return S_OK; + } + + // Make sure we get fresh data after being stopped + + if (HaveCurrentSample() == TRUE) { + if (OldState != State_Stopped) { + Ready(); + return S_OK; + } + } + NotReady(); + return S_FALSE; +} + + +// When we stop the filter the things we do are:- + +// Decommit the allocator being used in the connection +// Release the source filter if it's waiting in Receive +// Cancel any advise link we set up with the clock +// Any end of stream signalled is now obsolete so reset +// Allow us to be stopped when we are not connected + +STDMETHODIMP CBaseRenderer::Stop() +{ + CAutoLock cRendererLock(&m_InterfaceLock); + + // Make sure there really is a state change + + if (m_State == State_Stopped) { + return NOERROR; + } + + // Is our input pin connected + + if (m_pInputPin->IsConnected() == FALSE) { + NOTE("Input pin is not connected"); + m_State = State_Stopped; + return NOERROR; + } + + CBaseFilter::Stop(); + + // If we are going into a stopped state then we must decommit whatever + // allocator we are using it so that any source filter waiting in the + // GetBuffer can be released and unlock themselves for a state change + + if (m_pInputPin->Allocator()) { + m_pInputPin->Allocator()->Decommit(); + } + + // Cancel any scheduled rendering + + SetRepaintStatus(TRUE); + StopStreaming(); + SourceThreadCanWait(FALSE); + ResetEndOfStream(); + CancelNotification(); + + // There should be no outstanding clock advise + ASSERT(CancelNotification() == S_FALSE); + ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0)); + ASSERT(m_EndOfStreamTimer == 0); + + Ready(); + WaitForReceiveToComplete(); + m_bAbort = FALSE; + + return NOERROR; +} + + +// When we pause the filter the things we do are:- + +// Commit the allocator being used in the connection +// Allow a source filter thread to wait in Receive +// Cancel any clock advise link (we may be running) +// Possibly complete the state change if we have data +// Allow us to be paused when we are not connected + +STDMETHODIMP CBaseRenderer::Pause() +{ + CAutoLock cRendererLock(&m_InterfaceLock); + FILTER_STATE OldState = m_State; + ASSERT(m_pInputPin->IsFlushing() == FALSE); + + // Make sure there really is a state change + + if (m_State == State_Paused) { + return CompleteStateChange(State_Paused); + } + + // Has our input pin been connected + + if (m_pInputPin->IsConnected() == FALSE) { + NOTE("Input pin is not connected"); + m_State = State_Paused; + return CompleteStateChange(State_Paused); + } + + // Pause the base filter class + + HRESULT hr = CBaseFilter::Pause(); + if (FAILED(hr)) { + NOTE("Pause failed"); + return hr; + } + + // Enable EC_REPAINT events again + + SetRepaintStatus(TRUE); + StopStreaming(); + SourceThreadCanWait(TRUE); + CancelNotification(); + ResetEndOfStreamTimer(); + + // If we are going into a paused state then we must commit whatever + // allocator we are using it so that any source filter can call the + // GetBuffer and expect to get a buffer without returning an error + + if (m_pInputPin->Allocator()) { + m_pInputPin->Allocator()->Commit(); + } + + // There should be no outstanding advise + ASSERT(CancelNotification() == S_FALSE); + ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0)); + ASSERT(m_EndOfStreamTimer == 0); + ASSERT(m_pInputPin->IsFlushing() == FALSE); + + // When we come out of a stopped state we must clear any image we were + // holding onto for frame refreshing. Since renderers see state changes + // first we can reset ourselves ready to accept the source thread data + // Paused or running after being stopped causes the current position to + // be reset so we're not interested in passing end of stream signals + + if (OldState == State_Stopped) { + m_bAbort = FALSE; + ClearPendingSample(); + } + return CompleteStateChange(OldState); +} + + +// When we run the filter the things we do are:- + +// Commit the allocator being used in the connection +// Allow a source filter thread to wait in Receive +// Signal the render event just to get us going +// Start the base class by calling StartStreaming +// Allow us to be run when we are not connected +// Signal EC_COMPLETE if we are not connected + +STDMETHODIMP CBaseRenderer::Run(REFERENCE_TIME StartTime) +{ + CAutoLock cRendererLock(&m_InterfaceLock); + FILTER_STATE OldState = m_State; + + // Make sure there really is a state change + + if (m_State == State_Running) { + return NOERROR; + } + + // Send EC_COMPLETE if we're not connected + + if (m_pInputPin->IsConnected() == FALSE) { + NotifyEvent(EC_COMPLETE,S_OK,(LONG_PTR)(IBaseFilter *)this); + m_State = State_Running; + return NOERROR; + } + + Ready(); + + // Pause the base filter class + + HRESULT hr = CBaseFilter::Run(StartTime); + if (FAILED(hr)) { + NOTE("Run failed"); + return hr; + } + + // Allow the source thread to wait + ASSERT(m_pInputPin->IsFlushing() == FALSE); + SourceThreadCanWait(TRUE); + SetRepaintStatus(FALSE); + + // There should be no outstanding advise + ASSERT(CancelNotification() == S_FALSE); + ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0)); + ASSERT(m_EndOfStreamTimer == 0); + ASSERT(m_pInputPin->IsFlushing() == FALSE); + + // If we are going into a running state then we must commit whatever + // allocator we are using it so that any source filter can call the + // GetBuffer and expect to get a buffer without returning an error + + if (m_pInputPin->Allocator()) { + m_pInputPin->Allocator()->Commit(); + } + + // When we come out of a stopped state we must clear any image we were + // holding onto for frame refreshing. Since renderers see state changes + // first we can reset ourselves ready to accept the source thread data + // Paused or running after being stopped causes the current position to + // be reset so we're not interested in passing end of stream signals + + if (OldState == State_Stopped) { + m_bAbort = FALSE; + ClearPendingSample(); + } + return StartStreaming(); +} + + +// Return the number of input pins we support + +int CBaseRenderer::GetPinCount() +{ + if (m_pInputPin == NULL) { + // Try to create it + (void)GetPin(0); + } + return m_pInputPin != NULL ? 1 : 0; +} + + +// We only support one input pin and it is numbered zero + +CBasePin *CBaseRenderer::GetPin(int n) +{ + CAutoLock cObjectCreationLock(&m_ObjectCreationLock); + + // Should only ever be called with zero + ASSERT(n == 0); + + if (n != 0) { + return NULL; + } + + // Create the input pin if not already done so + + if (m_pInputPin == NULL) { + + // hr must be initialized to NOERROR because + // CRendererInputPin's constructor only changes + // hr's value if an error occurs. + HRESULT hr = NOERROR; + + m_pInputPin = new CRendererInputPin(this,&hr,L"In"); + if (NULL == m_pInputPin) { + return NULL; + } + + if (FAILED(hr)) { + delete m_pInputPin; + m_pInputPin = NULL; + return NULL; + } + } + return m_pInputPin; +} + + +// If "In" then return the IPin for our input pin, otherwise NULL and error + +STDMETHODIMP CBaseRenderer::FindPin(LPCWSTR Id, __deref_out IPin **ppPin) +{ + CheckPointer(ppPin,E_POINTER); + + if (0==lstrcmpW(Id,L"In")) { + *ppPin = GetPin(0); + if (*ppPin) { + (*ppPin)->AddRef(); + } else { + return E_OUTOFMEMORY; + } + } else { + *ppPin = NULL; + return VFW_E_NOT_FOUND; + } + return NOERROR; +} + + +// Called when the input pin receives an EndOfStream notification. If we have +// not got a sample, then notify EC_COMPLETE now. If we have samples, then set +// m_bEOS and check for this on completing samples. If we're waiting to pause +// then complete the transition to paused state by setting the state event + +HRESULT CBaseRenderer::EndOfStream() +{ + // Ignore these calls if we are stopped + + if (m_State == State_Stopped) { + return NOERROR; + } + + // If we have a sample then wait for it to be rendered + + m_bEOS = TRUE; + if (m_pMediaSample) { + return NOERROR; + } + + // If we are waiting for pause then we are now ready since we cannot now + // carry on waiting for a sample to arrive since we are being told there + // won't be any. This sets an event that the GetState function picks up + + Ready(); + + // Only signal completion now if we are running otherwise queue it until + // we do run in StartStreaming. This is used when we seek because a seek + // causes a pause where early notification of completion is misleading + + if (m_bStreaming) { + SendEndOfStream(); + } + return NOERROR; +} + + +// When we are told to flush we should release the source thread + +HRESULT CBaseRenderer::BeginFlush() +{ + // If paused then report state intermediate until we get some data + + if (m_State == State_Paused) { + NotReady(); + } + + SourceThreadCanWait(FALSE); + CancelNotification(); + ClearPendingSample(); + // Wait for Receive to complete + WaitForReceiveToComplete(); + + return NOERROR; +} + + +// After flushing the source thread can wait in Receive again + +HRESULT CBaseRenderer::EndFlush() +{ + // Reset the current sample media time + if (m_pPosition) m_pPosition->ResetMediaTime(); + + // There should be no outstanding advise + + ASSERT(CancelNotification() == S_FALSE); + SourceThreadCanWait(TRUE); + return NOERROR; +} + + +// We can now send EC_REPAINTs if so required + +HRESULT CBaseRenderer::CompleteConnect(IPin *pReceivePin) +{ + // The caller should always hold the interface lock because + // the function uses CBaseFilter::m_State. + ASSERT(CritCheckIn(&m_InterfaceLock)); + + m_bAbort = FALSE; + + if (State_Running == GetRealState()) { + HRESULT hr = StartStreaming(); + if (FAILED(hr)) { + return hr; + } + + SetRepaintStatus(FALSE); + } else { + SetRepaintStatus(TRUE); + } + + return NOERROR; +} + + +// Called when we go paused or running + +HRESULT CBaseRenderer::Active() +{ + return NOERROR; +} + + +// Called when we go into a stopped state + +HRESULT CBaseRenderer::Inactive() +{ + if (m_pPosition) { + m_pPosition->ResetMediaTime(); + } + // People who derive from this may want to override this behaviour + // to keep hold of the sample in some circumstances + ClearPendingSample(); + + return NOERROR; +} + + +// Tell derived classes about the media type agreed + +HRESULT CBaseRenderer::SetMediaType(const CMediaType *pmt) +{ + return NOERROR; +} + + +// When we break the input pin connection we should reset the EOS flags. When +// we are asked for either IMediaPosition or IMediaSeeking we will create a +// CPosPassThru object to handles media time pass through. When we're handed +// samples we store (by calling CPosPassThru::RegisterMediaTime) their media +// times so we can then return a real current position of data being rendered + +HRESULT CBaseRenderer::BreakConnect() +{ + // Do we have a quality management sink + + if (m_pQSink) { + m_pQSink->Release(); + m_pQSink = NULL; + } + + // Check we have a valid connection + + if (m_pInputPin->IsConnected() == FALSE) { + return S_FALSE; + } + + // Check we are stopped before disconnecting + if (m_State != State_Stopped && !m_pInputPin->CanReconnectWhenActive()) { + return VFW_E_NOT_STOPPED; + } + + SetRepaintStatus(FALSE); + ResetEndOfStream(); + ClearPendingSample(); + m_bAbort = FALSE; + + if (State_Running == m_State) { + StopStreaming(); + } + + return NOERROR; +} + + +// Retrieves the sample times for this samples (note the sample times are +// passed in by reference not value). We return S_FALSE to say schedule this +// sample according to the times on the sample. We also return S_OK in +// which case the object should simply render the sample data immediately + +HRESULT CBaseRenderer::GetSampleTimes(IMediaSample *pMediaSample, + __out REFERENCE_TIME *pStartTime, + __out REFERENCE_TIME *pEndTime) +{ + ASSERT(m_dwAdvise == 0); + ASSERT(pMediaSample); + + // If the stop time for this sample is before or the same as start time, + // then just ignore it (release it) and schedule the next one in line + // Source filters should always fill in the start and end times properly! + + if (SUCCEEDED(pMediaSample->GetTime(pStartTime, pEndTime))) { + if (*pEndTime < *pStartTime) { + return VFW_E_START_TIME_AFTER_END; + } + } else { + // no time set in the sample... draw it now? + return S_OK; + } + + // Can't synchronise without a clock so we return S_OK which tells the + // caller that the sample should be rendered immediately without going + // through the overhead of setting a timer advise link with the clock + + if (m_pClock == NULL) { + return S_OK; + } + return ShouldDrawSampleNow(pMediaSample,pStartTime,pEndTime); +} + + +// By default all samples are drawn according to their time stamps so we +// return S_FALSE. Returning S_OK means draw immediately, this is used +// by the derived video renderer class in its quality management. + +HRESULT CBaseRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample, + __out REFERENCE_TIME *ptrStart, + __out REFERENCE_TIME *ptrEnd) +{ + return S_FALSE; +} + + +// We must always reset the current advise time to zero after a timer fires +// because there are several possible ways which lead us not to do any more +// scheduling such as the pending image being cleared after state changes + +void CBaseRenderer::SignalTimerFired() +{ + m_dwAdvise = 0; +} + + +// Cancel any notification currently scheduled. This is called by the owning +// window object when it is told to stop streaming. If there is no timer link +// outstanding then calling this is benign otherwise we go ahead and cancel +// We must always reset the render event as the quality management code can +// signal immediate rendering by setting the event without setting an advise +// link. If we're subsequently stopped and run the first attempt to setup an +// advise link with the reference clock will find the event still signalled + +HRESULT CBaseRenderer::CancelNotification() +{ + ASSERT(m_dwAdvise == 0 || m_pClock); + DWORD_PTR dwAdvise = m_dwAdvise; + + // Have we a live advise link + + if (m_dwAdvise) { + m_pClock->Unadvise(m_dwAdvise); + SignalTimerFired(); + ASSERT(m_dwAdvise == 0); + } + + // Clear the event and return our status + + m_RenderEvent.Reset(); + return (dwAdvise ? S_OK : S_FALSE); +} + + +// Responsible for setting up one shot advise links with the clock +// Return FALSE if the sample is to be dropped (not drawn at all) +// Return TRUE if the sample is to be drawn and in this case also +// arrange for m_RenderEvent to be set at the appropriate time + +BOOL CBaseRenderer::ScheduleSample(IMediaSample *pMediaSample) +{ + REFERENCE_TIME StartSample, EndSample; + + // Is someone pulling our leg + + if (pMediaSample == NULL) { + return FALSE; + } + + // Get the next sample due up for rendering. If there aren't any ready + // then GetNextSampleTimes returns an error. If there is one to be done + // then it succeeds and yields the sample times. If it is due now then + // it returns S_OK other if it's to be done when due it returns S_FALSE + + HRESULT hr = GetSampleTimes(pMediaSample, &StartSample, &EndSample); + if (FAILED(hr)) { + return FALSE; + } + + // If we don't have a reference clock then we cannot set up the advise + // time so we simply set the event indicating an image to render. This + // will cause us to run flat out without any timing or synchronisation + + if (hr == S_OK) { + EXECUTE_ASSERT(SetEvent((HANDLE) m_RenderEvent)); + return TRUE; + } + + ASSERT(m_dwAdvise == 0); + ASSERT(m_pClock); + ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0)); + + // We do have a valid reference clock interface so we can ask it to + // set an event when the image comes due for rendering. We pass in + // the reference time we were told to start at and also the current + // stream time which is the offset from the start reference time + + hr = m_pClock->AdviseTime( + (REFERENCE_TIME) m_tStart, // Start run time + StartSample, // Stream time + (HEVENT)(HANDLE) m_RenderEvent, // Render notification + &m_dwAdvise); // Advise cookie + + if (SUCCEEDED(hr)) { + return TRUE; + } + + // We could not schedule the next sample for rendering despite the fact + // we have a valid sample here. This is a fair indication that either + // the system clock is wrong or the time stamp for the sample is duff + + ASSERT(m_dwAdvise == 0); + return FALSE; +} + + +// This is called when a sample comes due for rendering. We pass the sample +// on to the derived class. After rendering we will initialise the timer for +// the next sample, NOTE signal that the last one fired first, if we don't +// do this it thinks there is still one outstanding that hasn't completed + +HRESULT CBaseRenderer::Render(IMediaSample *pMediaSample) +{ + // If the media sample is NULL then we will have been notified by the + // clock that another sample is ready but in the mean time someone has + // stopped us streaming which causes the next sample to be released + + if (pMediaSample == NULL) { + return S_FALSE; + } + + // If we have stopped streaming then don't render any more samples, the + // thread that got in and locked us and then reset this flag does not + // clear the pending sample as we can use it to refresh any output device + + if (m_bStreaming == FALSE) { + return S_FALSE; + } + + // Time how long the rendering takes + + OnRenderStart(pMediaSample); + DoRenderSample(pMediaSample); + OnRenderEnd(pMediaSample); + + return NOERROR; +} + + +// Checks if there is a sample waiting at the renderer + +BOOL CBaseRenderer::HaveCurrentSample() +{ + CAutoLock cRendererLock(&m_RendererLock); + return (m_pMediaSample == NULL ? FALSE : TRUE); +} + + +// Returns the current sample waiting at the video renderer. We AddRef the +// sample before returning so that should it come due for rendering the +// person who called this method will hold the remaining reference count +// that will stop the sample being added back onto the allocator free list + +IMediaSample *CBaseRenderer::GetCurrentSample() +{ + CAutoLock cRendererLock(&m_RendererLock); + if (m_pMediaSample) { + m_pMediaSample->AddRef(); + } + return m_pMediaSample; +} + + +// Called when the source delivers us a sample. We go through a few checks to +// make sure the sample can be rendered. If we are running (streaming) then we +// have the sample scheduled with the reference clock, if we are not streaming +// then we have received an sample in paused mode so we can complete any state +// transition. On leaving this function everything will be unlocked so an app +// thread may get in and change our state to stopped (for example) in which +// case it will also signal the thread event so that our wait call is stopped + +HRESULT CBaseRenderer::PrepareReceive(IMediaSample *pMediaSample) +{ + CAutoLock cInterfaceLock(&m_InterfaceLock); + m_bInReceive = TRUE; + + // Check our flushing and filter state + + // This function must hold the interface lock because it calls + // CBaseInputPin::Receive() and CBaseInputPin::Receive() uses + // CBasePin::m_bRunTimeError. + HRESULT hr = m_pInputPin->CBaseInputPin::Receive(pMediaSample); + + if (hr != NOERROR) { + m_bInReceive = FALSE; + return E_FAIL; + } + + // Has the type changed on a media sample. We do all rendering + // synchronously on the source thread, which has a side effect + // that only one buffer is ever outstanding. Therefore when we + // have Receive called we can go ahead and change the format + // Since the format change can cause a SendMessage we just don't + // lock + if (m_pInputPin->SampleProps()->pMediaType) { + hr = m_pInputPin->SetMediaType( + (CMediaType *)m_pInputPin->SampleProps()->pMediaType); + if (FAILED(hr)) { + m_bInReceive = FALSE; + return hr; + } + } + + + CAutoLock cSampleLock(&m_RendererLock); + + ASSERT(IsActive() == TRUE); + ASSERT(m_pInputPin->IsFlushing() == FALSE); + ASSERT(m_pInputPin->IsConnected() == TRUE); + ASSERT(m_pMediaSample == NULL); + + // Return an error if we already have a sample waiting for rendering + // source pins must serialise the Receive calls - we also check that + // no data is being sent after the source signalled an end of stream + + if (m_pMediaSample || m_bEOS || m_bAbort) { + Ready(); + m_bInReceive = FALSE; + return E_UNEXPECTED; + } + + // Store the media times from this sample + if (m_pPosition) m_pPosition->RegisterMediaTime(pMediaSample); + + // Schedule the next sample if we are streaming + + if ((m_bStreaming == TRUE) && (ScheduleSample(pMediaSample) == FALSE)) { + ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0)); + ASSERT(CancelNotification() == S_FALSE); + m_bInReceive = FALSE; + return VFW_E_SAMPLE_REJECTED; + } + + // Store the sample end time for EC_COMPLETE handling + m_SignalTime = m_pInputPin->SampleProps()->tStop; + + // BEWARE we sometimes keep the sample even after returning the thread to + // the source filter such as when we go into a stopped state (we keep it + // to refresh the device with) so we must AddRef it to keep it safely. If + // we start flushing the source thread is released and any sample waiting + // will be released otherwise GetBuffer may never return (see BeginFlush) + + m_pMediaSample = pMediaSample; + m_pMediaSample->AddRef(); + + if (m_bStreaming == FALSE) { + SetRepaintStatus(TRUE); + } + return NOERROR; +} + + +// Called by the source filter when we have a sample to render. Under normal +// circumstances we set an advise link with the clock, wait for the time to +// arrive and then render the data using the PURE virtual DoRenderSample that +// the derived class will have overriden. After rendering the sample we may +// also signal EOS if it was the last one sent before EndOfStream was called + +HRESULT CBaseRenderer::Receive(IMediaSample *pSample) +{ + ASSERT(pSample); + + // It may return VFW_E_SAMPLE_REJECTED code to say don't bother + + HRESULT hr = PrepareReceive(pSample); + ASSERT(m_bInReceive == SUCCEEDED(hr)); + if (FAILED(hr)) { + if (hr == VFW_E_SAMPLE_REJECTED) { + return NOERROR; + } + return hr; + } + + // We realize the palette in "PrepareRender()" so we have to give away the + // filter lock here. + if (m_State == State_Paused) { + PrepareRender(); + // no need to use InterlockedExchange + m_bInReceive = FALSE; + { + // We must hold both these locks + CAutoLock cRendererLock(&m_InterfaceLock); + if (m_State == State_Stopped) + return NOERROR; + + m_bInReceive = TRUE; + CAutoLock cSampleLock(&m_RendererLock); + OnReceiveFirstSample(pSample); + } + Ready(); + } + // Having set an advise link with the clock we sit and wait. We may be + // awoken by the clock firing or by a state change. The rendering call + // will lock the critical section and check we can still render the data + + hr = WaitForRenderTime(); + if (FAILED(hr)) { + m_bInReceive = FALSE; + return NOERROR; + } + + PrepareRender(); + + // Set this here and poll it until we work out the locking correctly + // It can't be right that the streaming stuff grabs the interface + // lock - after all we want to be able to wait for this stuff + // to complete + m_bInReceive = FALSE; + + // We must hold both these locks + CAutoLock cRendererLock(&m_InterfaceLock); + + // since we gave away the filter wide lock, the sate of the filter could + // have chnaged to Stopped + if (m_State == State_Stopped) + return NOERROR; + + CAutoLock cSampleLock(&m_RendererLock); + + // Deal with this sample + + Render(m_pMediaSample); + ClearPendingSample(); + SendEndOfStream(); + CancelNotification(); + return NOERROR; +} + + +// This is called when we stop or are inactivated to clear the pending sample +// We release the media sample interface so that they can be allocated to the +// source filter again, unless of course we are changing state to inactive in +// which case GetBuffer will return an error. We must also reset the current +// media sample to NULL so that we know we do not currently have an image + +HRESULT CBaseRenderer::ClearPendingSample() +{ + CAutoLock cRendererLock(&m_RendererLock); + if (m_pMediaSample) { + m_pMediaSample->Release(); + m_pMediaSample = NULL; + } + return NOERROR; +} + + +// Used to signal end of stream according to the sample end time + +void CALLBACK EndOfStreamTimer(UINT uID, // Timer identifier + UINT uMsg, // Not currently used + DWORD_PTR dwUser,// User information + DWORD_PTR dw1, // Windows reserved + DWORD_PTR dw2) // is also reserved +{ + CBaseRenderer *pRenderer = (CBaseRenderer *) dwUser; + NOTE1("EndOfStreamTimer called (%d)",uID); + pRenderer->TimerCallback(); +} + +// Do the timer callback work +void CBaseRenderer::TimerCallback() +{ + // Lock for synchronization (but don't hold this lock when calling + // timeKillEvent) + CAutoLock cRendererLock(&m_RendererLock); + + // See if we should signal end of stream now + + if (m_EndOfStreamTimer) { + m_EndOfStreamTimer = 0; + SendEndOfStream(); + } +} + + +// If we are at the end of the stream signal the filter graph but do not set +// the state flag back to FALSE. Once we drop off the end of the stream we +// leave the flag set (until a subsequent ResetEndOfStream). Each sample we +// get delivered will update m_SignalTime to be the last sample's end time. +// We must wait this long before signalling end of stream to the filtergraph + +#define TIMEOUT_DELIVERYWAIT 50 +#define TIMEOUT_RESOLUTION 10 + +HRESULT CBaseRenderer::SendEndOfStream() +{ + ASSERT(CritCheckIn(&m_RendererLock)); + if (m_bEOS == FALSE || m_bEOSDelivered || m_EndOfStreamTimer) { + return NOERROR; + } + + // If there is no clock then signal immediately + if (m_pClock == NULL) { + return NotifyEndOfStream(); + } + + // How long into the future is the delivery time + + REFERENCE_TIME Signal = m_tStart + m_SignalTime; + REFERENCE_TIME CurrentTime; + m_pClock->GetTime(&CurrentTime); + LONG Delay = LONG((Signal - CurrentTime) / 10000); + + // Dump the timing information to the debugger + + NOTE1("Delay until end of stream delivery %d",Delay); + NOTE1("Current %s",(LPCTSTR)CDisp((LONGLONG)CurrentTime)); + NOTE1("Signal %s",(LPCTSTR)CDisp((LONGLONG)Signal)); + + // Wait for the delivery time to arrive + + if (Delay < TIMEOUT_DELIVERYWAIT) { + return NotifyEndOfStream(); + } + + // Signal a timer callback on another worker thread + + m_EndOfStreamTimer = CompatibleTimeSetEvent((UINT) Delay, // Period of timer + TIMEOUT_RESOLUTION, // Timer resolution + EndOfStreamTimer, // Callback function + DWORD_PTR(this), // Used information + TIME_ONESHOT); // Type of callback + if (m_EndOfStreamTimer == 0) { + return NotifyEndOfStream(); + } + return NOERROR; +} + + +// Signals EC_COMPLETE to the filtergraph manager + +HRESULT CBaseRenderer::NotifyEndOfStream() +{ + CAutoLock cRendererLock(&m_RendererLock); + ASSERT(m_bEOSDelivered == FALSE); + ASSERT(m_EndOfStreamTimer == 0); + + // Has the filter changed state + + if (m_bStreaming == FALSE) { + ASSERT(m_EndOfStreamTimer == 0); + return NOERROR; + } + + // Reset the end of stream timer + m_EndOfStreamTimer = 0; + + // If we've been using the IMediaPosition interface, set it's start + // and end media "times" to the stop position by hand. This ensures + // that we actually get to the end, even if the MPEG guestimate has + // been bad or if the quality management dropped the last few frames + + if (m_pPosition) m_pPosition->EOS(); + m_bEOSDelivered = TRUE; + NOTE("Sending EC_COMPLETE..."); + return NotifyEvent(EC_COMPLETE,S_OK,(LONG_PTR)(IBaseFilter *)this); +} + + +// Reset the end of stream flag, this is typically called when we transfer to +// stopped states since that resets the current position back to the start so +// we will receive more samples or another EndOfStream if there aren't any. We +// keep two separate flags one to say we have run off the end of the stream +// (this is the m_bEOS flag) and another to say we have delivered EC_COMPLETE +// to the filter graph. We need the latter otherwise we can end up sending an +// EC_COMPLETE every time the source changes state and calls our EndOfStream + +HRESULT CBaseRenderer::ResetEndOfStream() +{ + ResetEndOfStreamTimer(); + CAutoLock cRendererLock(&m_RendererLock); + + m_bEOS = FALSE; + m_bEOSDelivered = FALSE; + m_SignalTime = 0; + + return NOERROR; +} + + +// Kills any outstanding end of stream timer + +void CBaseRenderer::ResetEndOfStreamTimer() +{ + ASSERT(CritCheckOut(&m_RendererLock)); + if (m_EndOfStreamTimer) { + timeKillEvent(m_EndOfStreamTimer); + m_EndOfStreamTimer = 0; + } +} + + +// This is called when we start running so that we can schedule any pending +// image we have with the clock and display any timing information. If we +// don't have any sample but we have queued an EOS flag then we send it. If +// we do have a sample then we wait until that has been rendered before we +// signal the filter graph otherwise we may change state before it's done + +HRESULT CBaseRenderer::StartStreaming() +{ + CAutoLock cRendererLock(&m_RendererLock); + if (m_bStreaming == TRUE) { + return NOERROR; + } + + // Reset the streaming times ready for running + + m_bStreaming = TRUE; + + timeBeginPeriod(1); + OnStartStreaming(); + + // There should be no outstanding advise + ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0)); + ASSERT(CancelNotification() == S_FALSE); + + // If we have an EOS and no data then deliver it now + + if (m_pMediaSample == NULL) { + return SendEndOfStream(); + } + + // Have the data rendered + + ASSERT(m_pMediaSample); + if (!ScheduleSample(m_pMediaSample)) + m_RenderEvent.Set(); + + return NOERROR; +} + + +// This is called when we stop streaming so that we can set our internal flag +// indicating we are not now to schedule any more samples arriving. The state +// change methods in the filter implementation take care of cancelling any +// clock advise link we have set up and clearing any pending sample we have + +HRESULT CBaseRenderer::StopStreaming() +{ + CAutoLock cRendererLock(&m_RendererLock); + m_bEOSDelivered = FALSE; + + if (m_bStreaming == TRUE) { + m_bStreaming = FALSE; + OnStopStreaming(); + timeEndPeriod(1); + } + return NOERROR; +} + + +// We have a boolean flag that is reset when we have signalled EC_REPAINT to +// the filter graph. We set this when we receive an image so that should any +// conditions arise again we can send another one. By having a flag we ensure +// we don't flood the filter graph with redundant calls. We do not set the +// event when we receive an EndOfStream call since there is no point in us +// sending further EC_REPAINTs. In particular the AutoShowWindow method and +// the DirectDraw object use this method to control the window repainting + +void CBaseRenderer::SetRepaintStatus(BOOL bRepaint) +{ + CAutoLock cSampleLock(&m_RendererLock); + m_bRepaintStatus = bRepaint; +} + + +// Pass the window handle to the upstream filter + +void CBaseRenderer::SendNotifyWindow(IPin *pPin,HWND hwnd) +{ + IMediaEventSink *pSink; + + // Does the pin support IMediaEventSink + HRESULT hr = pPin->QueryInterface(IID_IMediaEventSink,(void **)&pSink); + if (SUCCEEDED(hr)) { + pSink->Notify(EC_NOTIFY_WINDOW,LONG_PTR(hwnd),0); + pSink->Release(); + } + NotifyEvent(EC_NOTIFY_WINDOW,LONG_PTR(hwnd),0); +} + + +// Signal an EC_REPAINT to the filter graph. This can be used to have data +// sent to us. For example when a video window is first displayed it may +// not have an image to display, at which point it signals EC_REPAINT. The +// filtergraph will either pause the graph if stopped or if already paused +// it will call put_CurrentPosition of the current position. Setting the +// current position to itself has the stream flushed and the image resent + +#define RLOG(_x_) DbgLog((LOG_TRACE,1,TEXT(_x_))); + +void CBaseRenderer::SendRepaint() +{ + CAutoLock cSampleLock(&m_RendererLock); + ASSERT(m_pInputPin); + + // We should not send repaint notifications when... + // - An end of stream has been notified + // - Our input pin is being flushed + // - The input pin is not connected + // - We have aborted a video playback + // - There is a repaint already sent + + if (m_bAbort == FALSE) { + if (m_pInputPin->IsConnected() == TRUE) { + if (m_pInputPin->IsFlushing() == FALSE) { + if (IsEndOfStream() == FALSE) { + if (m_bRepaintStatus == TRUE) { + IPin *pPin = (IPin *) m_pInputPin; + NotifyEvent(EC_REPAINT,(LONG_PTR) pPin,0); + SetRepaintStatus(FALSE); + RLOG("Sending repaint"); + } + } + } + } + } +} + + +// When a video window detects a display change (WM_DISPLAYCHANGE message) it +// can send an EC_DISPLAY_CHANGED event code along with the renderer pin. The +// filtergraph will stop everyone and reconnect our input pin. As we're then +// reconnected we can accept the media type that matches the new display mode +// since we may no longer be able to draw the current image type efficiently + +BOOL CBaseRenderer::OnDisplayChange() +{ + // Ignore if we are not connected yet + + CAutoLock cSampleLock(&m_RendererLock); + if (m_pInputPin->IsConnected() == FALSE) { + return FALSE; + } + + RLOG("Notification of EC_DISPLAY_CHANGE"); + + // Pass our input pin as parameter on the event + + IPin *pPin = (IPin *) m_pInputPin; + m_pInputPin->AddRef(); + NotifyEvent(EC_DISPLAY_CHANGED,(LONG_PTR) pPin,0); + SetAbortSignal(TRUE); + ClearPendingSample(); + m_pInputPin->Release(); + + return TRUE; +} + + +// Called just before we start drawing. +// Store the current time in m_trRenderStart to allow the rendering time to be +// logged. Log the time stamp of the sample and how late it is (neg is early) + +void CBaseRenderer::OnRenderStart(IMediaSample *pMediaSample) +{ +#ifdef PERF + REFERENCE_TIME trStart, trEnd; + pMediaSample->GetTime(&trStart, &trEnd); + + MSR_INTEGER(m_idBaseStamp, (int)trStart); // dump low order 32 bits + + m_pClock->GetTime(&m_trRenderStart); + MSR_INTEGER(0, (int)m_trRenderStart); + REFERENCE_TIME trStream; + trStream = m_trRenderStart-m_tStart; // convert reftime to stream time + MSR_INTEGER(0,(int)trStream); + + const int trLate = (int)(trStream - trStart); + MSR_INTEGER(m_idBaseAccuracy, trLate/10000); // dump in mSec +#endif + +} // OnRenderStart + + +// Called directly after drawing an image. +// calculate the time spent drawing and log it. + +void CBaseRenderer::OnRenderEnd(IMediaSample *pMediaSample) +{ +#ifdef PERF + REFERENCE_TIME trNow; + m_pClock->GetTime(&trNow); + MSR_INTEGER(0,(int)trNow); + int t = (int)((trNow - m_trRenderStart)/10000); // convert UNITS->msec + MSR_INTEGER(m_idBaseRenderTime, t); +#endif +} // OnRenderEnd + + + + +// Constructor must be passed the base renderer object + +CRendererInputPin::CRendererInputPin(__inout CBaseRenderer *pRenderer, + __inout HRESULT *phr, + __in_opt LPCWSTR pPinName) : + CBaseInputPin(NAME("Renderer pin"), + pRenderer, + &pRenderer->m_InterfaceLock, + (HRESULT *) phr, + pPinName) +{ + m_pRenderer = pRenderer; + ASSERT(m_pRenderer); +} + + +// Signals end of data stream on the input pin + +STDMETHODIMP CRendererInputPin::EndOfStream() +{ + CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock); + CAutoLock cSampleLock(&m_pRenderer->m_RendererLock); + + // Make sure we're streaming ok + + HRESULT hr = CheckStreaming(); + if (hr != NOERROR) { + return hr; + } + + // Pass it onto the renderer + + hr = m_pRenderer->EndOfStream(); + if (SUCCEEDED(hr)) { + hr = CBaseInputPin::EndOfStream(); + } + return hr; +} + + +// Signals start of flushing on the input pin - we do the final reset end of +// stream with the renderer lock unlocked but with the interface lock locked +// We must do this because we call timeKillEvent, our timer callback method +// has to take the renderer lock to serialise our state. Therefore holding a +// renderer lock when calling timeKillEvent could cause a deadlock condition + +STDMETHODIMP CRendererInputPin::BeginFlush() +{ + CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock); + { + CAutoLock cSampleLock(&m_pRenderer->m_RendererLock); + CBaseInputPin::BeginFlush(); + m_pRenderer->BeginFlush(); + } + return m_pRenderer->ResetEndOfStream(); +} + + +// Signals end of flushing on the input pin + +STDMETHODIMP CRendererInputPin::EndFlush() +{ + CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock); + CAutoLock cSampleLock(&m_pRenderer->m_RendererLock); + + HRESULT hr = m_pRenderer->EndFlush(); + if (SUCCEEDED(hr)) { + hr = CBaseInputPin::EndFlush(); + } + return hr; +} + + +// Pass the sample straight through to the renderer object + +STDMETHODIMP CRendererInputPin::Receive(IMediaSample *pSample) +{ + HRESULT hr = m_pRenderer->Receive(pSample); + if (FAILED(hr)) { + + // A deadlock could occur if the caller holds the renderer lock and + // attempts to acquire the interface lock. + ASSERT(CritCheckOut(&m_pRenderer->m_RendererLock)); + + { + // The interface lock must be held when the filter is calling + // IsStopped() or IsFlushing(). The interface lock must also + // be held because the function uses m_bRunTimeError. + CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock); + + // We do not report errors which occur while the filter is stopping, + // flushing or if the m_bAbort flag is set . Errors are expected to + // occur during these operations and the streaming thread correctly + // handles the errors. + if (!IsStopped() && !IsFlushing() && !m_pRenderer->m_bAbort && !m_bRunTimeError) { + + // EC_ERRORABORT's first parameter is the error which caused + // the event and its' last parameter is 0. See the Direct + // Show SDK documentation for more information. + m_pRenderer->NotifyEvent(EC_ERRORABORT,hr,0); + + { + CAutoLock alRendererLock(&m_pRenderer->m_RendererLock); + if (m_pRenderer->IsStreaming() && !m_pRenderer->IsEndOfStreamDelivered()) { + m_pRenderer->NotifyEndOfStream(); + } + } + + m_bRunTimeError = TRUE; + } + } + } + + return hr; +} + + +// Called when the input pin is disconnected + +HRESULT CRendererInputPin::BreakConnect() +{ + HRESULT hr = m_pRenderer->BreakConnect(); + if (FAILED(hr)) { + return hr; + } + return CBaseInputPin::BreakConnect(); +} + + +// Called when the input pin is connected + +HRESULT CRendererInputPin::CompleteConnect(IPin *pReceivePin) +{ + HRESULT hr = m_pRenderer->CompleteConnect(pReceivePin); + if (FAILED(hr)) { + return hr; + } + return CBaseInputPin::CompleteConnect(pReceivePin); +} + + +// Give the pin id of our one and only pin + +STDMETHODIMP CRendererInputPin::QueryId(__deref_out LPWSTR *Id) +{ + CheckPointer(Id,E_POINTER); + + const WCHAR szIn[] = L"In"; + + *Id = (LPWSTR)CoTaskMemAlloc(sizeof(szIn)); + if (*Id == NULL) { + return E_OUTOFMEMORY; + } + CopyMemory(*Id, szIn, sizeof(szIn)); + return NOERROR; +} + + +// Will the filter accept this media type + +HRESULT CRendererInputPin::CheckMediaType(const CMediaType *pmt) +{ + return m_pRenderer->CheckMediaType(pmt); +} + + +// Called when we go paused or running + +HRESULT CRendererInputPin::Active() +{ + return m_pRenderer->Active(); +} + + +// Called when we go into a stopped state + +HRESULT CRendererInputPin::Inactive() +{ + // The caller must hold the interface lock because + // this function uses m_bRunTimeError. + ASSERT(CritCheckIn(&m_pRenderer->m_InterfaceLock)); + + m_bRunTimeError = FALSE; + + return m_pRenderer->Inactive(); +} + + +// Tell derived classes about the media type agreed + +HRESULT CRendererInputPin::SetMediaType(const CMediaType *pmt) +{ + HRESULT hr = CBaseInputPin::SetMediaType(pmt); + if (FAILED(hr)) { + return hr; + } + return m_pRenderer->SetMediaType(pmt); +} + + +// We do not keep an event object to use when setting up a timer link with +// the clock but are given a pointer to one by the owning object through the +// SetNotificationObject method - this must be initialised before starting +// We can override the default quality management process to have it always +// draw late frames, this is currently done by having the following registry +// key (actually an INI key) called DrawLateFrames set to 1 (default is 0) + +const TCHAR AMQUALITY[] = TEXT("ActiveMovie"); +const TCHAR DRAWLATEFRAMES[] = TEXT("DrawLateFrames"); + +CBaseVideoRenderer::CBaseVideoRenderer( + REFCLSID RenderClass, // CLSID for this renderer + __in_opt LPCTSTR pName, // Debug ONLY description + __inout_opt LPUNKNOWN pUnk, // Aggregated owner object + __inout HRESULT *phr) : // General OLE return code + + CBaseRenderer(RenderClass,pName,pUnk,phr), + m_cFramesDropped(0), + m_cFramesDrawn(0), + m_bSupplierHandlingQuality(FALSE) +{ + ResetStreamingTimes(); + +#ifdef PERF + m_idTimeStamp = MSR_REGISTER(TEXT("Frame time stamp")); + m_idEarliness = MSR_REGISTER(TEXT("Earliness fudge")); + m_idTarget = MSR_REGISTER(TEXT("Target (mSec)")); + m_idSchLateTime = MSR_REGISTER(TEXT("mSec late when scheduled")); + m_idDecision = MSR_REGISTER(TEXT("Scheduler decision code")); + m_idQualityRate = MSR_REGISTER(TEXT("Quality rate sent")); + m_idQualityTime = MSR_REGISTER(TEXT("Quality time sent")); + m_idWaitReal = MSR_REGISTER(TEXT("Render wait")); + // m_idWait = MSR_REGISTER(TEXT("wait time recorded (msec)")); + m_idFrameAccuracy = MSR_REGISTER(TEXT("Frame accuracy (msecs)")); + m_bDrawLateFrames = GetProfileInt(AMQUALITY, DRAWLATEFRAMES, FALSE); + //m_idSendQuality = MSR_REGISTER(TEXT("Processing Quality message")); + + m_idRenderAvg = MSR_REGISTER(TEXT("Render draw time Avg")); + m_idFrameAvg = MSR_REGISTER(TEXT("FrameAvg")); + m_idWaitAvg = MSR_REGISTER(TEXT("WaitAvg")); + m_idDuration = MSR_REGISTER(TEXT("Duration")); + m_idThrottle = MSR_REGISTER(TEXT("Audio-video throttle wait")); + // m_idDebug = MSR_REGISTER(TEXT("Debug stuff")); +#endif // PERF +} // Constructor + + +// Destructor is just a placeholder + +CBaseVideoRenderer::~CBaseVideoRenderer() +{ + ASSERT(m_dwAdvise == 0); +} + + +// The timing functions in this class are called by the window object and by +// the renderer's allocator. +// The windows object calls timing functions as it receives media sample +// images for drawing using GDI. +// The allocator calls timing functions when it starts passing DCI/DirectDraw +// surfaces which are not rendered in the same way; The decompressor writes +// directly to the surface with no separate rendering, so those code paths +// call direct into us. Since we only ever hand out DCI/DirectDraw surfaces +// when we have allocated one and only one image we know there cannot be any +// conflict between the two. +// +// We use timeGetTime to return the timing counts we use (since it's relative +// performance we are interested in rather than absolute compared to a clock) +// The window object sets the accuracy of the system clock (normally 1ms) by +// calling timeBeginPeriod/timeEndPeriod when it changes streaming states + + +// Reset all times controlling streaming. +// Set them so that +// 1. Frames will not initially be dropped +// 2. The first frame will definitely be drawn (achieved by saying that there +// has not ben a frame drawn for a long time). + +HRESULT CBaseVideoRenderer::ResetStreamingTimes() +{ + m_trLastDraw = -1000; // set up as first frame since ages (1 sec) ago + m_tStreamingStart = timeGetTime(); + m_trRenderAvg = 0; + m_trFrameAvg = -1; // -1000 fps == "unset" + m_trDuration = 0; // 0 - strange value + m_trRenderLast = 0; + m_trWaitAvg = 0; + m_tRenderStart = 0; + m_cFramesDrawn = 0; + m_cFramesDropped = 0; + m_iTotAcc = 0; + m_iSumSqAcc = 0; + m_iSumSqFrameTime = 0; + m_trFrame = 0; // hygeine - not really needed + m_trLate = 0; // hygeine - not really needed + m_iSumFrameTime = 0; + m_nNormal = 0; + m_trEarliness = 0; + m_trTarget = -300000; // 30mSec early + m_trThrottle = 0; + m_trRememberStampForPerf = 0; + +#ifdef PERF + m_trRememberFrameForPerf = 0; +#endif + + return NOERROR; +} // ResetStreamingTimes + + +// Reset all times controlling streaming. Note that we're now streaming. We +// don't need to set the rendering event to have the source filter released +// as it is done during the Run processing. When we are run we immediately +// release the source filter thread and draw any image waiting (that image +// may already have been drawn once as a poster frame while we were paused) + +HRESULT CBaseVideoRenderer::OnStartStreaming() +{ + ResetStreamingTimes(); + return NOERROR; +} // OnStartStreaming + + +// Called at end of streaming. Fixes times for property page report + +HRESULT CBaseVideoRenderer::OnStopStreaming() +{ + m_tStreamingStart = timeGetTime()-m_tStreamingStart; + return NOERROR; +} // OnStopStreaming + + +// Called when we start waiting for a rendering event. +// Used to update times spent waiting and not waiting. + +void CBaseVideoRenderer::OnWaitStart() +{ + MSR_START(m_idWaitReal); +} // OnWaitStart + + +// Called when we are awoken from the wait in the window OR by our allocator +// when it is hanging around until the next sample is due for rendering on a +// DCI/DirectDraw surface. We add the wait time into our rolling average. +// We grab the interface lock so that we're serialised with the application +// thread going through the run code - which in due course ends up calling +// ResetStreaming times - possibly as we run through this section of code + +void CBaseVideoRenderer::OnWaitEnd() +{ +#ifdef PERF + MSR_STOP(m_idWaitReal); + // for a perf build we want to know just exactly how late we REALLY are. + // even if this means that we have to look at the clock again. + + REFERENCE_TIME trRealStream; // the real time now expressed as stream time. +#if 0 + m_pClock->GetTime(&trRealStream); // Calling clock here causes W95 deadlock! +#else + // We will be discarding overflows like mad here! + // This is wrong really because timeGetTime() can wrap but it's + // only for PERF + REFERENCE_TIME tr = timeGetTime()*10000; + trRealStream = tr + m_llTimeOffset; +#endif + trRealStream -= m_tStart; // convert to stream time (this is a reftime) + + if (m_trRememberStampForPerf==0) { + // This is probably the poster frame at the start, and it is not scheduled + // in the usual way at all. Just count it. The rememberstamp gets set + // in ShouldDrawSampleNow, so this does invalid frame recording until we + // actually start playing. + PreparePerformanceData(0, 0); + } else { + int trLate = (int)(trRealStream - m_trRememberStampForPerf); + int trFrame = (int)(tr - m_trRememberFrameForPerf); + PreparePerformanceData(trLate, trFrame); + } + m_trRememberFrameForPerf = tr; +#endif //PERF +} // OnWaitEnd + + +// Put data on one side that describes the lateness of the current frame. +// We don't yet know whether it will actually be drawn. In direct draw mode, +// this decision is up to the filter upstream, and it could change its mind. +// The rules say that if it did draw it must call Receive(). One way or +// another we eventually get into either OnRenderStart or OnDirectRender and +// these both call RecordFrameLateness to update the statistics. + +void CBaseVideoRenderer::PreparePerformanceData(int trLate, int trFrame) +{ + m_trLate = trLate; + m_trFrame = trFrame; +} // PreparePerformanceData + + +// update the statistics: +// m_iTotAcc, m_iSumSqAcc, m_iSumSqFrameTime, m_iSumFrameTime, m_cFramesDrawn +// Note that because the properties page reports using these variables, +// 1. We need to be inside a critical section +// 2. They must all be updated together. Updating the sums here and the count +// elsewhere can result in imaginary jitter (i.e. attempts to find square roots +// of negative numbers) in the property page code. + +void CBaseVideoRenderer::RecordFrameLateness(int trLate, int trFrame) +{ + // Record how timely we are. + int tLate = trLate/10000; + + // Best estimate of moment of appearing on the screen is average of + // start and end draw times. Here we have only the end time. This may + // tend to show us as spuriously late by up to 1/2 frame rate achieved. + // Decoder probably monitors draw time. We don't bother. + MSR_INTEGER( m_idFrameAccuracy, tLate ); + + // This is a kludge - we can get frames that are very late + // especially (at start-up) and they invalidate the statistics. + // So ignore things that are more than 1 sec off. + if (tLate>1000 || tLate<-1000) { + if (m_cFramesDrawn<=1) { + tLate = 0; + } else if (tLate>0) { + tLate = 1000; + } else { + tLate = -1000; + } + } + // The very first frame often has a invalid time, so don't + // count it into the statistics. (???) + if (m_cFramesDrawn>1) { + m_iTotAcc += tLate; + m_iSumSqAcc += (tLate*tLate); + } + + // calculate inter-frame time. Doesn't make sense for first frame + // second frame suffers from invalid first frame stamp. + if (m_cFramesDrawn>2) { + int tFrame = trFrame/10000; // convert to mSec else it overflows + + // This is a kludge. It can overflow anyway (a pause can cause + // a very long inter-frame time) and it overflows at 2**31/10**7 + // or about 215 seconds i.e. 3min 35sec + if (tFrame>1000||tFrame<0) tFrame = 1000; + m_iSumSqFrameTime += tFrame*tFrame; + ASSERT(m_iSumSqFrameTime>=0); + m_iSumFrameTime += tFrame; + } + ++m_cFramesDrawn; + +} // RecordFrameLateness + + +void CBaseVideoRenderer::ThrottleWait() +{ + if (m_trThrottle>0) { + int iThrottle = m_trThrottle/10000; // convert to mSec + MSR_INTEGER( m_idThrottle, iThrottle); + DbgLog((LOG_TRACE, 0, TEXT("Throttle %d ms"), iThrottle)); + Sleep(iThrottle); + } else { + Sleep(0); + } +} // ThrottleWait + + +// Whenever a frame is rendered it goes though either OnRenderStart +// or OnDirectRender. Data that are generated during ShouldDrawSample +// are added to the statistics by calling RecordFrameLateness from both +// these two places. + +// Called in place of OnRenderStart..OnRenderEnd +// When a DirectDraw image is drawn +void CBaseVideoRenderer::OnDirectRender(IMediaSample *pMediaSample) +{ + m_trRenderAvg = 0; + m_trRenderLast = 5000000; // If we mode switch, we do NOT want this + // to inhibit the new average getting going! + // so we set it to half a second + // MSR_INTEGER(m_idRenderAvg, m_trRenderAvg/10000); + RecordFrameLateness(m_trLate, m_trFrame); + ThrottleWait(); +} // OnDirectRender + + +// Called just before we start drawing. All we do is to get the current clock +// time (from the system) and return. We have to store the start render time +// in a member variable because it isn't used until we complete the drawing +// The rest is just performance logging. + +void CBaseVideoRenderer::OnRenderStart(IMediaSample *pMediaSample) +{ + RecordFrameLateness(m_trLate, m_trFrame); + m_tRenderStart = timeGetTime(); +} // OnRenderStart + + +// Called directly after drawing an image. We calculate the time spent in the +// drawing code and if this doesn't appear to have any odd looking spikes in +// it then we add it to the current average draw time. Measurement spikes may +// occur if the drawing thread is interrupted and switched to somewhere else. + +void CBaseVideoRenderer::OnRenderEnd(IMediaSample *pMediaSample) +{ + // The renderer time can vary erratically if we are interrupted so we do + // some smoothing to help get more sensible figures out but even that is + // not enough as figures can go 9,10,9,9,83,9 and we must disregard 83 + + int tr = (timeGetTime() - m_tRenderStart)*10000; // convert mSec->UNITS + if (tr < m_trRenderAvg*2 || tr < 2 * m_trRenderLast) { + // DO_MOVING_AVG(m_trRenderAvg, tr); + m_trRenderAvg = (tr + (AVGPERIOD-1)*m_trRenderAvg)/AVGPERIOD; + } + m_trRenderLast = tr; + ThrottleWait(); +} // OnRenderEnd + + +STDMETHODIMP CBaseVideoRenderer::SetSink( IQualityControl * piqc) +{ + + m_pQSink = piqc; + + return NOERROR; +} // SetSink + + +STDMETHODIMP CBaseVideoRenderer::Notify( IBaseFilter * pSelf, Quality q) +{ + // NOTE: We are NOT getting any locks here. We could be called + // asynchronously and possibly even on a time critical thread of + // someone else's - so we do the minumum. We only set one state + // variable (an integer) and if that happens to be in the middle + // of another thread reading it they will just get either the new + // or the old value. Locking would achieve no more than this. + + // It might be nice to check that we are being called from m_pGraph, but + // it turns out to be a millisecond or so per throw! + + // This is heuristics, these numbers are aimed at being "what works" + // rather than anything based on some theory. + // We use a hyperbola because it's easy to calculate and it includes + // a panic button asymptote (which we push off just to the left) + // The throttling fits the following table (roughly) + // Proportion Throttle (msec) + // >=1000 0 + // 900 3 + // 800 7 + // 700 11 + // 600 17 + // 500 25 + // 400 35 + // 300 50 + // 200 72 + // 125 100 + // 100 112 + // 50 146 + // 0 200 + + // (some evidence that we could go for a sharper kink - e.g. no throttling + // until below the 750 mark - might give fractionally more frames on a + // P60-ish machine). The easy way to get these coefficients is to use + // Renbase.xls follow the instructions therein using excel solver. + + if (q.Proportion>=1000) { m_trThrottle = 0; } + else { + // The DWORD is to make quite sure I get unsigned arithmetic + // as the constant is between 2**31 and 2**32 + m_trThrottle = -330000 + (388880000/(q.Proportion+167)); + } + return NOERROR; +} // Notify + + +// Send a message to indicate what our supplier should do about quality. +// Theory: +// What a supplier wants to know is "is the frame I'm working on NOW +// going to be late?". +// F1 is the frame at the supplier (as above) +// Tf1 is the due time for F1 +// T1 is the time at that point (NOW!) +// Tr1 is the time that f1 WILL actually be rendered +// L1 is the latency of the graph for frame F1 = Tr1-T1 +// D1 (for delay) is how late F1 will be beyond its due time i.e. +// D1 = (Tr1-Tf1) which is what the supplier really wants to know. +// Unfortunately Tr1 is in the future and is unknown, so is L1 +// +// We could estimate L1 by its value for a previous frame, +// L0 = Tr0-T0 and work off +// D1' = ((T1+L0)-Tf1) = (T1 + (Tr0-T0) -Tf1) +// Rearranging terms: +// D1' = (T1-T0) + (Tr0-Tf1) +// adding (Tf0-Tf0) and rearranging again: +// = (T1-T0) + (Tr0-Tf0) + (Tf0-Tf1) +// = (T1-T0) - (Tf1-Tf0) + (Tr0-Tf0) +// But (Tr0-Tf0) is just D0 - how late frame zero was, and this is the +// Late field in the quality message that we send. +// The other two terms just state what correction should be applied before +// using the lateness of F0 to predict the lateness of F1. +// (T1-T0) says how much time has actually passed (we have lost this much) +// (Tf1-Tf0) says how much time should have passed if we were keeping pace +// (we have gained this much). +// +// Suppliers should therefore work off: +// Quality.Late + (T1-T0) - (Tf1-Tf0) +// and see if this is "acceptably late" or even early (i.e. negative). +// They get T1 and T0 by polling the clock, they get Tf1 and Tf0 from +// the time stamps in the frames. They get Quality.Late from us. +// + +HRESULT CBaseVideoRenderer::SendQuality(REFERENCE_TIME trLate, + REFERENCE_TIME trRealStream) +{ + Quality q; + HRESULT hr; + + // If we are the main user of time, then report this as Flood/Dry. + // If our suppliers are, then report it as Famine/Glut. + // + // We need to take action, but avoid hunting. Hunting is caused by + // 1. Taking too much action too soon and overshooting + // 2. Taking too long to react (so averaging can CAUSE hunting). + // + // The reason why we use trLate as well as Wait is to reduce hunting; + // if the wait time is coming down and about to go into the red, we do + // NOT want to rely on some average which is only telling is that it used + // to be OK once. + + q.TimeStamp = (REFERENCE_TIME)trRealStream; + + if (m_trFrameAvg<0) { + q.Type = Famine; // guess + } + // Is the greater part of the time taken bltting or something else + else if (m_trFrameAvg > 2*m_trRenderAvg) { + q.Type = Famine; // mainly other + } else { + q.Type = Flood; // mainly bltting + } + + q.Proportion = 1000; // default + + if (m_trFrameAvg<0) { + // leave it alone - we don't know enough + } + else if ( trLate> 0 ) { + // try to catch up over the next second + // We could be Really, REALLY late, but rendering all the frames + // anyway, just because it's so cheap. + + q.Proportion = 1000 - (int)((trLate)/(UNITS/1000)); + if (q.Proportion<500) { + q.Proportion = 500; // don't go daft. (could've been negative!) + } else { + } + + } else if ( m_trWaitAvg>20000 + && trLate<-20000 + ){ + // Go cautiously faster - aim at 2mSec wait. + if (m_trWaitAvg>=m_trFrameAvg) { + // This can happen because of some fudges. + // The waitAvg is how long we originally planned to wait + // The frameAvg is more honest. + // It means that we are spending a LOT of time waiting + q.Proportion = 2000; // double. + } else { + if (m_trFrameAvg+20000 > m_trWaitAvg) { + q.Proportion + = 1000 * (m_trFrameAvg / (m_trFrameAvg + 20000 - m_trWaitAvg)); + } else { + // We're apparently spending more than the whole frame time waiting. + // Assume that the averages are slightly out of kilter, but that we + // are indeed doing a lot of waiting. (This leg probably never + // happens, but the code avoids any potential divide by zero). + q.Proportion = 2000; + } + } + + if (q.Proportion>2000) { + q.Proportion = 2000; // don't go crazy. + } + } + + // Tell the supplier how late frames are when they get rendered + // That's how late we are now. + // If we are in directdraw mode then the guy upstream can see the drawing + // times and we'll just report on the start time. He can figure out any + // offset to apply. If we are in DIB Section mode then we will apply an + // extra offset which is half of our drawing time. This is usually small + // but can sometimes be the dominant effect. For this we will use the + // average drawing time rather than the last frame. If the last frame took + // a long time to draw and made us late, that's already in the lateness + // figure. We should not add it in again unless we expect the next frame + // to be the same. We don't, we expect the average to be a better shot. + // In direct draw mode the RenderAvg will be zero. + + q.Late = trLate + m_trRenderAvg/2; + + // log what we're doing + MSR_INTEGER(m_idQualityRate, q.Proportion); + MSR_INTEGER( m_idQualityTime, (int)q.Late / 10000 ); + + // A specific sink interface may be set through IPin + + if (m_pQSink==NULL) { + // Get our input pin's peer. We send quality management messages + // to any nominated receiver of these things (set in the IPin + // interface), or else to our source filter. + + IQualityControl *pQC = NULL; + IPin *pOutputPin = m_pInputPin->GetConnected(); + ASSERT(pOutputPin != NULL); + + // And get an AddRef'd quality control interface + + hr = pOutputPin->QueryInterface(IID_IQualityControl,(void**) &pQC); + if (SUCCEEDED(hr)) { + m_pQSink = pQC; + } + } + if (m_pQSink) { + return m_pQSink->Notify(this,q); + } + + return S_FALSE; + +} // SendQuality + + +// We are called with a valid IMediaSample image to decide whether this is to +// be drawn or not. There must be a reference clock in operation. +// Return S_OK if it is to be drawn Now (as soon as possible) +// Return S_FALSE if it is to be drawn when it's due +// Return an error if we want to drop it +// m_nNormal=-1 indicates that we dropped the previous frame and so this +// one should be drawn early. Respect it and update it. +// Use current stream time plus a number of heuristics (detailed below) +// to make the decision + +HRESULT CBaseVideoRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample, + __inout REFERENCE_TIME *ptrStart, + __inout REFERENCE_TIME *ptrEnd) +{ + + // Don't call us unless there's a clock interface to synchronise with + ASSERT(m_pClock); + + MSR_INTEGER(m_idTimeStamp, (int)((*ptrStart)>>32)); // high order 32 bits + MSR_INTEGER(m_idTimeStamp, (int)(*ptrStart)); // low order 32 bits + + // We lose a bit of time depending on the monitor type waiting for the next + // screen refresh. On average this might be about 8mSec - so it will be + // later than we think when the picture appears. To compensate a bit + // we bias the media samples by -8mSec i.e. 80000 UNITs. + // We don't ever make a stream time negative (call it paranoia) + if (*ptrStart>=80000) { + *ptrStart -= 80000; + *ptrEnd -= 80000; // bias stop to to retain valid frame duration + } + + // Cache the time stamp now. We will want to compare what we did with what + // we started with (after making the monitor allowance). + m_trRememberStampForPerf = *ptrStart; + + // Get reference times (current and late) + REFERENCE_TIME trRealStream; // the real time now expressed as stream time. + m_pClock->GetTime(&trRealStream); +#ifdef PERF + // While the reference clock is expensive: + // Remember the offset from timeGetTime and use that. + // This overflows all over the place, but when we subtract to get + // differences the overflows all cancel out. + m_llTimeOffset = trRealStream-timeGetTime()*10000; +#endif + trRealStream -= m_tStart; // convert to stream time (this is a reftime) + + // We have to wory about two versions of "lateness". The truth, which we + // try to work out here and the one measured against m_trTarget which + // includes long term feedback. We report statistics against the truth + // but for operational decisions we work to the target. + // We use TimeDiff to make sure we get an integer because we + // may actually be late (or more likely early if there is a big time + // gap) by a very long time. + const int trTrueLate = TimeDiff(trRealStream - *ptrStart); + const int trLate = trTrueLate; + + MSR_INTEGER(m_idSchLateTime, trTrueLate/10000); + + // Send quality control messages upstream, measured against target + HRESULT hr = SendQuality(trLate, trRealStream); + // Note: the filter upstream is allowed to this FAIL meaning "you do it". + m_bSupplierHandlingQuality = (hr==S_OK); + + // Decision time! Do we drop, draw when ready or draw immediately? + + const int trDuration = (int)(*ptrEnd - *ptrStart); + { + // We need to see if the frame rate of the file has just changed. + // This would make comparing our previous frame rate with the current + // frame rate inefficent. Hang on a moment though. I've seen files + // where the frames vary between 33 and 34 mSec so as to average + // 30fps. A minor variation like that won't hurt us. + int t = m_trDuration/32; + if ( trDuration > m_trDuration+t + || trDuration < m_trDuration-t + ) { + // There's a major variation. Reset the average frame rate to + // exactly the current rate to disable decision 9002 for this frame, + // and remember the new rate. + m_trFrameAvg = trDuration; + m_trDuration = trDuration; + } + } + + MSR_INTEGER(m_idEarliness, m_trEarliness/10000); + MSR_INTEGER(m_idRenderAvg, m_trRenderAvg/10000); + MSR_INTEGER(m_idFrameAvg, m_trFrameAvg/10000); + MSR_INTEGER(m_idWaitAvg, m_trWaitAvg/10000); + MSR_INTEGER(m_idDuration, trDuration/10000); + +#ifdef PERF + if (S_OK==pMediaSample->IsDiscontinuity()) { + MSR_INTEGER(m_idDecision, 9000); + } +#endif + + // Control the graceful slide back from slow to fast machine mode. + // After a frame drop accept an early frame and set the earliness to here + // If this frame is already later than the earliness then slide it to here + // otherwise do the standard slide (reduce by about 12% per frame). + // Note: earliness is normally NEGATIVE + BOOL bJustDroppedFrame + = ( m_bSupplierHandlingQuality + // Can't use the pin sample properties because we might + // not be in Receive when we call this + && (S_OK == pMediaSample->IsDiscontinuity()) // he just dropped one + ) + || (m_nNormal==-1); // we just dropped one + + + // Set m_trEarliness (slide back from slow to fast machine mode) + if (trLate>0) { + m_trEarliness = 0; // we are no longer in fast machine mode at all! + } else if ( (trLate>=m_trEarliness) || bJustDroppedFrame) { + m_trEarliness = trLate; // Things have slipped of their own accord + } else { + m_trEarliness = m_trEarliness - m_trEarliness/8; // graceful slide + } + + // prepare the new wait average - but don't pollute the old one until + // we have finished with it. + int trWaitAvg; + { + // We never mix in a negative wait. This causes us to believe in fast machines + // slightly more. + int trL = trLate<0 ? -trLate : 0; + trWaitAvg = (trL + m_trWaitAvg*(AVGPERIOD-1))/AVGPERIOD; + } + + + int trFrame; + { + REFERENCE_TIME tr = trRealStream - m_trLastDraw; // Cd be large - 4 min pause! + if (tr>10000000) { + tr = 10000000; // 1 second - arbitrarily. + } + trFrame = int(tr); + } + + // We will DRAW this frame IF... + if ( + // ...the time we are spending drawing is a small fraction of the total + // observed inter-frame time so that dropping it won't help much. + (3*m_trRenderAvg <= m_trFrameAvg) + + // ...or our supplier is NOT handling things and the next frame would + // be less timely than this one or our supplier CLAIMS to be handling + // things, and is now less than a full FOUR frames late. + || ( m_bSupplierHandlingQuality + ? (trLate <= trDuration*4) + : (trLate+trLate < trDuration) + ) + + // ...or we are on average waiting for over eight milliseconds then + // this may be just a glitch. Draw it and we'll hope to catch up. + || (m_trWaitAvg > 80000) + + // ...or we haven't drawn an image for over a second. We will update + // the display, which stops the video looking hung. + // Do this regardless of how late this media sample is. + || ((trRealStream - m_trLastDraw) > UNITS) + + ) { + HRESULT Result; + + // We are going to play this frame. We may want to play it early. + // We will play it early if we think we are in slow machine mode. + // If we think we are NOT in slow machine mode, we will still play + // it early by m_trEarliness as this controls the graceful slide back. + // and in addition we aim at being m_trTarget late rather than "on time". + + BOOL bPlayASAP = FALSE; + + // we will play it AT ONCE (slow machine mode) if... + + // ...we are playing catch-up + if ( bJustDroppedFrame) { + bPlayASAP = TRUE; + MSR_INTEGER(m_idDecision, 9001); + } + + // ...or if we are running below the true frame rate + // exact comparisons are glitchy, for these measurements, + // so add an extra 5% or so + else if ( (m_trFrameAvg > trDuration + trDuration/16) + + // It's possible to get into a state where we are losing ground, but + // are a very long way ahead. To avoid this or recover from it + // we refuse to play early by more than 10 frames. + && (trLate > - trDuration*10) + ){ + bPlayASAP = TRUE; + MSR_INTEGER(m_idDecision, 9002); + } +#if 0 + // ...or if we have been late and are less than one frame early + else if ( (trLate + trDuration > 0) + && (m_trWaitAvg<=20000) + ) { + bPlayASAP = TRUE; + MSR_INTEGER(m_idDecision, 9003); + } +#endif + // We will NOT play it at once if we are grossly early. On very slow frame + // rate movies - e.g. clock.avi - it is not a good idea to leap ahead just + // because we got starved (for instance by the net) and dropped one frame + // some time or other. If we are more than 900mSec early, then wait. + if (trLate<-9000000) { + bPlayASAP = FALSE; + } + + if (bPlayASAP) { + + m_nNormal = 0; + MSR_INTEGER(m_idDecision, 0); + // When we are here, we are in slow-machine mode. trLate may well + // oscillate between negative and positive when the supplier is + // dropping frames to keep sync. We should not let that mislead + // us into thinking that we have as much as zero spare time! + // We just update with a zero wait. + m_trWaitAvg = (m_trWaitAvg*(AVGPERIOD-1))/AVGPERIOD; + + // Assume that we draw it immediately. Update inter-frame stats + m_trFrameAvg = (trFrame + m_trFrameAvg*(AVGPERIOD-1))/AVGPERIOD; +#ifndef PERF + // If this is NOT a perf build, then report what we know so far + // without looking at the clock any more. This assumes that we + // actually wait for exactly the time we hope to. It also reports + // how close we get to the manipulated time stamps that we now have + // rather than the ones we originally started with. It will + // therefore be a little optimistic. However it's fast. + PreparePerformanceData(trTrueLate, trFrame); +#endif + m_trLastDraw = trRealStream; + if (m_trEarliness > trLate) { + m_trEarliness = trLate; // if we are actually early, this is neg + } + Result = S_OK; // Draw it now + + } else { + ++m_nNormal; + // Set the average frame rate to EXACTLY the ideal rate. + // If we are exiting slow-machine mode then we will have caught up + // and be running ahead, so as we slide back to exact timing we will + // have a longer than usual gap at this point. If we record this + // real gap then we'll think that we're running slow and go back + // into slow-machine mode and vever get it straight. + m_trFrameAvg = trDuration; + MSR_INTEGER(m_idDecision, 1); + + // Play it early by m_trEarliness and by m_trTarget + + { + int trE = m_trEarliness; + if (trE < -m_trFrameAvg) { + trE = -m_trFrameAvg; + } + *ptrStart += trE; // N.B. earliness is negative + } + + int Delay = -trTrueLate; + Result = Delay<=0 ? S_OK : S_FALSE; // OK = draw now, FALSE = wait + + m_trWaitAvg = trWaitAvg; + + // Predict when it will actually be drawn and update frame stats + + if (Result==S_FALSE) { // We are going to wait + trFrame = TimeDiff(*ptrStart-m_trLastDraw); + m_trLastDraw = *ptrStart; + } else { + // trFrame is already = trRealStream-m_trLastDraw; + m_trLastDraw = trRealStream; + } +#ifndef PERF + int iAccuracy; + if (Delay>0) { + // Report lateness based on when we intend to play it + iAccuracy = TimeDiff(*ptrStart-m_trRememberStampForPerf); + } else { + // Report lateness based on playing it *now*. + iAccuracy = trTrueLate; // trRealStream-RememberStampForPerf; + } + PreparePerformanceData(iAccuracy, trFrame); +#endif + } + return Result; + } + + // We are going to drop this frame! + // Of course in DirectDraw mode the guy upstream may draw it anyway. + + // This will probably give a large negative wack to the wait avg. + m_trWaitAvg = trWaitAvg; + +#ifdef PERF + // Respect registry setting - debug only! + if (m_bDrawLateFrames) { + return S_OK; // draw it when it's ready + } // even though it's late. +#endif + + // We are going to drop this frame so draw the next one early + // n.b. if the supplier is doing direct draw then he may draw it anyway + // but he's doing something funny to arrive here in that case. + + MSR_INTEGER(m_idDecision, 2); + m_nNormal = -1; + return E_FAIL; // drop it + +} // ShouldDrawSampleNow + + +// NOTE we're called by both the window thread and the source filter thread +// so we have to be protected by a critical section (locked before called) +// Also, when the window thread gets signalled to render an image, it always +// does so regardless of how late it is. All the degradation is done when we +// are scheduling the next sample to be drawn. Hence when we start an advise +// link to draw a sample, that sample's time will always become the last one +// drawn - unless of course we stop streaming in which case we cancel links + +BOOL CBaseVideoRenderer::ScheduleSample(IMediaSample *pMediaSample) +{ + // We override ShouldDrawSampleNow to add quality management + + BOOL bDrawImage = CBaseRenderer::ScheduleSample(pMediaSample); + if (bDrawImage == FALSE) { + ++m_cFramesDropped; + return FALSE; + } + + // m_cFramesDrawn must NOT be updated here. It has to be updated + // in RecordFrameLateness at the same time as the other statistics. + return TRUE; +} + + +// Implementation of IQualProp interface needed to support the property page +// This is how the property page gets the data out of the scheduler. We are +// passed into the constructor the owning object in the COM sense, this will +// either be the video renderer or an external IUnknown if we're aggregated. +// We initialise our CUnknown base class with this interface pointer. Then +// all we have to do is to override NonDelegatingQueryInterface to expose +// our IQualProp interface. The AddRef and Release are handled automatically +// by the base class and will be passed on to the appropriate outer object + +STDMETHODIMP CBaseVideoRenderer::get_FramesDroppedInRenderer(__out int *pcFramesDropped) +{ + CheckPointer(pcFramesDropped,E_POINTER); + CAutoLock cVideoLock(&m_InterfaceLock); + *pcFramesDropped = m_cFramesDropped; + return NOERROR; +} // get_FramesDroppedInRenderer + + +// Set *pcFramesDrawn to the number of frames drawn since +// streaming started. + +STDMETHODIMP CBaseVideoRenderer::get_FramesDrawn( int *pcFramesDrawn) +{ + CheckPointer(pcFramesDrawn,E_POINTER); + CAutoLock cVideoLock(&m_InterfaceLock); + *pcFramesDrawn = m_cFramesDrawn; + return NOERROR; +} // get_FramesDrawn + + +// Set iAvgFrameRate to the frames per hundred secs since +// streaming started. 0 otherwise. + +STDMETHODIMP CBaseVideoRenderer::get_AvgFrameRate( int *piAvgFrameRate) +{ + CheckPointer(piAvgFrameRate,E_POINTER); + CAutoLock cVideoLock(&m_InterfaceLock); + + int t; + if (m_bStreaming) { + t = timeGetTime()-m_tStreamingStart; + } else { + t = m_tStreamingStart; + } + + if (t<=0) { + *piAvgFrameRate = 0; + ASSERT(m_cFramesDrawn == 0); + } else { + // i is frames per hundred seconds + *piAvgFrameRate = MulDiv(100000, m_cFramesDrawn, t); + } + return NOERROR; +} // get_AvgFrameRate + + +// Set *piAvg to the average sync offset since streaming started +// in mSec. The sync offset is the time in mSec between when the frame +// should have been drawn and when the frame was actually drawn. + +STDMETHODIMP CBaseVideoRenderer::get_AvgSyncOffset(__out int *piAvg) +{ + CheckPointer(piAvg,E_POINTER); + CAutoLock cVideoLock(&m_InterfaceLock); + + if (NULL==m_pClock) { + *piAvg = 0; + return NOERROR; + } + + // Note that we didn't gather the stats on the first frame + // so we use m_cFramesDrawn-1 here + if (m_cFramesDrawn<=1) { + *piAvg = 0; + } else { + *piAvg = (int)(m_iTotAcc / (m_cFramesDrawn-1)); + } + return NOERROR; +} // get_AvgSyncOffset + + +// To avoid dragging in the maths library - a cheap +// approximate integer square root. +// We do this by getting a starting guess which is between 1 +// and 2 times too large, followed by THREE iterations of +// Newton Raphson. (That will give accuracy to the nearest mSec +// for the range in question - roughly 0..1000) +// +// It would be faster to use a linear interpolation and ONE NR, but +// who cares. If anyone does - the best linear interpolation is +// to approximates sqrt(x) by +// y = x * (sqrt(2)-1) + 1 - 1/sqrt(2) + 1/(8*(sqrt(2)-1)) +// 0r y = x*0.41421 + 0.59467 +// This minimises the maximal error in the range in question. +// (error is about +0.008883 and then one NR will give error .0000something +// (Of course these are integers, so you can't just multiply by 0.41421 +// you'd have to do some sort of MulDiv). +// Anyone wanna check my maths? (This is only for a property display!) + +int isqrt(int x) +{ + int s = 1; + // Make s an initial guess for sqrt(x) + if (x > 0x40000000) { + s = 0x8000; // prevent any conceivable closed loop + } else { + while (s*s<x) { // loop cannot possible go more than 31 times + s = 2*s; // normally it goes about 6 times + } + // Three NR iterations. + if (x==0) { + s= 0; // Wouldn't it be tragic to divide by zero whenever our + // accuracy was perfect! + } else { + s = (s*s+x)/(2*s); + if (s>=0) s = (s*s+x)/(2*s); + if (s>=0) s = (s*s+x)/(2*s); + } + } + return s; +} + +// +// Do estimates for standard deviations for per-frame +// statistics +// +HRESULT CBaseVideoRenderer::GetStdDev( + int nSamples, + __out int *piResult, + LONGLONG llSumSq, + LONGLONG iTot +) +{ + CheckPointer(piResult,E_POINTER); + CAutoLock cVideoLock(&m_InterfaceLock); + + if (NULL==m_pClock) { + *piResult = 0; + return NOERROR; + } + + // If S is the Sum of the Squares of observations and + // T the Total (i.e. sum) of the observations and there were + // N observations, then an estimate of the standard deviation is + // sqrt( (S - T**2/N) / (N-1) ) + + if (nSamples<=1) { + *piResult = 0; + } else { + LONGLONG x; + // First frames have invalid stamps, so we get no stats for them + // So we need 2 frames to get 1 datum, so N is cFramesDrawn-1 + + // so we use m_cFramesDrawn-1 here + x = llSumSq - llMulDiv(iTot, iTot, nSamples, 0); + x = x / (nSamples-1); + ASSERT(x>=0); + *piResult = isqrt((LONG)x); + } + return NOERROR; +} + +// Set *piDev to the standard deviation in mSec of the sync offset +// of each frame since streaming started. + +STDMETHODIMP CBaseVideoRenderer::get_DevSyncOffset(__out int *piDev) +{ + // First frames have invalid stamps, so we get no stats for them + // So we need 2 frames to get 1 datum, so N is cFramesDrawn-1 + return GetStdDev(m_cFramesDrawn - 1, + piDev, + m_iSumSqAcc, + m_iTotAcc); +} // get_DevSyncOffset + + +// Set *piJitter to the standard deviation in mSec of the inter-frame time +// of frames since streaming started. + +STDMETHODIMP CBaseVideoRenderer::get_Jitter(__out int *piJitter) +{ + // First frames have invalid stamps, so we get no stats for them + // So second frame gives invalid inter-frame time + // So we need 3 frames to get 1 datum, so N is cFramesDrawn-2 + return GetStdDev(m_cFramesDrawn - 2, + piJitter, + m_iSumSqFrameTime, + m_iSumFrameTime); +} // get_Jitter + + +// Overidden to return our IQualProp interface + +STDMETHODIMP +CBaseVideoRenderer::NonDelegatingQueryInterface(REFIID riid,__deref_out VOID **ppv) +{ + // We return IQualProp and delegate everything else + + if (riid == IID_IQualProp) { + return GetInterface( (IQualProp *)this, ppv); + } else if (riid == IID_IQualityControl) { + return GetInterface( (IQualityControl *)this, ppv); + } + return CBaseRenderer::NonDelegatingQueryInterface(riid,ppv); +} + + +// Override JoinFilterGraph so that, just before leaving +// the graph we can send an EC_WINDOW_DESTROYED event + +STDMETHODIMP +CBaseVideoRenderer::JoinFilterGraph(__inout_opt IFilterGraph *pGraph, __in_opt LPCWSTR pName) +{ + // Since we send EC_ACTIVATE, we also need to ensure + // we send EC_WINDOW_DESTROYED or the resource manager may be + // holding us as a focus object + if (!pGraph && m_pGraph) { + + // We were in a graph and now we're not + // Do this properly in case we are aggregated + IBaseFilter* pFilter = this; + NotifyEvent(EC_WINDOW_DESTROYED, (LPARAM) pFilter, 0); + } + return CBaseFilter::JoinFilterGraph(pGraph, pName); +} + + +// This removes a large number of level 4 warnings from the +// Microsoft compiler which in this case are not very useful +#pragma warning(disable: 4514) + diff --git a/Src/Plugins/Input/in_dshow/base/renbase.h b/Src/Plugins/Input/in_dshow/base/renbase.h new file mode 100644 index 00000000..8634c6be --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/renbase.h @@ -0,0 +1,478 @@ +//------------------------------------------------------------------------------ +// File: RenBase.h +// +// Desc: DirectShow base classes - defines a generic ActiveX base renderer +// class. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __RENBASE__ +#define __RENBASE__ + +// Forward class declarations + +class CBaseRenderer; +class CBaseVideoRenderer; +class CRendererInputPin; + +// This is our input pin class that channels calls to the renderer + +class CRendererInputPin : public CBaseInputPin +{ +protected: + + CBaseRenderer *m_pRenderer; + +public: + + CRendererInputPin(__inout CBaseRenderer *pRenderer, + __inout HRESULT *phr, + __in_opt LPCWSTR Name); + + // Overriden from the base pin classes + + HRESULT BreakConnect(); + HRESULT CompleteConnect(IPin *pReceivePin); + HRESULT SetMediaType(const CMediaType *pmt); + HRESULT CheckMediaType(const CMediaType *pmt); + HRESULT Active(); + HRESULT Inactive(); + + // Add rendering behaviour to interface functions + + STDMETHODIMP QueryId(__deref_out LPWSTR *Id); + STDMETHODIMP EndOfStream(); + STDMETHODIMP BeginFlush(); + STDMETHODIMP EndFlush(); + STDMETHODIMP Receive(IMediaSample *pMediaSample); + + // Helper + IMemAllocator inline *Allocator() const + { + return m_pAllocator; + } +}; + +// Main renderer class that handles synchronisation and state changes + +class CBaseRenderer : public CBaseFilter +{ +protected: + + friend class CRendererInputPin; + + friend void CALLBACK EndOfStreamTimer(UINT uID, // Timer identifier + UINT uMsg, // Not currently used + DWORD_PTR dwUser, // User information + DWORD_PTR dw1, // Windows reserved + DWORD_PTR dw2); // Is also reserved + + CRendererPosPassThru *m_pPosition; // Media seeking pass by object + CAMEvent m_RenderEvent; // Used to signal timer events + CAMEvent m_ThreadSignal; // Signalled to release worker thread + CAMEvent m_evComplete; // Signalled when state complete + BOOL m_bAbort; // Stop us from rendering more data + BOOL m_bStreaming; // Are we currently streaming + DWORD_PTR m_dwAdvise; // Timer advise cookie + IMediaSample *m_pMediaSample; // Current image media sample + BOOL m_bEOS; // Any more samples in the stream + BOOL m_bEOSDelivered; // Have we delivered an EC_COMPLETE + CRendererInputPin *m_pInputPin; // Our renderer input pin object + CCritSec m_InterfaceLock; // Critical section for interfaces + CCritSec m_RendererLock; // Controls access to internals + IQualityControl * m_pQSink; // QualityControl sink + BOOL m_bRepaintStatus; // Can we signal an EC_REPAINT + // Avoid some deadlocks by tracking filter during stop + volatile BOOL m_bInReceive; // Inside Receive between PrepareReceive + // And actually processing the sample + REFERENCE_TIME m_SignalTime; // Time when we signal EC_COMPLETE + UINT m_EndOfStreamTimer; // Used to signal end of stream + CCritSec m_ObjectCreationLock; // This lock protects the creation and + // of m_pPosition and m_pInputPin. It + // ensures that two threads cannot create + // either object simultaneously. + +public: + + CBaseRenderer(REFCLSID RenderClass, // CLSID for this renderer + __in_opt LPCTSTR pName, // Debug ONLY description + __inout_opt LPUNKNOWN pUnk, // Aggregated owner object + __inout HRESULT *phr); // General OLE return code + + ~CBaseRenderer(); + + // Overriden to say what interfaces we support and where + + virtual HRESULT GetMediaPositionInterface(REFIID riid, __deref_out void **ppv); + STDMETHODIMP NonDelegatingQueryInterface(REFIID, __deref_out void **); + + virtual HRESULT SourceThreadCanWait(BOOL bCanWait); + +#ifdef DEBUG + // Debug only dump of the renderer state + void DisplayRendererState(); +#endif + virtual HRESULT WaitForRenderTime(); + virtual HRESULT CompleteStateChange(FILTER_STATE OldState); + + // Return internal information about this filter + + BOOL IsEndOfStream() { return m_bEOS; }; + BOOL IsEndOfStreamDelivered() { return m_bEOSDelivered; }; + BOOL IsStreaming() { return m_bStreaming; }; + void SetAbortSignal(BOOL bAbort) { m_bAbort = bAbort; }; + virtual void OnReceiveFirstSample(IMediaSample *pMediaSample) { }; + CAMEvent *GetRenderEvent() { return &m_RenderEvent; }; + + // Permit access to the transition state + + void Ready() { m_evComplete.Set(); }; + void NotReady() { m_evComplete.Reset(); }; + BOOL CheckReady() { return m_evComplete.Check(); }; + + virtual int GetPinCount(); + virtual CBasePin *GetPin(int n); + FILTER_STATE GetRealState(); + void SendRepaint(); + void SendNotifyWindow(IPin *pPin,HWND hwnd); + BOOL OnDisplayChange(); + void SetRepaintStatus(BOOL bRepaint); + + // Override the filter and pin interface functions + + STDMETHODIMP Stop(); + STDMETHODIMP Pause(); + STDMETHODIMP Run(REFERENCE_TIME StartTime); + STDMETHODIMP GetState(DWORD dwMSecs, __out FILTER_STATE *State); + STDMETHODIMP FindPin(LPCWSTR Id, __deref_out IPin **ppPin); + + // These are available for a quality management implementation + + virtual void OnRenderStart(IMediaSample *pMediaSample); + virtual void OnRenderEnd(IMediaSample *pMediaSample); + virtual HRESULT OnStartStreaming() { return NOERROR; }; + virtual HRESULT OnStopStreaming() { return NOERROR; }; + virtual void OnWaitStart() { }; + virtual void OnWaitEnd() { }; + virtual void PrepareRender() { }; + +#ifdef PERF + REFERENCE_TIME m_trRenderStart; // Just before we started drawing + // Set in OnRenderStart, Used in OnRenderEnd + int m_idBaseStamp; // MSR_id for frame time stamp + int m_idBaseRenderTime; // MSR_id for true wait time + int m_idBaseAccuracy; // MSR_id for time frame is late (int) +#endif + + // Quality management implementation for scheduling rendering + + virtual BOOL ScheduleSample(IMediaSample *pMediaSample); + virtual HRESULT GetSampleTimes(IMediaSample *pMediaSample, + __out REFERENCE_TIME *pStartTime, + __out REFERENCE_TIME *pEndTime); + + virtual HRESULT ShouldDrawSampleNow(IMediaSample *pMediaSample, + __out REFERENCE_TIME *ptrStart, + __out REFERENCE_TIME *ptrEnd); + + // Lots of end of stream complexities + + void TimerCallback(); + void ResetEndOfStreamTimer(); + HRESULT NotifyEndOfStream(); + virtual HRESULT SendEndOfStream(); + virtual HRESULT ResetEndOfStream(); + virtual HRESULT EndOfStream(); + + // Rendering is based around the clock + + void SignalTimerFired(); + virtual HRESULT CancelNotification(); + virtual HRESULT ClearPendingSample(); + + // Called when the filter changes state + + virtual HRESULT Active(); + virtual HRESULT Inactive(); + virtual HRESULT StartStreaming(); + virtual HRESULT StopStreaming(); + virtual HRESULT BeginFlush(); + virtual HRESULT EndFlush(); + + // Deal with connections and type changes + + virtual HRESULT BreakConnect(); + virtual HRESULT SetMediaType(const CMediaType *pmt); + virtual HRESULT CompleteConnect(IPin *pReceivePin); + + // These look after the handling of data samples + + virtual HRESULT PrepareReceive(IMediaSample *pMediaSample); + virtual HRESULT Receive(IMediaSample *pMediaSample); + virtual BOOL HaveCurrentSample(); + virtual IMediaSample *GetCurrentSample(); + virtual HRESULT Render(IMediaSample *pMediaSample); + + // Derived classes MUST override these + virtual HRESULT DoRenderSample(IMediaSample *pMediaSample) PURE; + virtual HRESULT CheckMediaType(const CMediaType *) PURE; + + // Helper + void WaitForReceiveToComplete(); +}; + + +// CBaseVideoRenderer is a renderer class (see its ancestor class) and +// it handles scheduling of media samples so that they are drawn at the +// correct time by the reference clock. It implements a degradation +// strategy. Possible degradation modes are: +// Drop frames here (only useful if the drawing takes significant time) +// Signal supplier (upstream) to drop some frame(s) - i.e. one-off skip. +// Signal supplier to change the frame rate - i.e. ongoing skipping. +// Or any combination of the above. +// In order to determine what's useful to try we need to know what's going +// on. This is done by timing various operations (including the supplier). +// This timing is done by using timeGetTime as it is accurate enough and +// usually cheaper than calling the reference clock. It also tells the +// truth if there is an audio break and the reference clock stops. +// We provide a number of public entry points (named OnXxxStart, OnXxxEnd) +// which the rest of the renderer calls at significant moments. These do +// the timing. + +// the number of frames that the sliding averages are averaged over. +// the rule is (1024*NewObservation + (AVGPERIOD-1) * PreviousAverage)/AVGPERIOD +#define AVGPERIOD 4 +#define DO_MOVING_AVG(avg,obs) (avg = (1024*obs + (AVGPERIOD-1)*avg)/AVGPERIOD) +// Spot the bug in this macro - I can't. but it doesn't work! + +class CBaseVideoRenderer : public CBaseRenderer, // Base renderer class + public IQualProp, // Property page guff + public IQualityControl // Allow throttling +{ +protected: + + // Hungarian: + // tFoo is the time Foo in mSec (beware m_tStart from filter.h) + // trBar is the time Bar by the reference clock + + //****************************************************************** + // State variables to control synchronisation + //****************************************************************** + + // Control of sending Quality messages. We need to know whether + // we are in trouble (e.g. frames being dropped) and where the time + // is being spent. + + // When we drop a frame we play the next one early. + // The frame after that is likely to wait before drawing and counting this + // wait as spare time is unfair, so we count it as a zero wait. + // We therefore need to know whether we are playing frames early or not. + + int m_nNormal; // The number of consecutive frames + // drawn at their normal time (not early) + // -1 means we just dropped a frame. + +#ifdef PERF + BOOL m_bDrawLateFrames; // Don't drop any frames (debug and I'm + // not keen on people using it!) +#endif + + BOOL m_bSupplierHandlingQuality;// The response to Quality messages says + // our supplier is handling things. + // We will allow things to go extra late + // before dropping frames. We will play + // very early after he has dropped one. + + // Control of scheduling, frame dropping etc. + // We need to know where the time is being spent so as to tell whether + // we should be taking action here, signalling supplier or what. + // The variables are initialised to a mode of NOT dropping frames. + // They will tell the truth after a few frames. + // We typically record a start time for an event, later we get the time + // again and subtract to get the elapsed time, and we average this over + // a few frames. The average is used to tell what mode we are in. + + // Although these are reference times (64 bit) they are all DIFFERENCES + // between times which are small. An int will go up to 214 secs before + // overflow. Avoiding 64 bit multiplications and divisions seems + // worth while. + + + + // Audio-video throttling. If the user has turned up audio quality + // very high (in principle it could be any other stream, not just audio) + // then we can receive cries for help via the graph manager. In this case + // we put in a wait for some time after rendering each frame. + int m_trThrottle; + + // The time taken to render (i.e. BitBlt) frames controls which component + // needs to degrade. If the blt is expensive, the renderer degrades. + // If the blt is cheap it's done anyway and the supplier degrades. + int m_trRenderAvg; // Time frames are taking to blt + int m_trRenderLast; // Time for last frame blt + int m_tRenderStart; // Just before we started drawing (mSec) + // derived from timeGetTime. + + // When frames are dropped we will play the next frame as early as we can. + // If it was a false alarm and the machine is fast we slide gently back to + // normal timing. To do this, we record the offset showing just how early + // we really are. This will normally be negative meaning early or zero. + int m_trEarliness; + + // Target provides slow long-term feedback to try to reduce the + // average sync offset to zero. Whenever a frame is actually rendered + // early we add a msec or two, whenever late we take off a few. + // We add or take off 1/32 of the error time. + // Eventually we should be hovering around zero. For a really bad case + // where we were (say) 300mSec off, it might take 100 odd frames to + // settle down. The rate of change of this is intended to be slower + // than any other mechanism in Quartz, thereby avoiding hunting. + int m_trTarget; + + // The proportion of time spent waiting for the right moment to blt + // controls whether we bother to drop a frame or whether we reckon that + // we're doing well enough that we can stand a one-frame glitch. + int m_trWaitAvg; // Average of last few wait times + // (actually we just average how early + // we were). Negative here means LATE. + + // The average inter-frame time. + // This is used to calculate the proportion of the time used by the + // three operations (supplying us, waiting, rendering) + int m_trFrameAvg; // Average inter-frame time + int m_trDuration; // duration of last frame. + +#ifdef PERF + // Performance logging identifiers + int m_idTimeStamp; // MSR_id for frame time stamp + int m_idEarliness; // MSR_id for earliness fudge + int m_idTarget; // MSR_id for Target fudge + int m_idWaitReal; // MSR_id for true wait time + int m_idWait; // MSR_id for wait time recorded + int m_idFrameAccuracy; // MSR_id for time frame is late (int) + int m_idRenderAvg; // MSR_id for Render time recorded (int) + int m_idSchLateTime; // MSR_id for lateness at scheduler + int m_idQualityRate; // MSR_id for Quality rate requested + int m_idQualityTime; // MSR_id for Quality time requested + int m_idDecision; // MSR_id for decision code + int m_idDuration; // MSR_id for duration of a frame + int m_idThrottle; // MSR_id for audio-video throttling + //int m_idDebug; // MSR_id for trace style debugging + //int m_idSendQuality; // MSR_id for timing the notifications per se +#endif // PERF + REFERENCE_TIME m_trRememberStampForPerf; // original time stamp of frame + // with no earliness fudges etc. +#ifdef PERF + REFERENCE_TIME m_trRememberFrameForPerf; // time when previous frame rendered + + // debug... + int m_idFrameAvg; + int m_idWaitAvg; +#endif + + // PROPERTY PAGE + // This has edit fields that show the user what's happening + // These member variables hold these counts. + + int m_cFramesDropped; // cumulative frames dropped IN THE RENDERER + int m_cFramesDrawn; // Frames since streaming started seen BY THE + // RENDERER (some may be dropped upstream) + + // Next two support average sync offset and standard deviation of sync offset. + LONGLONG m_iTotAcc; // Sum of accuracies in mSec + LONGLONG m_iSumSqAcc; // Sum of squares of (accuracies in mSec) + + // Next two allow jitter calculation. Jitter is std deviation of frame time. + REFERENCE_TIME m_trLastDraw; // Time of prev frame (for inter-frame times) + LONGLONG m_iSumSqFrameTime; // Sum of squares of (inter-frame time in mSec) + LONGLONG m_iSumFrameTime; // Sum of inter-frame times in mSec + + // To get performance statistics on frame rate, jitter etc, we need + // to record the lateness and inter-frame time. What we actually need are the + // data above (sum, sum of squares and number of entries for each) but the data + // is generated just ahead of time and only later do we discover whether the + // frame was actually drawn or not. So we have to hang on to the data + int m_trLate; // hold onto frame lateness + int m_trFrame; // hold onto inter-frame time + + int m_tStreamingStart; // if streaming then time streaming started + // else time of last streaming session + // used for property page statistics +#ifdef PERF + LONGLONG m_llTimeOffset; // timeGetTime()*10000+m_llTimeOffset==ref time +#endif + +public: + + + CBaseVideoRenderer(REFCLSID RenderClass, // CLSID for this renderer + __in_opt LPCTSTR pName, // Debug ONLY description + __inout_opt LPUNKNOWN pUnk, // Aggregated owner object + __inout HRESULT *phr); // General OLE return code + + ~CBaseVideoRenderer(); + + // IQualityControl methods - Notify allows audio-video throttling + + STDMETHODIMP SetSink( IQualityControl * piqc); + STDMETHODIMP Notify( IBaseFilter * pSelf, Quality q); + + // These provide a full video quality management implementation + + void OnRenderStart(IMediaSample *pMediaSample); + void OnRenderEnd(IMediaSample *pMediaSample); + void OnWaitStart(); + void OnWaitEnd(); + HRESULT OnStartStreaming(); + HRESULT OnStopStreaming(); + void ThrottleWait(); + + // Handle the statistics gathering for our quality management + + void PreparePerformanceData(int trLate, int trFrame); + virtual void RecordFrameLateness(int trLate, int trFrame); + virtual void OnDirectRender(IMediaSample *pMediaSample); + virtual HRESULT ResetStreamingTimes(); + BOOL ScheduleSample(IMediaSample *pMediaSample); + HRESULT ShouldDrawSampleNow(IMediaSample *pMediaSample, + __inout REFERENCE_TIME *ptrStart, + __inout REFERENCE_TIME *ptrEnd); + + virtual HRESULT SendQuality(REFERENCE_TIME trLate, REFERENCE_TIME trRealStream); + STDMETHODIMP JoinFilterGraph(__inout_opt IFilterGraph * pGraph, __in_opt LPCWSTR pName); + + // + // Do estimates for standard deviations for per-frame + // statistics + // + // *piResult = (llSumSq - iTot * iTot / m_cFramesDrawn - 1) / + // (m_cFramesDrawn - 2) + // or 0 if m_cFramesDrawn <= 3 + // + HRESULT GetStdDev( + int nSamples, + __out int *piResult, + LONGLONG llSumSq, + LONGLONG iTot + ); +public: + + // IQualProp property page support + + STDMETHODIMP get_FramesDroppedInRenderer(__out int *cFramesDropped); + STDMETHODIMP get_FramesDrawn(__out int *pcFramesDrawn); + STDMETHODIMP get_AvgFrameRate(__out int *piAvgFrameRate); + STDMETHODIMP get_Jitter(__out int *piJitter); + STDMETHODIMP get_AvgSyncOffset(__out int *piAvg); + STDMETHODIMP get_DevSyncOffset(__out int *piDev); + + // Implement an IUnknown interface and expose IQualProp + + DECLARE_IUNKNOWN + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid,__deref_out VOID **ppv); +}; + +#endif // __RENBASE__ + diff --git a/Src/Plugins/Input/in_dshow/base/schedule.cpp b/Src/Plugins/Input/in_dshow/base/schedule.cpp new file mode 100644 index 00000000..330a397b --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/schedule.cpp @@ -0,0 +1,284 @@ +//------------------------------------------------------------------------------ +// File: Schedule.cpp +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1996-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> + +// DbgLog values (all on LOG_TIMING): +// +// 2 for schedulting, firing and shunting of events +// 3 for wait delays and wake-up times of event thread +// 4 for details of whats on the list when the thread awakes + +/* Construct & destructors */ + +CAMSchedule::CAMSchedule( HANDLE ev ) +: CBaseObject(TEXT("CAMSchedule")) +, head(&z, 0), z(0, MAX_TIME) +, m_dwNextCookie(0), m_dwAdviseCount(0) +, m_pAdviseCache(0), m_dwCacheCount(0) +, m_ev( ev ) +{ + head.m_dwAdviseCookie = z.m_dwAdviseCookie = 0; +} + +CAMSchedule::~CAMSchedule() +{ + m_Serialize.Lock(); + + // Delete cache + CAdvisePacket * p = m_pAdviseCache; + while (p) + { + CAdvisePacket *const p_next = p->m_next; + delete p; + p = p_next; + } + + ASSERT( m_dwAdviseCount == 0 ); + // Better to be safe than sorry + if ( m_dwAdviseCount > 0 ) + { + DumpLinkedList(); + while ( !head.m_next->IsZ() ) + { + head.DeleteNext(); + --m_dwAdviseCount; + } + } + + // If, in the debug version, we assert twice, it means, not only + // did we have left over advises, but we have also let m_dwAdviseCount + // get out of sync. with the number of advises actually on the list. + ASSERT( m_dwAdviseCount == 0 ); + + m_Serialize.Unlock(); +} + +/* Public methods */ + +DWORD CAMSchedule::GetAdviseCount() +{ + // No need to lock, m_dwAdviseCount is 32bits & declared volatile + return m_dwAdviseCount; +} + +REFERENCE_TIME CAMSchedule::GetNextAdviseTime() +{ + CAutoLock lck(&m_Serialize); // Need to stop the linked list from changing + return head.m_next->m_rtEventTime; +} + +DWORD_PTR CAMSchedule::AddAdvisePacket +( const REFERENCE_TIME & time1 +, const REFERENCE_TIME & time2 +, HANDLE h, BOOL periodic +) +{ + // Since we use MAX_TIME as a sentry, we can't afford to + // schedule a notification at MAX_TIME + ASSERT( time1 < MAX_TIME ); + DWORD_PTR Result; + CAdvisePacket * p; + + m_Serialize.Lock(); + + if (m_pAdviseCache) + { + p = m_pAdviseCache; + m_pAdviseCache = p->m_next; + --m_dwCacheCount; + } + else + { + p = new CAdvisePacket(); + } + if (p) + { + p->m_rtEventTime = time1; p->m_rtPeriod = time2; + p->m_hNotify = h; p->m_bPeriodic = periodic; + Result = AddAdvisePacket( p ); + } + else Result = 0; + + m_Serialize.Unlock(); + + return Result; +} + +HRESULT CAMSchedule::Unadvise(DWORD_PTR dwAdviseCookie) +{ + HRESULT hr = S_FALSE; + CAdvisePacket * p_prev = &head; + CAdvisePacket * p_n; + m_Serialize.Lock(); + while ( p_n = p_prev->Next() ) // The Next() method returns NULL when it hits z + { + if ( p_n->m_dwAdviseCookie == dwAdviseCookie ) + { + Delete( p_prev->RemoveNext() ); + --m_dwAdviseCount; + hr = S_OK; + // Having found one cookie that matches, there should be no more + #ifdef DEBUG + while (p_n = p_prev->Next()) + { + ASSERT(p_n->m_dwAdviseCookie != dwAdviseCookie); + p_prev = p_n; + } + #endif + break; + } + p_prev = p_n; + }; + m_Serialize.Unlock(); + return hr; +} + +REFERENCE_TIME CAMSchedule::Advise( const REFERENCE_TIME & rtTime ) +{ + REFERENCE_TIME rtNextTime; + CAdvisePacket * pAdvise; + + DbgLog((LOG_TIMING, 2, + TEXT("CAMSchedule::Advise( %lu ms )"), ULONG(rtTime / (UNITS / MILLISECONDS)))); + + CAutoLock lck(&m_Serialize); + + #ifdef DEBUG + if (DbgCheckModuleLevel(LOG_TIMING, 4)) DumpLinkedList(); + #endif + + // Note - DON'T cache the difference, it might overflow + while ( rtTime >= (rtNextTime = (pAdvise=head.m_next)->m_rtEventTime) && + !pAdvise->IsZ() ) + { + ASSERT(pAdvise->m_dwAdviseCookie); // If this is zero, its the _head or the _tail!! + + ASSERT(pAdvise->m_hNotify != INVALID_HANDLE_VALUE); + + if (pAdvise->m_bPeriodic == TRUE) + { + ReleaseSemaphore(pAdvise->m_hNotify,1,NULL); + pAdvise->m_rtEventTime += pAdvise->m_rtPeriod; + ShuntHead(); + } + else + { + ASSERT( pAdvise->m_bPeriodic == FALSE ); + EXECUTE_ASSERT(SetEvent(pAdvise->m_hNotify)); + --m_dwAdviseCount; + Delete( head.RemoveNext() ); + } + + } + + DbgLog((LOG_TIMING, 3, + TEXT("CAMSchedule::Advise() Next time stamp: %lu ms, for advise %lu."), + DWORD(rtNextTime / (UNITS / MILLISECONDS)), pAdvise->m_dwAdviseCookie )); + + return rtNextTime; +} + +/* Private methods */ + +DWORD_PTR CAMSchedule::AddAdvisePacket( __inout CAdvisePacket * pPacket ) +{ + ASSERT(pPacket->m_rtEventTime >= 0 && pPacket->m_rtEventTime < MAX_TIME); + ASSERT(CritCheckIn(&m_Serialize)); + + CAdvisePacket * p_prev = &head; + CAdvisePacket * p_n; + + const DWORD_PTR Result = pPacket->m_dwAdviseCookie = ++m_dwNextCookie; + // This relies on the fact that z is a sentry with a maximal m_rtEventTime + for(;;p_prev = p_n) + { + p_n = p_prev->m_next; + if ( p_n->m_rtEventTime >= pPacket->m_rtEventTime ) break; + } + p_prev->InsertAfter( pPacket ); + ++m_dwAdviseCount; + + DbgLog((LOG_TIMING, 2, TEXT("Added advise %lu, for thread 0x%02X, scheduled at %lu"), + pPacket->m_dwAdviseCookie, GetCurrentThreadId(), (pPacket->m_rtEventTime / (UNITS / MILLISECONDS)) )); + + // If packet added at the _head, then clock needs to re-evaluate wait time. + if ( p_prev == &head ) SetEvent( m_ev ); + + return Result; +} + +void CAMSchedule::Delete( __inout CAdvisePacket * pPacket ) +{ + if ( m_dwCacheCount >= dwCacheMax ) delete pPacket; + else + { + m_Serialize.Lock(); + pPacket->m_next = m_pAdviseCache; + m_pAdviseCache = pPacket; + ++m_dwCacheCount; + m_Serialize.Unlock(); + } +} + + +// Takes the _head of the list & repositions it +void CAMSchedule::ShuntHead() +{ + CAdvisePacket * p_prev = &head; + CAdvisePacket * p_n; + + m_Serialize.Lock(); + CAdvisePacket *const pPacket = head.m_next; + + // This will catch both an empty list, + // and if somehow a MAX_TIME time gets into the list + // (which would also break this method). + ASSERT( pPacket->m_rtEventTime < MAX_TIME ); + + // This relies on the fact that z is a sentry with a maximal m_rtEventTime + for(;;p_prev = p_n) + { + p_n = p_prev->m_next; + if ( p_n->m_rtEventTime > pPacket->m_rtEventTime ) break; + } + // If p_prev == pPacket then we're already in the right place + if (p_prev != pPacket) + { + head.m_next = pPacket->m_next; + (p_prev->m_next = pPacket)->m_next = p_n; + } + #ifdef DEBUG + DbgLog((LOG_TIMING, 2, TEXT("Periodic advise %lu, shunted to %lu"), + pPacket->m_dwAdviseCookie, (pPacket->m_rtEventTime / (UNITS / MILLISECONDS)) )); + #endif + m_Serialize.Unlock(); +} + + +#ifdef DEBUG +void CAMSchedule::DumpLinkedList() +{ + m_Serialize.Lock(); + int i=0; + DbgLog((LOG_TIMING, 1, TEXT("CAMSchedule::DumpLinkedList() this = 0x%p"), this)); + for ( CAdvisePacket * p = &head + ; p + ; p = p->m_next , i++ + ) + { + DbgLog((LOG_TIMING, 1, TEXT("Advise List # %lu, Cookie %d, RefTime %lu"), + i, + p->m_dwAdviseCookie, + p->m_rtEventTime / (UNITS / MILLISECONDS) + )); + } + m_Serialize.Unlock(); +} +#endif diff --git a/Src/Plugins/Input/in_dshow/base/schedule.h b/Src/Plugins/Input/in_dshow/base/schedule.h new file mode 100644 index 00000000..e952f95a --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/schedule.h @@ -0,0 +1,128 @@ +//------------------------------------------------------------------------------ +// File: Schedule.h +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1996-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __CAMSchedule__ +#define __CAMSchedule__ + +class CAMSchedule : private CBaseObject +{ +public: + virtual ~CAMSchedule(); + // ev is the event we should fire if the advise time needs re-evaluating + CAMSchedule( HANDLE ev ); + + DWORD GetAdviseCount(); + REFERENCE_TIME GetNextAdviseTime(); + + // We need a method for derived classes to add advise packets, we return the cookie + DWORD_PTR AddAdvisePacket( const REFERENCE_TIME & time1, const REFERENCE_TIME & time2, HANDLE h, BOOL periodic ); + // And a way to cancel + HRESULT Unadvise(DWORD_PTR dwAdviseCookie); + + // Tell us the time please, and we'll dispatch the expired events. We return the time of the next event. + // NB: The time returned will be "useless" if you start adding extra Advises. But that's the problem of + // whoever is using this helper class (typically a clock). + REFERENCE_TIME Advise( const REFERENCE_TIME & rtTime ); + + // Get the event handle which will be set if advise time requires re-evaluation. + HANDLE GetEvent() const { return m_ev; } + +private: + // We define the nodes that will be used in our singly linked list + // of advise packets. The list is ordered by time, with the + // elements that will expire first at the front. + class CAdvisePacket + { + public: + CAdvisePacket() + {} + + CAdvisePacket * m_next; + DWORD_PTR m_dwAdviseCookie; + REFERENCE_TIME m_rtEventTime; // Time at which event should be set + REFERENCE_TIME m_rtPeriod; // Periodic time + HANDLE m_hNotify; // Handle to event or semephore + BOOL m_bPeriodic; // TRUE => Periodic event + + CAdvisePacket( __inout_opt CAdvisePacket * next, LONGLONG time ) : m_next(next), m_rtEventTime(time) + {} + + void InsertAfter( __inout CAdvisePacket * p ) + { + p->m_next = m_next; + m_next = p; + } + + int IsZ() const // That is, is it the node that represents the end of the list + { return m_next == 0; } + + CAdvisePacket * RemoveNext() + { + CAdvisePacket *const next = m_next; + CAdvisePacket *const new_next = next->m_next; + m_next = new_next; + return next; + } + + void DeleteNext() + { + delete RemoveNext(); + } + + CAdvisePacket * Next() const + { + CAdvisePacket * result = m_next; + if (result->IsZ()) result = 0; + return result; + } + + DWORD_PTR Cookie() const + { return m_dwAdviseCookie; } + }; + + // Structure is: + // _head -> elmt1 -> elmt2 -> z -> null + // So an empty list is: _head -> z -> null + // Having _head & z as links makes insertaion, + // deletion and shunting much easier. + CAdvisePacket head, z; // z is both a _tail and a sentry + + volatile DWORD_PTR m_dwNextCookie; // Strictly increasing + volatile DWORD m_dwAdviseCount; // Number of elements on list + + CCritSec m_Serialize; + + // AddAdvisePacket: adds the packet, returns the cookie (0 if failed) + DWORD_PTR AddAdvisePacket( __inout CAdvisePacket * pPacket ); + // Event that we should set if the packed added above will be the next to fire. + const HANDLE m_ev; + + // A Shunt is where we have changed the first element in the + // list and want it re-evaluating (i.e. repositioned) in + // the list. + void ShuntHead(); + + // Rather than delete advise packets, we cache them for future use + CAdvisePacket * m_pAdviseCache; + DWORD m_dwCacheCount; + enum { dwCacheMax = 5 }; // Don't bother caching more than five + + void Delete( __inout CAdvisePacket * pLink );// This "Delete" will cache the Link + +// Attributes and methods for debugging +public: +#ifdef DEBUG + void DumpLinkedList(); +#else + void DumpLinkedList() {} +#endif + +}; + +#endif // __CAMSchedule__ diff --git a/Src/Plugins/Input/in_dshow/base/seekpt.cpp b/Src/Plugins/Input/in_dshow/base/seekpt.cpp new file mode 100644 index 00000000..0fcde4db --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/seekpt.cpp @@ -0,0 +1,83 @@ +//------------------------------------------------------------------------------ +// File: SeekPT.cpp +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> +#include "seekpt.h" + +//================================================================== +// CreateInstance +// This goes in the factory template table to create new instances +// If there is already a mapper instance - return that, else make one +// and save it in a static variable so that forever after we can return that. +//================================================================== + +CUnknown * CSeekingPassThru::CreateInstance(__inout_opt LPUNKNOWN pUnk, __inout HRESULT *phr) +{ + return new CSeekingPassThru(NAME("Seeking PassThru"),pUnk, phr); +} + + +STDMETHODIMP CSeekingPassThru::NonDelegatingQueryInterface(REFIID riid, __deref_out void ** ppv) +{ + if (riid == IID_ISeekingPassThru) { + return GetInterface((ISeekingPassThru *) this, ppv); + } else { + if (m_pPosPassThru && + (riid == IID_IMediaSeeking || + riid == IID_IMediaPosition)) { + return m_pPosPassThru->NonDelegatingQueryInterface(riid,ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } + } +} + + +CSeekingPassThru::CSeekingPassThru( __in_opt LPCTSTR pName, __inout_opt LPUNKNOWN pUnk, __inout HRESULT *phr ) + : CUnknown(pName, pUnk, phr), + m_pPosPassThru(NULL) +{ +} + + +CSeekingPassThru::~CSeekingPassThru() +{ + delete m_pPosPassThru; +} + +STDMETHODIMP CSeekingPassThru::Init(BOOL bRendererSeeking, IPin *pPin) +{ + HRESULT hr = NOERROR; + if (m_pPosPassThru) { + hr = E_FAIL; + } else { + m_pPosPassThru = + bRendererSeeking ? + new CRendererPosPassThru( + NAME("Render Seeking COM object"), + (IUnknown *)this, + &hr, + pPin) : + new CPosPassThru( + NAME("Render Seeking COM object"), + (IUnknown *)this, + &hr, + pPin); + if (!m_pPosPassThru) { + hr = E_OUTOFMEMORY; + } else { + if (FAILED(hr)) { + delete m_pPosPassThru; + m_pPosPassThru = NULL; + } + } + } + return hr; +} + diff --git a/Src/Plugins/Input/in_dshow/base/seekpt.h b/Src/Plugins/Input/in_dshow/base/seekpt.h new file mode 100644 index 00000000..26abdf3e --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/seekpt.h @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// File: SeekPT.h +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __seekpt_h__ +#define __seekpt_h__ + + +class CSeekingPassThru : public ISeekingPassThru, public CUnknown +{ +public: + static CUnknown *CreateInstance(__inout_opt LPUNKNOWN pUnk, __inout HRESULT *phr); + CSeekingPassThru(__in_opt LPCTSTR pName, __inout_opt LPUNKNOWN pUnk, __inout HRESULT *phr); + ~CSeekingPassThru(); + + DECLARE_IUNKNOWN; + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void ** ppv); + + STDMETHODIMP Init(BOOL bSupportRendering, IPin *pPin); + +private: + CPosPassThru *m_pPosPassThru; +}; + +#endif diff --git a/Src/Plugins/Input/in_dshow/base/source.cpp b/Src/Plugins/Input/in_dshow/base/source.cpp new file mode 100644 index 00000000..472e36bb --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/source.cpp @@ -0,0 +1,522 @@ +//------------------------------------------------------------------------------ +// File: Source.cpp +// +// Desc: DirectShow base classes - implements CSource, which is a Quartz +// source filter 'template.' +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// Locking Strategy. +// +// Hold the filter critical section (m_pFilter->pStateLock()) to serialise +// access to functions. Note that, in general, this lock may be held +// by a function when the worker thread may want to hold it. Therefore +// if you wish to access shared state from the worker thread you will +// need to add another critical section object. The execption is during +// the threads processing loop, when it is safe to get the filter critical +// section from within FillBuffer(). + +#include <streams.h> + + +// +// CSource::Constructor +// +// Initialise the pin count for the filter. The user will create the pins in +// the derived class. +CSource::CSource(__in_opt LPCTSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid) + : CBaseFilter(pName, lpunk, &m_cStateLock, clsid), + m_iPins(0), + m_paStreams(NULL) +{ +} + +CSource::CSource(__in_opt LPCTSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid, __inout HRESULT *phr) + : CBaseFilter(pName, lpunk, &m_cStateLock, clsid), + m_iPins(0), + m_paStreams(NULL) +{ + UNREFERENCED_PARAMETER(phr); +} + +#ifdef UNICODE +CSource::CSource(__in_opt LPCSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid) + : CBaseFilter(pName, lpunk, &m_cStateLock, clsid), + m_iPins(0), + m_paStreams(NULL) +{ +} + +CSource::CSource(__in_opt LPCSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid, __inout HRESULT *phr) + : CBaseFilter(pName, lpunk, &m_cStateLock, clsid), + m_iPins(0), + m_paStreams(NULL) +{ + UNREFERENCED_PARAMETER(phr); +} +#endif + +// +// CSource::Destructor +// +CSource::~CSource() +{ + /* Free our pins and pin array */ + while (m_iPins != 0) { + // deleting the pins causes them to be removed from the array... + delete m_paStreams[m_iPins - 1]; + } + + ASSERT(m_paStreams == NULL); +} + + +// +// Add a new pin +// +HRESULT CSource::AddPin(__in CSourceStream *pStream) +{ + CAutoLock lock(&m_cStateLock); + + /* Allocate space for this pin and the old ones */ + CSourceStream **paStreams = new CSourceStream *[m_iPins + 1]; + if (paStreams == NULL) { + return E_OUTOFMEMORY; + } + if (m_paStreams != NULL) { + CopyMemory((PVOID)paStreams, (PVOID)m_paStreams, + m_iPins * sizeof(m_paStreams[0])); + paStreams[m_iPins] = pStream; + delete [] m_paStreams; + } + m_paStreams = paStreams; + m_paStreams[m_iPins] = pStream; + m_iPins++; + return S_OK; +} + +// +// Remove a pin - pStream is NOT deleted +// +HRESULT CSource::RemovePin(__in CSourceStream *pStream) +{ + int i; + for (i = 0; i < m_iPins; i++) { + if (m_paStreams[i] == pStream) { + if (m_iPins == 1) { + delete [] m_paStreams; + m_paStreams = NULL; + } else { + /* no need to reallocate */ + while (++i < m_iPins) + m_paStreams[i - 1] = m_paStreams[i]; + } + m_iPins--; + return S_OK; + } + } + return S_FALSE; +} + +// +// FindPin +// +// Set *ppPin to the IPin* that has the id Id. +// or to NULL if the Id cannot be matched. +STDMETHODIMP CSource::FindPin(LPCWSTR Id, __deref_out IPin **ppPin) +{ + CheckPointer(ppPin,E_POINTER); + ValidateReadWritePtr(ppPin,sizeof(IPin *)); + // The -1 undoes the +1 in QueryId and ensures that totally invalid + // strings (for which WstrToInt delivers 0) give a deliver a NULL pin. + int i = WstrToInt(Id) -1; + *ppPin = GetPin(i); + if (*ppPin!=NULL){ + (*ppPin)->AddRef(); + return NOERROR; + } else { + return VFW_E_NOT_FOUND; + } +} + +// +// FindPinNumber +// +// return the number of the pin with this IPin* or -1 if none +int CSource::FindPinNumber(__in IPin *iPin) { + int i; + for (i=0; i<m_iPins; ++i) { + if ((IPin *)(m_paStreams[i])==iPin) { + return i; + } + } + return -1; +} + +// +// GetPinCount +// +// Returns the number of pins this filter has +int CSource::GetPinCount(void) { + + CAutoLock lock(&m_cStateLock); + return m_iPins; +} + + +// +// GetPin +// +// Return a non-addref'd pointer to pin n +// needed by CBaseFilter +CBasePin *CSource::GetPin(int n) { + + CAutoLock lock(&m_cStateLock); + + // n must be in the range 0..m_iPins-1 + // if m_iPins>n && n>=0 it follows that m_iPins>0 + // which is what used to be checked (i.e. checking that we have a pin) + if ((n >= 0) && (n < m_iPins)) { + + ASSERT(m_paStreams[n]); + return m_paStreams[n]; + } + return NULL; +} + + +// + + +// * +// * --- CSourceStream ---- +// * + +// +// Set Id to point to a CoTaskMemAlloc'd +STDMETHODIMP CSourceStream::QueryId(__deref_out LPWSTR *Id) { + CheckPointer(Id,E_POINTER); + ValidateReadWritePtr(Id,sizeof(LPWSTR)); + + // We give the pins id's which are 1,2,... + // FindPinNumber returns -1 for an invalid pin + int i = 1+ m_pFilter->FindPinNumber(this); + if (i<1) return VFW_E_NOT_FOUND; + *Id = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR) * 12); + if (*Id==NULL) { + return E_OUTOFMEMORY; + } + IntToWstr(i, *Id); + return NOERROR; +} + + + +// +// CSourceStream::Constructor +// +// increments the number of pins present on the filter +CSourceStream::CSourceStream( + __in_opt LPCTSTR pObjectName, + __inout HRESULT *phr, + __inout CSource *ps, + __in_opt LPCWSTR pPinName) + : CBaseOutputPin(pObjectName, ps, ps->pStateLock(), phr, pPinName), + m_pFilter(ps) { + + *phr = m_pFilter->AddPin(this); +} + +#ifdef UNICODE +CSourceStream::CSourceStream( + __in_opt LPCSTR pObjectName, + __inout HRESULT *phr, + __inout CSource *ps, + __in_opt LPCWSTR pPinName) + : CBaseOutputPin(pObjectName, ps, ps->pStateLock(), phr, pPinName), + m_pFilter(ps) { + + *phr = m_pFilter->AddPin(this); +} +#endif +// +// CSourceStream::Destructor +// +// Decrements the number of pins on this filter +CSourceStream::~CSourceStream(void) { + + m_pFilter->RemovePin(this); +} + + +// +// CheckMediaType +// +// Do we support this type? Provides the default support for 1 type. +HRESULT CSourceStream::CheckMediaType(const CMediaType *pMediaType) { + + CAutoLock lock(m_pFilter->pStateLock()); + + CMediaType mt; + GetMediaType(&mt); + + if (mt == *pMediaType) { + return NOERROR; + } + + return E_FAIL; +} + + +// +// GetMediaType/3 +// +// By default we support only one type +// iPosition indexes are 0-n +HRESULT CSourceStream::GetMediaType(int iPosition, __inout CMediaType *pMediaType) { + + CAutoLock lock(m_pFilter->pStateLock()); + + if (iPosition<0) { + return E_INVALIDARG; + } + if (iPosition>0) { + return VFW_S_NO_MORE_ITEMS; + } + return GetMediaType(pMediaType); +} + + +// +// Active +// +// The pin is active - start up the worker thread +HRESULT CSourceStream::Active(void) { + + CAutoLock lock(m_pFilter->pStateLock()); + + HRESULT hr; + + if (m_pFilter->IsActive()) { + return S_FALSE; // succeeded, but did not allocate resources (they already exist...) + } + + // do nothing if not connected - its ok not to connect to + // all pins of a source filter + if (!IsConnected()) { + return NOERROR; + } + + hr = CBaseOutputPin::Active(); + if (FAILED(hr)) { + return hr; + } + + ASSERT(!ThreadExists()); + + // start the thread + if (!Create()) { + return E_FAIL; + } + + // Tell thread to initialize. If OnThreadCreate Fails, so does this. + hr = Init(); + if (FAILED(hr)) + return hr; + + return Pause(); +} + + +// +// Inactive +// +// Pin is inactive - shut down the worker thread +// Waits for the worker to exit before returning. +HRESULT CSourceStream::Inactive(void) { + + CAutoLock lock(m_pFilter->pStateLock()); + + HRESULT hr; + + // do nothing if not connected - its ok not to connect to + // all pins of a source filter + if (!IsConnected()) { + return NOERROR; + } + + // !!! need to do this before trying to stop the thread, because + // we may be stuck waiting for our own allocator!!! + + hr = CBaseOutputPin::Inactive(); // call this first to Decommit the allocator + if (FAILED(hr)) { + return hr; + } + + if (ThreadExists()) { + hr = Stop(); + + if (FAILED(hr)) { + return hr; + } + + hr = Exit(); + if (FAILED(hr)) { + return hr; + } + + Close(); // Wait for the thread to exit, then tidy up. + } + + // hr = CBaseOutputPin::Inactive(); // call this first to Decommit the allocator + //if (FAILED(hr)) { + // return hr; + //} + + return NOERROR; +} + + +// +// ThreadProc +// +// When this returns the thread exits +// Return codes > 0 indicate an error occured +DWORD CSourceStream::ThreadProc(void) { + + HRESULT hr; // the return code from calls + Command com; + + do { + com = GetRequest(); + if (com != CMD_INIT) { + DbgLog((LOG_ERROR, 1, TEXT("Thread expected init command"))); + Reply((DWORD) E_UNEXPECTED); + } + } while (com != CMD_INIT); + + DbgLog((LOG_TRACE, 1, TEXT("CSourceStream worker thread initializing"))); + + hr = OnThreadCreate(); // perform set up tasks + if (FAILED(hr)) { + DbgLog((LOG_ERROR, 1, TEXT("CSourceStream::OnThreadCreate failed. Aborting thread."))); + OnThreadDestroy(); + Reply(hr); // send failed return code from OnThreadCreate + return 1; + } + + // Initialisation suceeded + Reply(NOERROR); + + Command cmd; + do { + cmd = GetRequest(); + + switch (cmd) { + + case CMD_EXIT: + Reply(NOERROR); + break; + + case CMD_RUN: + DbgLog((LOG_ERROR, 1, TEXT("CMD_RUN received before a CMD_PAUSE???"))); + // !!! fall through??? + + case CMD_PAUSE: + Reply(NOERROR); + DoBufferProcessingLoop(); + break; + + case CMD_STOP: + Reply(NOERROR); + break; + + default: + DbgLog((LOG_ERROR, 1, TEXT("Unknown command %d received!"), cmd)); + Reply((DWORD) E_NOTIMPL); + break; + } + } while (cmd != CMD_EXIT); + + hr = OnThreadDestroy(); // tidy up. + if (FAILED(hr)) { + DbgLog((LOG_ERROR, 1, TEXT("CSourceStream::OnThreadDestroy failed. Exiting thread."))); + return 1; + } + + DbgLog((LOG_TRACE, 1, TEXT("CSourceStream worker thread exiting"))); + return 0; +} + + +// +// DoBufferProcessingLoop +// +// Grabs a buffer and calls the users processing function. +// Overridable, so that different delivery styles can be catered for. +HRESULT CSourceStream::DoBufferProcessingLoop(void) { + + Command com; + + OnThreadStartPlay(); + + do { + while (!CheckRequest(&com)) { + + IMediaSample *pSample; + + HRESULT hr = GetDeliveryBuffer(&pSample,NULL,NULL,0); + if (FAILED(hr)) { + Sleep(1); + continue; // go round again. Perhaps the error will go away + // or the allocator is decommited & we will be asked to + // exit soon. + } + + // Virtual function user will override. + hr = FillBuffer(pSample); + + if (hr == S_OK) { + hr = Deliver(pSample); + pSample->Release(); + + // downstream filter returns S_FALSE if it wants us to + // stop or an error if it's reporting an error. + if(hr != S_OK) + { + DbgLog((LOG_TRACE, 2, TEXT("Deliver() returned %08x; stopping"), hr)); + return S_OK; + } + + } else if (hr == S_FALSE) { + // derived class wants us to stop pushing data + pSample->Release(); + DeliverEndOfStream(); + return S_OK; + } else { + // derived class encountered an error + pSample->Release(); + DbgLog((LOG_ERROR, 1, TEXT("Error %08lX from FillBuffer!!!"), hr)); + DeliverEndOfStream(); + m_pFilter->NotifyEvent(EC_ERRORABORT, hr, 0); + return hr; + } + + // all paths release the sample + } + + // For all commands sent to us there must be a Reply call! + + if (com == CMD_RUN || com == CMD_PAUSE) { + Reply(NOERROR); + } else if (com != CMD_STOP) { + Reply((DWORD) E_UNEXPECTED); + DbgLog((LOG_ERROR, 1, TEXT("Unexpected command!!!"))); + } + } while (com != CMD_STOP); + + return S_FALSE; +} + diff --git a/Src/Plugins/Input/in_dshow/base/source.h b/Src/Plugins/Input/in_dshow/base/source.h new file mode 100644 index 00000000..e6e451bd --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/source.h @@ -0,0 +1,172 @@ +//------------------------------------------------------------------------------ +// File: Source.h +// +// Desc: DirectShow base classes - defines classes to simplify creation of +// ActiveX source filters that support continuous generation of data. +// No support is provided for IMediaControl or IMediaPosition. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// +// Derive your source filter from CSource. +// During construction either: +// Create some CSourceStream objects to manage your pins +// Provide the user with a means of doing so eg, an IPersistFile interface. +// +// CSource provides: +// IBaseFilter interface management +// IMediaFilter interface management, via CBaseFilter +// Pin counting for CBaseFilter +// +// Derive a class from CSourceStream to manage your output pin types +// Implement GetMediaType/1 to return the type you support. If you support multiple +// types then overide GetMediaType/3, CheckMediaType and GetMediaTypeCount. +// Implement Fillbuffer() to put data into one buffer. +// +// CSourceStream provides: +// IPin management via CBaseOutputPin +// Worker thread management + +#ifndef __CSOURCE__ +#define __CSOURCE__ + +class CSourceStream; // The class that will handle each pin + + +// +// CSource +// +// Override construction to provide a means of creating +// CSourceStream derived objects - ie a way of creating pins. +class CSource : public CBaseFilter { +public: + + CSource(__in_opt LPCTSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid, __inout HRESULT *phr); + CSource(__in_opt LPCTSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid); +#ifdef UNICODE + CSource(__in_opt LPCSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid, __inout HRESULT *phr); + CSource(__in_opt LPCSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid); +#endif + ~CSource(); + + int GetPinCount(void); + CBasePin *GetPin(int n); + + // -- Utilities -- + + CCritSec* pStateLock(void) { return &m_cStateLock; } // provide our critical section + + HRESULT AddPin(__in CSourceStream *); + HRESULT RemovePin(__in CSourceStream *); + + STDMETHODIMP FindPin( + LPCWSTR Id, + __deref_out IPin ** ppPin + ); + + int FindPinNumber(__in IPin *iPin); + +protected: + + int m_iPins; // The number of pins on this filter. Updated by CSourceStream + // constructors & destructors. + CSourceStream **m_paStreams; // the pins on this filter. + + CCritSec m_cStateLock; // Lock this to serialize function accesses to the filter state + +}; + + +// +// CSourceStream +// +// Use this class to manage a stream of data that comes from a +// pin. +// Uses a worker thread to put data on the pin. +class CSourceStream : public CAMThread, public CBaseOutputPin { +public: + + CSourceStream(__in_opt LPCTSTR pObjectName, + __inout HRESULT *phr, + __inout CSource *pms, + __in_opt LPCWSTR pName); +#ifdef UNICODE + CSourceStream(__in_opt LPCSTR pObjectName, + __inout HRESULT *phr, + __inout CSource *pms, + __in_opt LPCWSTR pName); +#endif + virtual ~CSourceStream(void); // virtual destructor ensures derived class destructors are called too. + +protected: + + CSource *m_pFilter; // The parent of this stream + + // * + // * Data Source + // * + // * The following three functions: FillBuffer, OnThreadCreate/Destroy, are + // * called from within the ThreadProc. They are used in the creation of + // * the media samples this pin will provide + // * + + // Override this to provide the worker thread a means + // of processing a buffer + virtual HRESULT FillBuffer(IMediaSample *pSamp) PURE; + + // Called as the thread is created/destroyed - use to perform + // jobs such as start/stop streaming mode + // If OnThreadCreate returns an error the thread will exit. + virtual HRESULT OnThreadCreate(void) {return NOERROR;}; + virtual HRESULT OnThreadDestroy(void) {return NOERROR;}; + virtual HRESULT OnThreadStartPlay(void) {return NOERROR;}; + + // * + // * Worker Thread + // * + + HRESULT Active(void); // Starts up the worker thread + HRESULT Inactive(void); // Exits the worker thread. + +public: + // thread commands + enum Command {CMD_INIT, CMD_PAUSE, CMD_RUN, CMD_STOP, CMD_EXIT}; + HRESULT Init(void) { return CallWorker(CMD_INIT); } + HRESULT Exit(void) { return CallWorker(CMD_EXIT); } + HRESULT Run(void) { return CallWorker(CMD_RUN); } + HRESULT Pause(void) { return CallWorker(CMD_PAUSE); } + HRESULT Stop(void) { return CallWorker(CMD_STOP); } + +protected: + Command GetRequest(void) { return (Command) CAMThread::GetRequest(); } + BOOL CheckRequest(Command *pCom) { return CAMThread::CheckRequest( (DWORD *) pCom); } + + // override these if you want to add thread commands + virtual DWORD ThreadProc(void); // the thread function + + virtual HRESULT DoBufferProcessingLoop(void); // the loop executed whilst running + + + // * + // * AM_MEDIA_TYPE support + // * + + // If you support more than one media type then override these 2 functions + virtual HRESULT CheckMediaType(const CMediaType *pMediaType); + virtual HRESULT GetMediaType(int iPosition, __inout CMediaType *pMediaType); // List pos. 0-n + + // If you support only one type then override this fn. + // This will only be called by the default implementations + // of CheckMediaType and GetMediaType(int, CMediaType*) + // You must override this fn. or the above 2! + virtual HRESULT GetMediaType(__inout CMediaType *pMediaType) {return E_UNEXPECTED;} + + STDMETHODIMP QueryId( + __deref_out LPWSTR * Id + ); +}; + +#endif // __CSOURCE__ + diff --git a/Src/Plugins/Input/in_dshow/base/streams.h b/Src/Plugins/Input/in_dshow/base/streams.h new file mode 100644 index 00000000..72c6fd00 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/streams.h @@ -0,0 +1,202 @@ +//------------------------------------------------------------------------------ +// File: Streams.h +// +// Desc: DirectShow base classes - defines overall streams architecture. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __STREAMS__ +#define __STREAMS__ + +#ifdef _MSC_VER +// disable some level-4 warnings, use #pragma warning(enable:###) to re-enable +#pragma warning(disable:4100) // warning C4100: unreferenced formal parameter +#pragma warning(disable:4201) // warning C4201: nonstandard extension used : nameless struct/union +#pragma warning(disable:4511) // warning C4511: copy constructor could not be generated +#pragma warning(disable:4512) // warning C4512: assignment operator could not be generated +#pragma warning(disable:4514) // warning C4514: "unreferenced inline function has been removed" + +#if _MSC_VER>=1100 +#define AM_NOVTABLE __declspec(novtable) +#else +#define AM_NOVTABLE +#endif +#endif // MSC_VER + + +// Because of differences between Visual C++ and older Microsoft SDKs, +// you may have defined _DEBUG without defining DEBUG. This logic +// ensures that both will be set if Visual C++ sets _DEBUG. +#ifdef _DEBUG +#ifndef DEBUG +#define DEBUG +#endif +#endif + + +#include <windows.h> +#include <windowsx.h> +#include <olectl.h> +#include <ddraw.h> +#include <mmsystem.h> + + +#ifndef NUMELMS +#if _WIN32_WINNT < 0x0600 + #define NUMELMS(aa) (sizeof(aa)/sizeof((aa)[0])) +#else + #define NUMELMS(aa) ARRAYSIZE(aa) +#endif +#endif + +/////////////////////////////////////////////////////////////////////////// +// The following definitions come from the Platform SDK and are required if +// the applicaiton is being compiled with the headers from Visual C++ 6.0. +/////////////////////////////////////////////////// //////////////////////// +#ifndef InterlockedExchangePointer + #define InterlockedExchangePointer(Target, Value) \ + (PVOID)InterlockedExchange((PLONG)(Target), (LONG)(Value)) +#endif + +#ifndef _WAVEFORMATEXTENSIBLE_ +#define _WAVEFORMATEXTENSIBLE_ +typedef struct { + WAVEFORMATEX Format; + union { + WORD wValidBitsPerSample; /* bits of precision */ + WORD wSamplesPerBlock; /* valid if wBitsPerSample==0 */ + WORD wReserved; /* If neither applies, set to zero. */ + } Samples; + DWORD dwChannelMask; /* which channels are */ + /* present in stream */ + GUID SubFormat; +} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE; +#endif // !_WAVEFORMATEXTENSIBLE_ + +#if !defined(WAVE_FORMAT_EXTENSIBLE) +#define WAVE_FORMAT_EXTENSIBLE 0xFFFE +#endif // !defined(WAVE_FORMAT_EXTENSIBLE) + +#ifndef GetWindowLongPtr + #define GetWindowLongPtrA GetWindowLongA + #define GetWindowLongPtrW GetWindowLongW + #ifdef UNICODE + #define GetWindowLongPtr GetWindowLongPtrW + #else + #define GetWindowLongPtr GetWindowLongPtrA + #endif // !UNICODE +#endif // !GetWindowLongPtr + +#ifndef SetWindowLongPtr + #define SetWindowLongPtrA SetWindowLongA + #define SetWindowLongPtrW SetWindowLongW + #ifdef UNICODE + #define SetWindowLongPtr SetWindowLongPtrW + #else + #define SetWindowLongPtr SetWindowLongPtrA + #endif // !UNICODE +#endif // !SetWindowLongPtr + +#ifndef GWLP_WNDPROC + #define GWLP_WNDPROC (-4) +#endif +#ifndef GWLP_HINSTANCE + #define GWLP_HINSTANCE (-6) +#endif +#ifndef GWLP_HWNDPARENT + #define GWLP_HWNDPARENT (-8) +#endif +#ifndef GWLP_USERDATA + #define GWLP_USERDATA (-21) +#endif +#ifndef GWLP_ID + #define GWLP_ID (-12) +#endif +#ifndef DWLP_MSGRESULT + #define DWLP_MSGRESULT 0 +#endif +#ifndef DWLP_DLGPROC + #define DWLP_DLGPROC DWLP_MSGRESULT + sizeof(LRESULT) +#endif +#ifndef DWLP_USER + #define DWLP_USER DWLP_DLGPROC + sizeof(DLGPROC) +#endif + + +#pragma warning(push) +#pragma warning(disable: 4312 4244) +// _GetWindowLongPtr +// Templated version of GetWindowLongPtr, to suppress spurious compiler warning. +template <class T> +T _GetWindowLongPtr(HWND hwnd, int nIndex) +{ + return (T)GetWindowLongPtr(hwnd, nIndex); +} + +// _SetWindowLongPtr +// Templated version of SetWindowLongPtr, to suppress spurious compiler warning. +template <class T> +LONG_PTR _SetWindowLongPtr(HWND hwnd, int nIndex, T p) +{ + return SetWindowLongPtr(hwnd, nIndex, (LONG_PTR)p); +} +#pragma warning(pop) + +/////////////////////////////////////////////////////////////////////////// +// End Platform SDK definitions +/////////////////////////////////////////////////////////////////////////// + + +#include <strmif.h> // Generated IDL header file for streams interfaces +#include <intsafe.h> // required by amvideo.h + +#include <reftime.h> // Helper class for REFERENCE_TIME management +#include <wxdebug.h> // Debug support for logging and ASSERTs +#include <amvideo.h> // ActiveMovie video interfaces and definitions +//include amaudio.h explicitly if you need it. it requires the DX SDK. +//#include <amaudio.h> // ActiveMovie audio interfaces and definitions +#include <wxutil.h> // General helper classes for threads etc +#include <combase.h> // Base COM classes to support IUnknown +#include <dllsetup.h> // Filter registration support functions +#include <measure.h> // Performance measurement +#include <comlite.h> // Light weight com function prototypes + +#include <cache.h> // Simple cache container class +#include <wxlist.h> // Non MFC generic list class +#include <msgthrd.h> // CMsgThread +#include <mtype.h> // Helper class for managing media types +#include <fourcc.h> // conversions between FOURCCs and GUIDs +#include <control.h> // generated from control.odl +#include <ctlutil.h> // control interface utility classes +#include <evcode.h> // event code definitions +#include <amfilter.h> // Main streams architecture class hierachy +#include <transfrm.h> // Generic transform filter +#include <transip.h> // Generic transform-in-place filter +#include <uuids.h> // declaration of type GUIDs and well-known clsids +#include <source.h> // Generic source filter +#include <outputq.h> // Output pin queueing +#include <errors.h> // HRESULT status and error definitions +#include <renbase.h> // Base class for writing ActiveX renderers +#include <winutil.h> // Helps with filters that manage windows +#include <winctrl.h> // Implements the IVideoWindow interface +#include <videoctl.h> // Specifically video related classes +#include <refclock.h> // Base clock class +#include <sysclock.h> // System clock +#include <pstream.h> // IPersistStream helper class +#include <vtrans.h> // Video Transform Filter base class +#include <amextra.h> +#include <cprop.h> // Base property page class +#include <strmctl.h> // IAMStreamControl support +#include <edevdefs.h> // External device control interface defines +#include <audevcod.h> // audio filter device error event codes + + + +#else + #ifdef DEBUG + #pragma message("STREAMS.H included TWICE") + #endif +#endif // __STREAMS__ + diff --git a/Src/Plugins/Input/in_dshow/base/strmctl.cpp b/Src/Plugins/Input/in_dshow/base/strmctl.cpp new file mode 100644 index 00000000..76b3810c --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/strmctl.cpp @@ -0,0 +1,402 @@ +//------------------------------------------------------------------------------ +// File: StrmCtl.cpp +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1996-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> +#include <strmctl.h> + +CBaseStreamControl::CBaseStreamControl(__inout HRESULT *phr) +: m_StreamState(STREAM_FLOWING) +, m_StreamStateOnStop(STREAM_FLOWING) // means no pending stop +, m_tStartTime(MAX_TIME) +, m_tStopTime(MAX_TIME) +, m_StreamEvent(FALSE, phr) +, m_dwStartCookie(0) +, m_dwStopCookie(0) +, m_pRefClock(NULL) +, m_FilterState(State_Stopped) +, m_bIsFlushing(FALSE) +, m_bStopSendExtra(FALSE) +{} + +CBaseStreamControl::~CBaseStreamControl() +{ + // Make sure we release the clock. + SetSyncSource(NULL); + return; +} + + +STDMETHODIMP CBaseStreamControl::StopAt(const REFERENCE_TIME * ptStop, BOOL bSendExtra, DWORD dwCookie) +{ + CAutoLock lck(&m_CritSec); + m_bStopSendExtra = FALSE; // reset + m_bStopExtraSent = FALSE; + if (ptStop) + { + if (*ptStop == MAX_TIME) + { + DbgLog((LOG_TRACE,2,TEXT("StopAt: Cancel stop"))); + CancelStop(); + // If there's now a command to start in the future, we assume + // they want to be stopped when the graph is first run + if (m_FilterState == State_Stopped && m_tStartTime < MAX_TIME) { + m_StreamState = STREAM_DISCARDING; + DbgLog((LOG_TRACE,2,TEXT("graph will begin by DISCARDING"))); + } + return NOERROR; + } + DbgLog((LOG_TRACE,2,TEXT("StopAt: %dms extra=%d"), + (int)(*ptStop/10000), bSendExtra)); + // if the first command is to stop in the future, then we assume they + // want to be started when the graph is first run + if (m_FilterState == State_Stopped && m_tStartTime > *ptStop) { + m_StreamState = STREAM_FLOWING; + DbgLog((LOG_TRACE,2,TEXT("graph will begin by FLOWING"))); + } + m_bStopSendExtra = bSendExtra; + m_tStopTime = *ptStop; + m_dwStopCookie = dwCookie; + m_StreamStateOnStop = STREAM_DISCARDING; + } + else + { + DbgLog((LOG_TRACE,2,TEXT("StopAt: now"))); + // sending an extra frame when told to stop now would mess people up + m_bStopSendExtra = FALSE; + m_tStopTime = MAX_TIME; + m_dwStopCookie = 0; + m_StreamState = STREAM_DISCARDING; + m_StreamStateOnStop = STREAM_FLOWING; // no pending stop + } + // we might change our mind what to do with a sample we're blocking + m_StreamEvent.Set(); + return NOERROR; +} + +STDMETHODIMP CBaseStreamControl::StartAt +( const REFERENCE_TIME *ptStart, DWORD dwCookie ) +{ + CAutoLock lck(&m_CritSec); + if (ptStart) + { + if (*ptStart == MAX_TIME) + { + DbgLog((LOG_TRACE,2,TEXT("StartAt: Cancel start"))); + CancelStart(); + // If there's now a command to stop in the future, we assume + // they want to be started when the graph is first run + if (m_FilterState == State_Stopped && m_tStopTime < MAX_TIME) { + DbgLog((LOG_TRACE,2,TEXT("graph will begin by FLOWING"))); + m_StreamState = STREAM_FLOWING; + } + return NOERROR; + } + DbgLog((LOG_TRACE,2,TEXT("StartAt: %dms"), (int)(*ptStart/10000))); + // if the first command is to start in the future, then we assume they + // want to be stopped when the graph is first run + if (m_FilterState == State_Stopped && m_tStopTime >= *ptStart) { + DbgLog((LOG_TRACE,2,TEXT("graph will begin by DISCARDING"))); + m_StreamState = STREAM_DISCARDING; + } + m_tStartTime = *ptStart; + m_dwStartCookie = dwCookie; + // if (m_tStopTime == m_tStartTime) CancelStop(); + } + else + { + DbgLog((LOG_TRACE,2,TEXT("StartAt: now"))); + m_tStartTime = MAX_TIME; + m_dwStartCookie = 0; + m_StreamState = STREAM_FLOWING; + } + // we might change our mind what to do with a sample we're blocking + m_StreamEvent.Set(); + return NOERROR; +} + +// Retrieve information about current settings +STDMETHODIMP CBaseStreamControl::GetInfo(__out AM_STREAM_INFO *pInfo) +{ + if (pInfo == NULL) + return E_POINTER; + + pInfo->tStart = m_tStartTime; + pInfo->tStop = m_tStopTime; + pInfo->dwStartCookie = m_dwStartCookie; + pInfo->dwStopCookie = m_dwStopCookie; + pInfo->dwFlags = m_bStopSendExtra ? AM_STREAM_INFO_STOP_SEND_EXTRA : 0; + pInfo->dwFlags |= m_tStartTime == MAX_TIME ? 0 : AM_STREAM_INFO_START_DEFINED; + pInfo->dwFlags |= m_tStopTime == MAX_TIME ? 0 : AM_STREAM_INFO_STOP_DEFINED; + switch (m_StreamState) { + default: + DbgBreak("Invalid stream state"); + case STREAM_FLOWING: + break; + case STREAM_DISCARDING: + pInfo->dwFlags |= AM_STREAM_INFO_DISCARDING; + break; + } + return S_OK; +} + + +void CBaseStreamControl::ExecuteStop() +{ + ASSERT(CritCheckIn(&m_CritSec)); + m_StreamState = m_StreamStateOnStop; + if (m_dwStopCookie && m_pSink) { + DbgLog((LOG_TRACE,2,TEXT("*sending EC_STREAM_CONTROL_STOPPED (%d)"), + m_dwStopCookie)); + m_pSink->Notify(EC_STREAM_CONTROL_STOPPED, (LONG_PTR)this, m_dwStopCookie); + } + CancelStop(); // This will do the tidy up +} + +void CBaseStreamControl::ExecuteStart() +{ + ASSERT(CritCheckIn(&m_CritSec)); + m_StreamState = STREAM_FLOWING; + if (m_dwStartCookie) { + DbgLog((LOG_TRACE,2,TEXT("*sending EC_STREAM_CONTROL_STARTED (%d)"), + m_dwStartCookie)); + m_pSink->Notify(EC_STREAM_CONTROL_STARTED, (LONG_PTR)this, m_dwStartCookie); + } + CancelStart(); // This will do the tidy up +} + +void CBaseStreamControl::CancelStop() +{ + ASSERT(CritCheckIn(&m_CritSec)); + m_tStopTime = MAX_TIME; + m_dwStopCookie = 0; + m_StreamStateOnStop = STREAM_FLOWING; +} + +void CBaseStreamControl::CancelStart() +{ + ASSERT(CritCheckIn(&m_CritSec)); + m_tStartTime = MAX_TIME; + m_dwStartCookie = 0; +} + + +// This guy will return one of the three StreamControlState's. Here's what the caller +// should do for each one: +// +// STREAM_FLOWING: Proceed as usual (render or pass the sample on) +// STREAM_DISCARDING: Calculate the time 'til *pSampleStart and wait that long +// for the event handle (GetStreamEventHandle()). If the +// wait expires, throw the sample away. If the event +// fires, call me back, I've changed my mind. +// I use pSampleStart (not Stop) so that live sources don't +// block for the duration of their samples, since the clock +// will always read approximately pSampleStart when called + + +// All through this code, you'll notice the following rules: +// - When start and stop time are the same, it's as if start was first +// - An event is considered inside the sample when it's >= sample start time +// but < sample stop time +// - if any part of the sample is supposed to be sent, we'll send the whole +// thing since we don't break it into smaller pieces +// - If we skip over a start or stop without doing it, we still signal the event +// and reset ourselves in case somebody's waiting for the event, and to make +// sure we notice that the event is past and should be forgotten +// Here are the 19 cases that have to be handled (x=start o=stop <-->=sample): +// +// 1. xo<--> start then stop +// 2. ox<--> stop then start +// 3. x<o-> start +// 4. o<x-> stop then start +// 5. x<-->o start +// 6. o<-->x stop +// 7. <x->o start +// 8. <o->x no change +// 9. <xo> start +// 10. <ox> stop then start +// 11. <-->xo no change +// 12. <-->ox no change +// 13. x<--> start +// 14. <x-> start +// 15. <-->x no change +// 16. o<--> stop +// 17. <o-> no change +// 18. <-->o no change +// 19. <--> no change + + +enum CBaseStreamControl::StreamControlState CBaseStreamControl::CheckSampleTimes +( __in const REFERENCE_TIME * pSampleStart, __in const REFERENCE_TIME * pSampleStop ) +{ + CAutoLock lck(&m_CritSec); + + ASSERT(!m_bIsFlushing); + ASSERT(pSampleStart && pSampleStop); + + // Don't ask me how I came up with the code below to handle all 19 cases + // - DannyMi + + if (m_tStopTime >= *pSampleStart) + { + if (m_tStartTime >= *pSampleStop) + return m_StreamState; // cases 8 11 12 15 17 18 19 + if (m_tStopTime < m_tStartTime) + ExecuteStop(); // case 10 + ExecuteStart(); // cases 3 5 7 9 13 14 + return m_StreamState; + } + + if (m_tStartTime >= *pSampleStop) + { + ExecuteStop(); // cases 6 16 + return m_StreamState; + } + + if (m_tStartTime <= m_tStopTime) + { + ExecuteStart(); + ExecuteStop(); + return m_StreamState; // case 1 + } + else + { + ExecuteStop(); + ExecuteStart(); + return m_StreamState; // cases 2 4 + } +} + + +enum CBaseStreamControl::StreamControlState CBaseStreamControl::CheckStreamState( IMediaSample * pSample ) +{ + + REFERENCE_TIME rtBufferStart, rtBufferStop; + const BOOL bNoBufferTimes = + pSample == NULL || + FAILED(pSample->GetTime(&rtBufferStart, &rtBufferStop)); + + StreamControlState state; + LONG lWait; + + do + { + // something has to break out of the blocking + if (m_bIsFlushing || m_FilterState == State_Stopped) + return STREAM_DISCARDING; + + if (bNoBufferTimes) { + // Can't do anything until we get a time stamp + state = m_StreamState; + break; + } else { + state = CheckSampleTimes( &rtBufferStart, &rtBufferStop ); + if (state == STREAM_FLOWING) + break; + + // we aren't supposed to send this, but we've been + // told to send one more than we were supposed to + // (and the stop isn't still pending and we're streaming) + if (m_bStopSendExtra && !m_bStopExtraSent && + m_tStopTime == MAX_TIME && + m_FilterState != State_Stopped) { + m_bStopExtraSent = TRUE; + DbgLog((LOG_TRACE,2,TEXT("%d sending an EXTRA frame"), + m_dwStopCookie)); + state = STREAM_FLOWING; + break; + } + } + + // We're in discarding mode + + // If we've no clock, discard as fast as we can + if (!m_pRefClock) { + break; + + // If we're paused, we can't discard in a timely manner because + // there's no such thing as stream times. We must block until + // we run or stop, or we'll end up throwing the whole stream away + // as quickly as possible + } else if (m_FilterState == State_Paused) { + lWait = INFINITE; + + } else { + // wait until it's time for the sample until we say "discard" + // ("discard in a timely fashion") + REFERENCE_TIME rtNow; + EXECUTE_ASSERT(SUCCEEDED(m_pRefClock->GetTime(&rtNow))); + rtNow -= m_tRunStart; // Into relative ref-time + lWait = LONG((rtBufferStart - rtNow)/10000); // 100ns -> ms + if (lWait < 10) break; // Not worth waiting - discard early + } + + } while(WaitForSingleObject(GetStreamEventHandle(), lWait) != WAIT_TIMEOUT); + + return state; +} + + +void CBaseStreamControl::NotifyFilterState( FILTER_STATE new_state, REFERENCE_TIME tStart ) +{ + CAutoLock lck(&m_CritSec); + + // or we will get confused + if (m_FilterState == new_state) + return; + + switch (new_state) + { + case State_Stopped: + + DbgLog((LOG_TRACE,2,TEXT("Filter is STOPPED"))); + + // execute any pending starts and stops in the right order, + // to make sure all notifications get sent, and we end up + // in the right state to begin next time (??? why not?) + + if (m_tStartTime != MAX_TIME && m_tStopTime == MAX_TIME) { + ExecuteStart(); + } else if (m_tStopTime != MAX_TIME && m_tStartTime == MAX_TIME) { + ExecuteStop(); + } else if (m_tStopTime != MAX_TIME && m_tStartTime != MAX_TIME) { + if (m_tStartTime <= m_tStopTime) { + ExecuteStart(); + ExecuteStop(); + } else { + ExecuteStop(); + ExecuteStart(); + } + } + // always start off flowing when the graph starts streaming + // unless told otherwise + m_StreamState = STREAM_FLOWING; + m_FilterState = new_state; + break; + + case State_Running: + + DbgLog((LOG_TRACE,2,TEXT("Filter is RUNNING"))); + + m_tRunStart = tStart; + // fall-through + + default: // case State_Paused: + m_FilterState = new_state; + } + // unblock! + m_StreamEvent.Set(); +} + + +void CBaseStreamControl::Flushing(BOOL bInProgress) +{ + CAutoLock lck(&m_CritSec); + m_bIsFlushing = bInProgress; + m_StreamEvent.Set(); +} diff --git a/Src/Plugins/Input/in_dshow/base/strmctl.h b/Src/Plugins/Input/in_dshow/base/strmctl.h new file mode 100644 index 00000000..4077e6c3 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/strmctl.h @@ -0,0 +1,157 @@ +//------------------------------------------------------------------------------ +// File: StrmCtl.h +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1996-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __strmctl_h__ +#define __strmctl_h__ + +class CBaseStreamControl : public IAMStreamControl +{ +public: + // Used by the implementation + enum StreamControlState + { STREAM_FLOWING = 0x1000, + STREAM_DISCARDING + }; + +private: + enum StreamControlState m_StreamState; // Current stream state + enum StreamControlState m_StreamStateOnStop; // State after next stop + // (i.e.Blocking or Discarding) + + REFERENCE_TIME m_tStartTime; // MAX_TIME implies none + REFERENCE_TIME m_tStopTime; // MAX_TIME implies none + DWORD m_dwStartCookie; // Cookie for notification to app + DWORD m_dwStopCookie; // Cookie for notification to app + volatile BOOL m_bIsFlushing; // No optimization pls! + volatile BOOL m_bStopSendExtra; // bSendExtra was set + volatile BOOL m_bStopExtraSent; // the extra one was sent + + CCritSec m_CritSec; // CritSec to guard above attributes + + // Event to fire when we can come + // out of blocking, or to come out of waiting + // to discard if we change our minds. + // + CAMEvent m_StreamEvent; + + // All of these methods execute immediately. Helpers for others. + // + void ExecuteStop(); + void ExecuteStart(); + void CancelStop(); + void CancelStart(); + + // Some things we need to be told by our owning filter + // Your pin must also expose IAMStreamControl when QI'd for it! + // + IReferenceClock * m_pRefClock; // Need it to set advises + // Filter must tell us via + // SetSyncSource + IMediaEventSink * m_pSink; // Event sink + // Filter must tell us after it + // creates it in JoinFilterGraph() + FILTER_STATE m_FilterState; // Just need it! + // Filter must tell us via + // NotifyFilterState + REFERENCE_TIME m_tRunStart; // Per the Run call to the filter + + // This guy will return one of the three StreamControlState's. Here's what + // the caller should do for each one: + // + // STREAM_FLOWING: Proceed as usual (render or pass the sample on) + // STREAM_DISCARDING: Calculate the time 'til *pSampleStop and wait + // that long for the event handle + // (GetStreamEventHandle()). If the wait + // expires, throw the sample away. If the event + // fires, call me back - I've changed my mind. + // + enum StreamControlState CheckSampleTimes( __in const REFERENCE_TIME * pSampleStart, + __in const REFERENCE_TIME * pSampleStop ); + +public: + // You don't have to tell us much when we're created, but there are other + // obligations that must be met. See SetSyncSource & NotifyFilterState + // below. + // + CBaseStreamControl(__inout_opt HRESULT *phr = NULL); + ~CBaseStreamControl(); + + // If you want this class to work properly, there are thing you need to + // (keep) telling it. Filters with pins that use this class + // should ensure that they pass through to this method any calls they + // receive on their SetSyncSource. + + // We need a clock to see what time it is. This is for the + // "discard in a timely fashion" logic. If we discard everything as + // quick as possible, a whole 60 minute file could get discarded in the + // first 10 seconds, and if somebody wants to turn streaming on at 30 + // minutes into the file, and they make the call more than a few seconds + // after the graph is run, it may be too late! + // So we hold every sample until it's time has gone, then we discard it. + // The filter should call this when it gets a SetSyncSource + // + void SetSyncSource( IReferenceClock * pRefClock ) + { + CAutoLock lck(&m_CritSec); + if (m_pRefClock) m_pRefClock->Release(); + m_pRefClock = pRefClock; + if (m_pRefClock) m_pRefClock->AddRef(); + } + + // Set event sink for notifications + // The filter should call this in its JoinFilterGraph after it creates the + // IMediaEventSink + // + void SetFilterGraph( IMediaEventSink *pSink ) { + m_pSink = pSink; + } + + // Since we schedule in stream time, we need the tStart and must track the + // state of our owning filter. + // The app should call this ever state change + // + void NotifyFilterState( FILTER_STATE new_state, REFERENCE_TIME tStart = 0 ); + + // Filter should call Flushing(TRUE) in BeginFlush, + // and Flushing(FALSE) in EndFlush. + // + void Flushing( BOOL bInProgress ); + + + // The two main methods of IAMStreamControl + + // Class adds default values suitable for immediate + // muting and unmuting of the stream. + + STDMETHODIMP StopAt( const REFERENCE_TIME * ptStop = NULL, + BOOL bSendExtra = FALSE, + DWORD dwCookie = 0 ); + STDMETHODIMP StartAt( const REFERENCE_TIME * ptStart = NULL, + DWORD dwCookie = 0 ); + STDMETHODIMP GetInfo( __out AM_STREAM_INFO *pInfo); + + // Helper function for pin's receive method. Call this with + // the sample and we'll tell you what to do with it. We'll do a + // WaitForSingleObject within this call if one is required. This is + // a "What should I do with this sample?" kind of call. We'll tell the + // caller to either flow it or discard it. + // If pSample is NULL we evaluate based on the current state + // settings + enum StreamControlState CheckStreamState( IMediaSample * pSample ); + +private: + // These don't require locking, but we are relying on the fact that + // m_StreamState can be retrieved with integrity, and is a snap shot that + // may have just been, or may be just about to be, changed. + HANDLE GetStreamEventHandle() const { return m_StreamEvent; } + enum StreamControlState GetStreamState() const { return m_StreamState; } + BOOL IsStreaming() const { return m_StreamState == STREAM_FLOWING; } +}; + +#endif diff --git a/Src/Plugins/Input/in_dshow/base/sysclock.cpp b/Src/Plugins/Input/in_dshow/base/sysclock.cpp new file mode 100644 index 00000000..aac262ea --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/sysclock.cpp @@ -0,0 +1,74 @@ +//------------------------------------------------------------------------------ +// File: SysClock.cpp +// +// Desc: DirectShow base classes - implements a system clock based on +// IReferenceClock. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> +#include <limits.h> + + +#ifdef FILTER_DLL + +/* List of class IDs and creator functions for the class factory. This + provides the link between the OLE entry point in the DLL and an object + being created. The class factory will call the static CreateInstance + function when it is asked to create a CLSID_SystemClock object */ + +CFactoryTemplate g_Templates[1] = { + {&CLSID_SystemClock, CSystemClock::CreateInstance} +}; + +int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]); +#endif + +/* This goes in the factory template table to create new instances */ +CUnknown * WINAPI CSystemClock::CreateInstance(__inout_opt LPUNKNOWN pUnk, __inout HRESULT *phr) +{ + return new CSystemClock(NAME("System reference clock"),pUnk, phr); +} + + +CSystemClock::CSystemClock(__in_opt LPCTSTR pName, __inout_opt LPUNKNOWN pUnk, __inout HRESULT *phr) : + CBaseReferenceClock(pName, pUnk, phr) +{ +} + +STDMETHODIMP CSystemClock::NonDelegatingQueryInterface( + REFIID riid, + __deref_out void ** ppv) +{ + if (riid == IID_IPersist) + { + return GetInterface(static_cast<IPersist *>(this), ppv); + } + else if (riid == IID_IAMClockAdjust) + { + return GetInterface(static_cast<IAMClockAdjust *>(this), ppv); + } + else + { + return CBaseReferenceClock::NonDelegatingQueryInterface(riid, ppv); + } +} + +/* Return the clock's clsid */ +STDMETHODIMP +CSystemClock::GetClassID(__out CLSID *pClsID) +{ + CheckPointer(pClsID,E_POINTER); + ValidateReadWritePtr(pClsID,sizeof(CLSID)); + *pClsID = CLSID_SystemClock; + return NOERROR; +} + + +STDMETHODIMP +CSystemClock::SetClockDelta(REFERENCE_TIME rtDelta) +{ + return SetTimeDelta(rtDelta); +} diff --git a/Src/Plugins/Input/in_dshow/base/sysclock.h b/Src/Plugins/Input/in_dshow/base/sysclock.h new file mode 100644 index 00000000..bf9192ce --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/sysclock.h @@ -0,0 +1,39 @@ +//------------------------------------------------------------------------------ +// File: SysClock.h +// +// Desc: DirectShow base classes - defines a system clock implementation of +// IReferenceClock. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __SYSTEMCLOCK__ +#define __SYSTEMCLOCK__ + +// +// Base clock. Uses timeGetTime ONLY +// Uses most of the code in the base reference clock. +// Provides GetTime +// + +class CSystemClock : public CBaseReferenceClock, public IAMClockAdjust, public IPersist +{ +public: + // We must be able to create an instance of ourselves + static CUnknown * WINAPI CreateInstance(__inout_opt LPUNKNOWN pUnk, __inout HRESULT *phr); + CSystemClock(__in_opt LPCTSTR pName, __inout_opt LPUNKNOWN pUnk, __inout HRESULT *phr); + + DECLARE_IUNKNOWN + + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void ** ppv); + + // Yield up our class id so that we can be persisted + // Implement required Ipersist method + STDMETHODIMP GetClassID(__out CLSID *pClsID); + + // IAMClockAdjust methods + STDMETHODIMP SetClockDelta(REFERENCE_TIME rtDelta); +}; //CSystemClock + +#endif /* __SYSTEMCLOCK__ */ diff --git a/Src/Plugins/Input/in_dshow/base/transfrm.cpp b/Src/Plugins/Input/in_dshow/base/transfrm.cpp new file mode 100644 index 00000000..4e08c97e --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/transfrm.cpp @@ -0,0 +1,1016 @@ +//------------------------------------------------------------------------------ +// File: Transfrm.cpp +// +// Desc: DirectShow base classes - implements class for simple transform +// filters such as video decompressors. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> +#include <measure.h> + + +// ================================================================= +// Implements the CTransformFilter class +// ================================================================= + +CTransformFilter::CTransformFilter(__in_opt LPCTSTR pName, + __inout_opt LPUNKNOWN pUnk, + REFCLSID clsid) : + CBaseFilter(pName,pUnk,&m_csFilter, clsid), + m_pInput(NULL), + m_pOutput(NULL), + m_bEOSDelivered(FALSE), + m_bQualityChanged(FALSE), + m_bSampleSkipped(FALSE) +{ +#ifdef PERF + RegisterPerfId(); +#endif // PERF +} + +#ifdef UNICODE +CTransformFilter::CTransformFilter(__in_opt LPCSTR pName, + __inout_opt LPUNKNOWN pUnk, + REFCLSID clsid) : + CBaseFilter(pName,pUnk,&m_csFilter, clsid), + m_pInput(NULL), + m_pOutput(NULL), + m_bEOSDelivered(FALSE), + m_bQualityChanged(FALSE), + m_bSampleSkipped(FALSE) +{ +#ifdef PERF + RegisterPerfId(); +#endif // PERF +} +#endif + +// destructor + +CTransformFilter::~CTransformFilter() +{ + // Delete the pins + + delete m_pInput; + delete m_pOutput; +} + + +// Transform place holder - should never be called +HRESULT CTransformFilter::Transform(IMediaSample * pIn, IMediaSample *pOut) +{ + UNREFERENCED_PARAMETER(pIn); + UNREFERENCED_PARAMETER(pOut); + DbgBreak("CTransformFilter::Transform() should never be called"); + return E_UNEXPECTED; +} + + +// return the number of pins we provide + +int CTransformFilter::GetPinCount() +{ + return 2; +} + + +// return a non-addrefed CBasePin * for the user to addref if he holds onto it +// for longer than his pointer to us. We create the pins dynamically when they +// are asked for rather than in the constructor. This is because we want to +// give the derived class an oppportunity to return different pin objects + +// We return the objects as and when they are needed. If either of these fails +// then we return NULL, the assumption being that the caller will realise the +// whole deal is off and destroy us - which in turn will delete everything. + +CBasePin * +CTransformFilter::GetPin(int n) +{ + HRESULT hr = S_OK; + + // Create an input pin if necessary + + if (m_pInput == NULL) { + + m_pInput = new CTransformInputPin(NAME("Transform input pin"), + this, // Owner filter + &hr, // Result code + L"XForm In"); // Pin name + + + // Can't fail + ASSERT(SUCCEEDED(hr)); + if (m_pInput == NULL) { + return NULL; + } + m_pOutput = (CTransformOutputPin *) + new CTransformOutputPin(NAME("Transform output pin"), + this, // Owner filter + &hr, // Result code + L"XForm Out"); // Pin name + + + // Can't fail + ASSERT(SUCCEEDED(hr)); + if (m_pOutput == NULL) { + delete m_pInput; + m_pInput = NULL; + } + } + + // Return the appropriate pin + + if (n == 0) { + return m_pInput; + } else + if (n == 1) { + return m_pOutput; + } else { + return NULL; + } +} + + +// +// FindPin +// +// If Id is In or Out then return the IPin* for that pin +// creating the pin if need be. Otherwise return NULL with an error. + +STDMETHODIMP CTransformFilter::FindPin(LPCWSTR Id, __deref_out IPin **ppPin) +{ + CheckPointer(ppPin,E_POINTER); + ValidateReadWritePtr(ppPin,sizeof(IPin *)); + + if (0==lstrcmpW(Id,L"In")) { + *ppPin = GetPin(0); + } else if (0==lstrcmpW(Id,L"Out")) { + *ppPin = GetPin(1); + } else { + *ppPin = NULL; + return VFW_E_NOT_FOUND; + } + + HRESULT hr = NOERROR; + // AddRef() returned pointer - but GetPin could fail if memory is low. + if (*ppPin) { + (*ppPin)->AddRef(); + } else { + hr = E_OUTOFMEMORY; // probably. There's no pin anyway. + } + return hr; +} + + +// override these two functions if you want to inform something +// about entry to or exit from streaming state. + +HRESULT +CTransformFilter::StartStreaming() +{ + return NOERROR; +} + + +HRESULT +CTransformFilter::StopStreaming() +{ + return NOERROR; +} + + +// override this to grab extra interfaces on connection + +HRESULT +CTransformFilter::CheckConnect(PIN_DIRECTION dir, IPin *pPin) +{ + UNREFERENCED_PARAMETER(dir); + UNREFERENCED_PARAMETER(pPin); + return NOERROR; +} + + +// place holder to allow derived classes to release any extra interfaces + +HRESULT +CTransformFilter::BreakConnect(PIN_DIRECTION dir) +{ + UNREFERENCED_PARAMETER(dir); + return NOERROR; +} + + +// Let derived classes know about connection completion + +HRESULT +CTransformFilter::CompleteConnect(PIN_DIRECTION direction,IPin *pReceivePin) +{ + UNREFERENCED_PARAMETER(direction); + UNREFERENCED_PARAMETER(pReceivePin); + return NOERROR; +} + + +// override this to know when the media type is really set + +HRESULT +CTransformFilter::SetMediaType(PIN_DIRECTION direction,const CMediaType *pmt) +{ + UNREFERENCED_PARAMETER(direction); + UNREFERENCED_PARAMETER(pmt); + return NOERROR; +} + + +// Set up our output sample +HRESULT +CTransformFilter::InitializeOutputSample(IMediaSample *pSample, __deref_out IMediaSample **ppOutSample) +{ + IMediaSample *pOutSample; + + // default - times are the same + + AM_SAMPLE2_PROPERTIES * const pProps = m_pInput->SampleProps(); + DWORD dwFlags = m_bSampleSkipped ? AM_GBF_PREVFRAMESKIPPED : 0; + + // This will prevent the image renderer from switching us to DirectDraw + // when we can't do it without skipping frames because we're not on a + // keyframe. If it really has to switch us, it still will, but then we + // will have to wait for the next keyframe + if (!(pProps->dwSampleFlags & AM_SAMPLE_SPLICEPOINT)) { + dwFlags |= AM_GBF_NOTASYNCPOINT; + } + + ASSERT(m_pOutput->m_pAllocator != NULL); + HRESULT hr = m_pOutput->m_pAllocator->GetBuffer( + &pOutSample + , pProps->dwSampleFlags & AM_SAMPLE_TIMEVALID ? + &pProps->tStart : NULL + , pProps->dwSampleFlags & AM_SAMPLE_STOPVALID ? + &pProps->tStop : NULL + , dwFlags + ); + *ppOutSample = pOutSample; + if (FAILED(hr)) { + return hr; + } + + ASSERT(pOutSample); + IMediaSample2 *pOutSample2; + if (SUCCEEDED(pOutSample->QueryInterface(IID_IMediaSample2, + (void **)&pOutSample2))) { + /* Modify it */ + AM_SAMPLE2_PROPERTIES OutProps; + EXECUTE_ASSERT(SUCCEEDED(pOutSample2->GetProperties( + FIELD_OFFSET(AM_SAMPLE2_PROPERTIES, tStart), (PBYTE)&OutProps) + )); + OutProps.dwTypeSpecificFlags = pProps->dwTypeSpecificFlags; + OutProps.dwSampleFlags = + (OutProps.dwSampleFlags & AM_SAMPLE_TYPECHANGED) | + (pProps->dwSampleFlags & ~AM_SAMPLE_TYPECHANGED); + OutProps.tStart = pProps->tStart; + OutProps.tStop = pProps->tStop; + OutProps.cbData = FIELD_OFFSET(AM_SAMPLE2_PROPERTIES, dwStreamId); + hr = pOutSample2->SetProperties( + FIELD_OFFSET(AM_SAMPLE2_PROPERTIES, dwStreamId), + (PBYTE)&OutProps + ); + if (pProps->dwSampleFlags & AM_SAMPLE_DATADISCONTINUITY) { + m_bSampleSkipped = FALSE; + } + pOutSample2->Release(); + } else { + if (pProps->dwSampleFlags & AM_SAMPLE_TIMEVALID) { + pOutSample->SetTime(&pProps->tStart, + &pProps->tStop); + } + if (pProps->dwSampleFlags & AM_SAMPLE_SPLICEPOINT) { + pOutSample->SetSyncPoint(TRUE); + } + if (pProps->dwSampleFlags & AM_SAMPLE_DATADISCONTINUITY) { + pOutSample->SetDiscontinuity(TRUE); + m_bSampleSkipped = FALSE; + } + // Copy the media times + + LONGLONG MediaStart, MediaEnd; + if (pSample->GetMediaTime(&MediaStart,&MediaEnd) == NOERROR) { + pOutSample->SetMediaTime(&MediaStart,&MediaEnd); + } + } + return S_OK; +} + +// override this to customize the transform process + +HRESULT +CTransformFilter::Receive(IMediaSample *pSample) +{ + /* Check for other streams and pass them on */ + AM_SAMPLE2_PROPERTIES * const pProps = m_pInput->SampleProps(); + if (pProps->dwStreamId != AM_STREAM_MEDIA) { + return m_pOutput->m_pInputPin->Receive(pSample); + } + HRESULT hr; + ASSERT(pSample); + IMediaSample * pOutSample; + + // If no output to deliver to then no point sending us data + + ASSERT (m_pOutput != NULL) ; + + // Set up the output sample + hr = InitializeOutputSample(pSample, &pOutSample); + + if (FAILED(hr)) { + return hr; + } + + // Start timing the transform (if PERF is defined) + MSR_START(m_idTransform); + + // have the derived class transform the data + + hr = Transform(pSample, pOutSample); + + // Stop the clock and log it (if PERF is defined) + MSR_STOP(m_idTransform); + + if (FAILED(hr)) { + DbgLog((LOG_TRACE,1,TEXT("Error from transform"))); + } else { + // the Transform() function can return S_FALSE to indicate that the + // sample should not be delivered; we only deliver the sample if it's + // really S_OK (same as NOERROR, of course.) + if (hr == NOERROR) { + hr = m_pOutput->m_pInputPin->Receive(pOutSample); + m_bSampleSkipped = FALSE; // last thing no longer dropped + } else { + // S_FALSE returned from Transform is a PRIVATE agreement + // We should return NOERROR from Receive() in this cause because returning S_FALSE + // from Receive() means that this is the end of the stream and no more data should + // be sent. + if (S_FALSE == hr) { + + // Release the sample before calling notify to avoid + // deadlocks if the sample holds a lock on the system + // such as DirectDraw buffers do + pOutSample->Release(); + m_bSampleSkipped = TRUE; + if (!m_bQualityChanged) { + NotifyEvent(EC_QUALITY_CHANGE,0,0); + m_bQualityChanged = TRUE; + } + return NOERROR; + } + } + } + + // release the output buffer. If the connected pin still needs it, + // it will have addrefed it itself. + pOutSample->Release(); + + return hr; +} + + +// Return S_FALSE to mean "pass the note on upstream" +// Return NOERROR (Same as S_OK) +// to mean "I've done something about it, don't pass it on" +HRESULT CTransformFilter::AlterQuality(Quality q) +{ + UNREFERENCED_PARAMETER(q); + return S_FALSE; +} + + +// EndOfStream received. Default behaviour is to deliver straight +// downstream, since we have no queued data. If you overrode Receive +// and have queue data, then you need to handle this and deliver EOS after +// all queued data is sent +HRESULT +CTransformFilter::EndOfStream(void) +{ + HRESULT hr = NOERROR; + if (m_pOutput != NULL) { + hr = m_pOutput->DeliverEndOfStream(); + } + + return hr; +} + + +// enter flush state. Receives already blocked +// must override this if you have queued data or a worker thread +HRESULT +CTransformFilter::BeginFlush(void) +{ + HRESULT hr = NOERROR; + if (m_pOutput != NULL) { + // block receives -- done by caller (CBaseInputPin::BeginFlush) + + // discard queued data -- we have no queued data + + // free anyone blocked on receive - not possible in this filter + + // call downstream + hr = m_pOutput->DeliverBeginFlush(); + } + return hr; +} + + +// leave flush state. must override this if you have queued data +// or a worker thread +HRESULT +CTransformFilter::EndFlush(void) +{ + // sync with pushing thread -- we have no worker thread + + // ensure no more data to go downstream -- we have no queued data + + // call EndFlush on downstream pins + ASSERT (m_pOutput != NULL); + return m_pOutput->DeliverEndFlush(); + + // caller (the input pin's method) will unblock Receives +} + + +// override these so that the derived filter can catch them + +STDMETHODIMP +CTransformFilter::Stop() +{ + CAutoLock lck1(&m_csFilter); + if (m_State == State_Stopped) { + return NOERROR; + } + + // Succeed the Stop if we are not completely connected + + ASSERT(m_pInput == NULL || m_pOutput != NULL); + if (m_pInput == NULL || m_pInput->IsConnected() == FALSE || + m_pOutput->IsConnected() == FALSE) { + m_State = State_Stopped; + m_bEOSDelivered = FALSE; + return NOERROR; + } + + ASSERT(m_pInput); + ASSERT(m_pOutput); + + // decommit the input pin before locking or we can deadlock + m_pInput->Inactive(); + + // synchronize with Receive calls + + CAutoLock lck2(&m_csReceive); + m_pOutput->Inactive(); + + // allow a class derived from CTransformFilter + // to know about starting and stopping streaming + + HRESULT hr = StopStreaming(); + if (SUCCEEDED(hr)) { + // complete the state transition + m_State = State_Stopped; + m_bEOSDelivered = FALSE; + } + return hr; +} + + +STDMETHODIMP +CTransformFilter::Pause() +{ + CAutoLock lck(&m_csFilter); + HRESULT hr = NOERROR; + + if (m_State == State_Paused) { + // (This space left deliberately blank) + } + + // If we have no input pin or it isn't yet connected then when we are + // asked to pause we deliver an end of stream to the downstream filter. + // This makes sure that it doesn't sit there forever waiting for + // samples which we cannot ever deliver without an input connection. + + else if (m_pInput == NULL || m_pInput->IsConnected() == FALSE) { + if (m_pOutput && m_bEOSDelivered == FALSE) { + m_pOutput->DeliverEndOfStream(); + m_bEOSDelivered = TRUE; + } + m_State = State_Paused; + } + + // We may have an input connection but no output connection + // However, if we have an input pin we do have an output pin + + else if (m_pOutput->IsConnected() == FALSE) { + m_State = State_Paused; + } + + else { + if (m_State == State_Stopped) { + // allow a class derived from CTransformFilter + // to know about starting and stopping streaming + CAutoLock lck2(&m_csReceive); + hr = StartStreaming(); + } + if (SUCCEEDED(hr)) { + hr = CBaseFilter::Pause(); + } + } + + m_bSampleSkipped = FALSE; + m_bQualityChanged = FALSE; + return hr; +} + +HRESULT +CTransformFilter::NewSegment( + REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate) +{ + if (m_pOutput != NULL) { + return m_pOutput->DeliverNewSegment(tStart, tStop, dRate); + } + return S_OK; +} + +// Check streaming status +HRESULT +CTransformInputPin::CheckStreaming() +{ + ASSERT(m_pTransformFilter->m_pOutput != NULL); + if (!m_pTransformFilter->m_pOutput->IsConnected()) { + return VFW_E_NOT_CONNECTED; + } else { + // Shouldn't be able to get any data if we're not connected! + ASSERT(IsConnected()); + + // we're flushing + if (m_bFlushing) { + return S_FALSE; + } + // Don't process stuff in Stopped state + if (IsStopped()) { + return VFW_E_WRONG_STATE; + } + if (m_bRunTimeError) { + return VFW_E_RUNTIME_ERROR; + } + return S_OK; + } +} + + +// ================================================================= +// Implements the CTransformInputPin class +// ================================================================= + + +// constructor + +CTransformInputPin::CTransformInputPin( + __in_opt LPCTSTR pObjectName, + __inout CTransformFilter *pTransformFilter, + __inout HRESULT * phr, + __in_opt LPCWSTR pName) + : CBaseInputPin(pObjectName, pTransformFilter, &pTransformFilter->m_csFilter, phr, pName) +{ + DbgLog((LOG_TRACE,2,TEXT("CTransformInputPin::CTransformInputPin"))); + m_pTransformFilter = pTransformFilter; +} + +#ifdef UNICODE +CTransformInputPin::CTransformInputPin( + __in_opt LPCSTR pObjectName, + __inout CTransformFilter *pTransformFilter, + __inout HRESULT * phr, + __in_opt LPCWSTR pName) + : CBaseInputPin(pObjectName, pTransformFilter, &pTransformFilter->m_csFilter, phr, pName) +{ + DbgLog((LOG_TRACE,2,TEXT("CTransformInputPin::CTransformInputPin"))); + m_pTransformFilter = pTransformFilter; +} +#endif + +// provides derived filter a chance to grab extra interfaces + +HRESULT +CTransformInputPin::CheckConnect(IPin *pPin) +{ + HRESULT hr = m_pTransformFilter->CheckConnect(PINDIR_INPUT,pPin); + if (FAILED(hr)) { + return hr; + } + return CBaseInputPin::CheckConnect(pPin); +} + + +// provides derived filter a chance to release it's extra interfaces + +HRESULT +CTransformInputPin::BreakConnect() +{ + // Can't disconnect unless stopped + ASSERT(IsStopped()); + m_pTransformFilter->BreakConnect(PINDIR_INPUT); + return CBaseInputPin::BreakConnect(); +} + + +// Let derived class know when the input pin is connected + +HRESULT +CTransformInputPin::CompleteConnect(IPin *pReceivePin) +{ + HRESULT hr = m_pTransformFilter->CompleteConnect(PINDIR_INPUT,pReceivePin); + if (FAILED(hr)) { + return hr; + } + return CBaseInputPin::CompleteConnect(pReceivePin); +} + + +// check that we can support a given media type + +HRESULT +CTransformInputPin::CheckMediaType(const CMediaType* pmt) +{ + // Check the input type + + HRESULT hr = m_pTransformFilter->CheckInputType(pmt); + if (S_OK != hr) { + return hr; + } + + // if the output pin is still connected, then we have + // to check the transform not just the input format + + if ((m_pTransformFilter->m_pOutput != NULL) && + (m_pTransformFilter->m_pOutput->IsConnected())) { + return m_pTransformFilter->CheckTransform( + pmt, + &m_pTransformFilter->m_pOutput->CurrentMediaType()); + } else { + return hr; + } +} + + +// set the media type for this connection + +HRESULT +CTransformInputPin::SetMediaType(const CMediaType* mtIn) +{ + // Set the base class media type (should always succeed) + HRESULT hr = CBasePin::SetMediaType(mtIn); + if (FAILED(hr)) { + return hr; + } + + // check the transform can be done (should always succeed) + ASSERT(SUCCEEDED(m_pTransformFilter->CheckInputType(mtIn))); + + return m_pTransformFilter->SetMediaType(PINDIR_INPUT,mtIn); +} + + +// ================================================================= +// Implements IMemInputPin interface +// ================================================================= + + +// provide EndOfStream that passes straight downstream +// (there is no queued data) +STDMETHODIMP +CTransformInputPin::EndOfStream(void) +{ + CAutoLock lck(&m_pTransformFilter->m_csReceive); + HRESULT hr = CheckStreaming(); + if (S_OK == hr) { + hr = m_pTransformFilter->EndOfStream(); + } + return hr; +} + + +// enter flushing state. Call default handler to block Receives, then +// pass to overridable method in filter +STDMETHODIMP +CTransformInputPin::BeginFlush(void) +{ + CAutoLock lck(&m_pTransformFilter->m_csFilter); + // Are we actually doing anything? + ASSERT(m_pTransformFilter->m_pOutput != NULL); + if (!IsConnected() || + !m_pTransformFilter->m_pOutput->IsConnected()) { + return VFW_E_NOT_CONNECTED; + } + HRESULT hr = CBaseInputPin::BeginFlush(); + if (FAILED(hr)) { + return hr; + } + + return m_pTransformFilter->BeginFlush(); +} + + +// leave flushing state. +// Pass to overridable method in filter, then call base class +// to unblock receives (finally) +STDMETHODIMP +CTransformInputPin::EndFlush(void) +{ + CAutoLock lck(&m_pTransformFilter->m_csFilter); + // Are we actually doing anything? + ASSERT(m_pTransformFilter->m_pOutput != NULL); + if (!IsConnected() || + !m_pTransformFilter->m_pOutput->IsConnected()) { + return VFW_E_NOT_CONNECTED; + } + + HRESULT hr = m_pTransformFilter->EndFlush(); + if (FAILED(hr)) { + return hr; + } + + return CBaseInputPin::EndFlush(); +} + + +// here's the next block of data from the stream. +// AddRef it yourself if you need to hold it beyond the end +// of this call. + +HRESULT +CTransformInputPin::Receive(IMediaSample * pSample) +{ + HRESULT hr; + CAutoLock lck(&m_pTransformFilter->m_csReceive); + ASSERT(pSample); + + // check all is well with the base class + hr = CBaseInputPin::Receive(pSample); + if (S_OK == hr) { + hr = m_pTransformFilter->Receive(pSample); + } + return hr; +} + + + + +// override to pass downstream +STDMETHODIMP +CTransformInputPin::NewSegment( + REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate) +{ + // Save the values in the pin + CBasePin::NewSegment(tStart, tStop, dRate); + return m_pTransformFilter->NewSegment(tStart, tStop, dRate); +} + + + + +// ================================================================= +// Implements the CTransformOutputPin class +// ================================================================= + + +// constructor + +CTransformOutputPin::CTransformOutputPin( + __in_opt LPCTSTR pObjectName, + __inout CTransformFilter *pTransformFilter, + __inout HRESULT * phr, + __in_opt LPCWSTR pPinName) + : CBaseOutputPin(pObjectName, pTransformFilter, &pTransformFilter->m_csFilter, phr, pPinName), + m_pPosition(NULL) +{ + DbgLog((LOG_TRACE,2,TEXT("CTransformOutputPin::CTransformOutputPin"))); + m_pTransformFilter = pTransformFilter; + +} + +#ifdef UNICODE +CTransformOutputPin::CTransformOutputPin( + __in_opt LPCSTR pObjectName, + __inout CTransformFilter *pTransformFilter, + __inout HRESULT * phr, + __in_opt LPCWSTR pPinName) + : CBaseOutputPin(pObjectName, pTransformFilter, &pTransformFilter->m_csFilter, phr, pPinName), + m_pPosition(NULL) +{ + DbgLog((LOG_TRACE,2,TEXT("CTransformOutputPin::CTransformOutputPin"))); + m_pTransformFilter = pTransformFilter; + +} +#endif + +// destructor + +CTransformOutputPin::~CTransformOutputPin() +{ + DbgLog((LOG_TRACE,2,TEXT("CTransformOutputPin::~CTransformOutputPin"))); + + if (m_pPosition) m_pPosition->Release(); +} + + +// overriden to expose IMediaPosition and IMediaSeeking control interfaces + +STDMETHODIMP +CTransformOutputPin::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + CheckPointer(ppv,E_POINTER); + ValidateReadWritePtr(ppv,sizeof(PVOID)); + *ppv = NULL; + + if (riid == IID_IMediaPosition || riid == IID_IMediaSeeking) { + + // we should have an input pin by now + + ASSERT(m_pTransformFilter->m_pInput != NULL); + + if (m_pPosition == NULL) { + + HRESULT hr = CreatePosPassThru( + GetOwner(), + FALSE, + (IPin *)m_pTransformFilter->m_pInput, + &m_pPosition); + if (FAILED(hr)) { + return hr; + } + } + return m_pPosition->QueryInterface(riid, ppv); + } else { + return CBaseOutputPin::NonDelegatingQueryInterface(riid, ppv); + } +} + + +// provides derived filter a chance to grab extra interfaces + +HRESULT +CTransformOutputPin::CheckConnect(IPin *pPin) +{ + // we should have an input connection first + + ASSERT(m_pTransformFilter->m_pInput != NULL); + if ((m_pTransformFilter->m_pInput->IsConnected() == FALSE)) { + return E_UNEXPECTED; + } + + HRESULT hr = m_pTransformFilter->CheckConnect(PINDIR_OUTPUT,pPin); + if (FAILED(hr)) { + return hr; + } + return CBaseOutputPin::CheckConnect(pPin); +} + + +// provides derived filter a chance to release it's extra interfaces + +HRESULT +CTransformOutputPin::BreakConnect() +{ + // Can't disconnect unless stopped + ASSERT(IsStopped()); + m_pTransformFilter->BreakConnect(PINDIR_OUTPUT); + return CBaseOutputPin::BreakConnect(); +} + + +// Let derived class know when the output pin is connected + +HRESULT +CTransformOutputPin::CompleteConnect(IPin *pReceivePin) +{ + HRESULT hr = m_pTransformFilter->CompleteConnect(PINDIR_OUTPUT,pReceivePin); + if (FAILED(hr)) { + return hr; + } + return CBaseOutputPin::CompleteConnect(pReceivePin); +} + + +// check a given transform - must have selected input type first + +HRESULT +CTransformOutputPin::CheckMediaType(const CMediaType* pmtOut) +{ + // must have selected input first + ASSERT(m_pTransformFilter->m_pInput != NULL); + if ((m_pTransformFilter->m_pInput->IsConnected() == FALSE)) { + return E_INVALIDARG; + } + + return m_pTransformFilter->CheckTransform( + &m_pTransformFilter->m_pInput->CurrentMediaType(), + pmtOut); +} + + +// called after we have agreed a media type to actually set it in which case +// we run the CheckTransform function to get the output format type again + +HRESULT +CTransformOutputPin::SetMediaType(const CMediaType* pmtOut) +{ + HRESULT hr = NOERROR; + ASSERT(m_pTransformFilter->m_pInput != NULL); + + ASSERT(m_pTransformFilter->m_pInput->CurrentMediaType().IsValid()); + + // Set the base class media type (should always succeed) + hr = CBasePin::SetMediaType(pmtOut); + if (FAILED(hr)) { + return hr; + } + +#ifdef DEBUG + if (FAILED(m_pTransformFilter->CheckTransform(&m_pTransformFilter-> + m_pInput->CurrentMediaType(),pmtOut))) { + DbgLog((LOG_ERROR,0,TEXT("*** This filter is accepting an output media type"))); + DbgLog((LOG_ERROR,0,TEXT(" that it can't currently transform to. I hope"))); + DbgLog((LOG_ERROR,0,TEXT(" it's smart enough to reconnect its input."))); + } +#endif + + return m_pTransformFilter->SetMediaType(PINDIR_OUTPUT,pmtOut); +} + + +// pass the buffer size decision through to the main transform class + +HRESULT +CTransformOutputPin::DecideBufferSize( + IMemAllocator * pAllocator, + __inout ALLOCATOR_PROPERTIES* pProp) +{ + return m_pTransformFilter->DecideBufferSize(pAllocator, pProp); +} + + + +// return a specific media type indexed by iPosition + +HRESULT +CTransformOutputPin::GetMediaType( + int iPosition, + __inout CMediaType *pMediaType) +{ + ASSERT(m_pTransformFilter->m_pInput != NULL); + + // We don't have any media types if our input is not connected + + if (m_pTransformFilter->m_pInput->IsConnected()) { + return m_pTransformFilter->GetMediaType(iPosition,pMediaType); + } else { + return VFW_S_NO_MORE_ITEMS; + } +} + + +// Override this if you can do something constructive to act on the +// quality message. Consider passing it upstream as well + +// Pass the quality mesage on upstream. + +STDMETHODIMP +CTransformOutputPin::Notify(IBaseFilter * pSender, Quality q) +{ + UNREFERENCED_PARAMETER(pSender); + ValidateReadPtr(pSender,sizeof(IBaseFilter)); + + // First see if we want to handle this ourselves + HRESULT hr = m_pTransformFilter->AlterQuality(q); + if (hr!=S_FALSE) { + return hr; // either S_OK or a failure + } + + // S_FALSE means we pass the message on. + // Find the quality sink for our input pin and send it there + + ASSERT(m_pTransformFilter->m_pInput != NULL); + + return m_pTransformFilter->m_pInput->PassNotify(q); + +} // Notify + + +// the following removes a very large number of level 4 warnings from the microsoft +// compiler output, which are not useful at all in this case. +#pragma warning(disable:4514) diff --git a/Src/Plugins/Input/in_dshow/base/transfrm.h b/Src/Plugins/Input/in_dshow/base/transfrm.h new file mode 100644 index 00000000..36c2e0d1 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/transfrm.h @@ -0,0 +1,304 @@ +//------------------------------------------------------------------------------ +// File: Transfrm.h +// +// Desc: DirectShow base classes - defines classes from which simple +// transform codecs may be derived. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// It assumes the codec has one input and one output stream, and has no +// interest in memory management, interface negotiation or anything else. +// +// derive your class from this, and supply Transform and the media type/format +// negotiation functions. Implement that class, compile and link and +// you're done. + + +#ifndef __TRANSFRM__ +#define __TRANSFRM__ + +// ====================================================================== +// This is the com object that represents a simple transform filter. It +// supports IBaseFilter, IMediaFilter and two pins through nested interfaces +// ====================================================================== + +class CTransformFilter; + +// ================================================== +// Implements the input pin +// ================================================== + +class CTransformInputPin : public CBaseInputPin +{ + friend class CTransformFilter; + +protected: + CTransformFilter *m_pTransformFilter; + + +public: + + CTransformInputPin( + __in_opt LPCTSTR pObjectName, + __inout CTransformFilter *pTransformFilter, + __inout HRESULT * phr, + __in_opt LPCWSTR pName); +#ifdef UNICODE + CTransformInputPin( + __in_opt LPCSTR pObjectName, + __inout CTransformFilter *pTransformFilter, + __inout HRESULT * phr, + __in_opt LPCWSTR pName); +#endif + + STDMETHODIMP QueryId(__deref_out LPWSTR * Id) + { + return AMGetWideString(L"In", Id); + } + + // Grab and release extra interfaces if required + + HRESULT CheckConnect(IPin *pPin); + HRESULT BreakConnect(); + HRESULT CompleteConnect(IPin *pReceivePin); + + // check that we can support this output type + HRESULT CheckMediaType(const CMediaType* mtIn); + + // set the connection media type + HRESULT SetMediaType(const CMediaType* mt); + + // --- IMemInputPin ----- + + // here's the next block of data from the stream. + // AddRef it yourself if you need to hold it beyond the end + // of this call. + STDMETHODIMP Receive(IMediaSample * pSample); + + // provide EndOfStream that passes straight downstream + // (there is no queued data) + STDMETHODIMP EndOfStream(void); + + // passes it to CTransformFilter::BeginFlush + STDMETHODIMP BeginFlush(void); + + // passes it to CTransformFilter::EndFlush + STDMETHODIMP EndFlush(void); + + STDMETHODIMP NewSegment( + REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate); + + // Check if it's OK to process samples + virtual HRESULT CheckStreaming(); + + // Media type +public: + CMediaType& CurrentMediaType() { return m_mt; }; + +}; + +// ================================================== +// Implements the output pin +// ================================================== + +class CTransformOutputPin : public CBaseOutputPin +{ + friend class CTransformFilter; + +protected: + CTransformFilter *m_pTransformFilter; + +public: + + // implement IMediaPosition by passing upstream + IUnknown * m_pPosition; + + CTransformOutputPin( + __in_opt LPCTSTR pObjectName, + __inout CTransformFilter *pTransformFilter, + __inout HRESULT * phr, + __in_opt LPCWSTR pName); +#ifdef UNICODE + CTransformOutputPin( + __in_opt LPCSTR pObjectName, + __inout CTransformFilter *pTransformFilter, + __inout HRESULT * phr, + __in_opt LPCWSTR pName); +#endif + ~CTransformOutputPin(); + + // override to expose IMediaPosition + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv); + + // --- CBaseOutputPin ------------ + + STDMETHODIMP QueryId(__deref_out LPWSTR * Id) + { + return AMGetWideString(L"Out", Id); + } + + // Grab and release extra interfaces if required + + HRESULT CheckConnect(IPin *pPin); + HRESULT BreakConnect(); + HRESULT CompleteConnect(IPin *pReceivePin); + + // check that we can support this output type + HRESULT CheckMediaType(const CMediaType* mtOut); + + // set the connection media type + HRESULT SetMediaType(const CMediaType *pmt); + + // called from CBaseOutputPin during connection to ask for + // the count and size of buffers we need. + HRESULT DecideBufferSize( + IMemAllocator * pAlloc, + __inout ALLOCATOR_PROPERTIES *pProp); + + // returns the preferred formats for a pin + HRESULT GetMediaType(int iPosition, __inout CMediaType *pMediaType); + + // inherited from IQualityControl via CBasePin + STDMETHODIMP Notify(IBaseFilter * pSender, Quality q); + + // Media type +public: + CMediaType& CurrentMediaType() { return m_mt; }; +}; + + +class AM_NOVTABLE CTransformFilter : public CBaseFilter +{ + +public: + + // map getpin/getpincount for base enum of pins to owner + // override this to return more specialised pin objects + + virtual int GetPinCount(); + virtual CBasePin * GetPin(int n); + STDMETHODIMP FindPin(LPCWSTR Id, __deref_out IPin **ppPin); + + // override state changes to allow derived transform filter + // to control streaming start/stop + STDMETHODIMP Stop(); + STDMETHODIMP Pause(); + +public: + + CTransformFilter(__in_opt LPCTSTR , __inout_opt LPUNKNOWN, REFCLSID clsid); +#ifdef UNICODE + CTransformFilter(__in_opt LPCSTR , __inout_opt LPUNKNOWN, REFCLSID clsid); +#endif + ~CTransformFilter(); + + // ================================================================= + // ----- override these bits --------------------------------------- + // ================================================================= + + // These must be supplied in a derived class + + virtual HRESULT Transform(IMediaSample * pIn, IMediaSample *pOut); + + // check if you can support mtIn + virtual HRESULT CheckInputType(const CMediaType* mtIn) PURE; + + // check if you can support the transform from this input to this output + virtual HRESULT CheckTransform(const CMediaType* mtIn, const CMediaType* mtOut) PURE; + + // this goes in the factory template table to create new instances + // static CCOMObject * CreateInstance(__inout_opt LPUNKNOWN, HRESULT *); + + // call the SetProperties function with appropriate arguments + virtual HRESULT DecideBufferSize( + IMemAllocator * pAllocator, + __inout ALLOCATOR_PROPERTIES *pprop) PURE; + + // override to suggest OUTPUT pin media types + virtual HRESULT GetMediaType(int iPosition, __inout CMediaType *pMediaType) PURE; + + + + // ================================================================= + // ----- Optional Override Methods ----------------------- + // ================================================================= + + // you can also override these if you want to know about streaming + virtual HRESULT StartStreaming(); + virtual HRESULT StopStreaming(); + + // override if you can do anything constructive with quality notifications + virtual HRESULT AlterQuality(Quality q); + + // override this to know when the media type is actually set + virtual HRESULT SetMediaType(PIN_DIRECTION direction,const CMediaType *pmt); + + // chance to grab extra interfaces on connection + virtual HRESULT CheckConnect(PIN_DIRECTION dir,IPin *pPin); + virtual HRESULT BreakConnect(PIN_DIRECTION dir); + virtual HRESULT CompleteConnect(PIN_DIRECTION direction,IPin *pReceivePin); + + // chance to customize the transform process + virtual HRESULT Receive(IMediaSample *pSample); + + // Standard setup for output sample + HRESULT InitializeOutputSample(IMediaSample *pSample, __deref_out IMediaSample **ppOutSample); + + // if you override Receive, you may need to override these three too + virtual HRESULT EndOfStream(void); + virtual HRESULT BeginFlush(void); + virtual HRESULT EndFlush(void); + virtual HRESULT NewSegment( + REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate); + +#ifdef PERF + // Override to register performance measurement with a less generic string + // You should do this to avoid confusion with other filters + virtual void RegisterPerfId() + {m_idTransform = MSR_REGISTER(TEXT("Transform"));} +#endif // PERF + + +// implementation details + +protected: + +#ifdef PERF + int m_idTransform; // performance measuring id +#endif + BOOL m_bEOSDelivered; // have we sent EndOfStream + BOOL m_bSampleSkipped; // Did we just skip a frame + BOOL m_bQualityChanged; // Have we degraded? + + // critical section protecting filter state. + + CCritSec m_csFilter; + + // critical section stopping state changes (ie Stop) while we're + // processing a sample. + // + // This critical section is held when processing + // events that occur on the receive thread - Receive() and EndOfStream(). + // + // If you want to hold both m_csReceive and m_csFilter then grab + // m_csFilter FIRST - like CTransformFilter::Stop() does. + + CCritSec m_csReceive; + + // these hold our input and output pins + + friend class CTransformInputPin; + friend class CTransformOutputPin; + CTransformInputPin *m_pInput; + CTransformOutputPin *m_pOutput; +}; + +#endif /* __TRANSFRM__ */ + + diff --git a/Src/Plugins/Input/in_dshow/base/transip.cpp b/Src/Plugins/Input/in_dshow/base/transip.cpp new file mode 100644 index 00000000..89890255 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/transip.cpp @@ -0,0 +1,974 @@ +//------------------------------------------------------------------------------ +// File: TransIP.cpp +// +// Desc: DirectShow base classes - implements class for simple Transform- +// In-Place filters such as audio. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// How allocators are decided. +// +// An in-place transform tries to do its work in someone else's buffers. +// It tries to persuade the filters on either side to use the same allocator +// (and for that matter the same media type). In desperation, if the downstream +// filter refuses to supply an allocator and the upstream filter offers only +// a read-only one then it will provide an allocator. +// if the upstream filter insists on a read-only allocator then the transform +// filter will (reluctantly) copy the data before transforming it. +// +// In order to pass an allocator through it needs to remember the one it got +// from the first connection to pass it on to the second one. +// +// It is good if we can avoid insisting on a particular order of connection +// (There is a precedent for insisting on the input +// being connected first. Insisting on the output being connected first is +// not allowed. That would break RenderFile.) +// +// The base pin classes (CBaseOutputPin and CBaseInputPin) both have a +// m_pAllocator member which is used in places like +// CBaseOutputPin::GetDeliveryBuffer and CBaseInputPin::Inactive. +// To avoid lots of extra overriding, we should keep these happy +// by using these pointers. +// +// When each pin is connected, it will set the corresponding m_pAllocator +// and will have a single ref-count on that allocator. +// +// Refcounts are acquired by GetAllocator calls which return AddReffed +// allocators and are released in one of: +// CBaseInputPin::Disconnect +// CBaseOutputPin::BreakConect +// In each case m_pAllocator is set to NULL after the release, so this +// is the last chance to ever release it. If there should ever be +// multiple refcounts associated with the same pointer, this had better +// be cleared up before that happens. To avoid such problems, we'll +// stick with one per pointer. + + + +// RECONNECTING and STATE CHANGES +// +// Each pin could be disconnected, connected with a read-only allocator, +// connected with an upstream read/write allocator, connected with an +// allocator from downstream or connected with its own allocator. +// Five states for each pin gives a data space of 25 states. +// +// Notation: +// +// R/W == read/write +// R-O == read-only +// +// <input pin state> <output pin state> <comments> +// +// 00 means an unconnected pin. +// <- means using a R/W allocator from the upstream filter +// <= means using a R-O allocator from an upstream filter +// || means using our own (R/W) allocator. +// -> means using a R/W allocator from a downstream filter +// (a R-O allocator from downstream is nonsense, it can't ever work). +// +// +// That makes 25 possible states. Some states are nonsense (two different +// allocators from the same place). These are just an artifact of the notation. +// <= <- Nonsense. +// <- <= Nonsense +// Some states are illegal (the output pin never accepts a R-O allocator): +// 00 <= !! Error !! +// <= <= !! Error !! +// || <= !! Error !! +// -> <= !! Error !! +// Three states appears to be inaccessible: +// -> || Inaccessible +// || -> Inaccessible +// || <- Inaccessible +// Some states only ever occur as intermediates with a pending reconnect which +// is guaranteed to finish in another state. +// -> 00 ?? unstable goes to || 00 +// 00 <- ?? unstable goes to 00 || +// -> <- ?? unstable goes to -> -> +// <- || ?? unstable goes to <- <- +// <- -> ?? unstable goes to <- <- +// And that leaves 11 possible resting states: +// 1 00 00 Nothing connected. +// 2 <- 00 Input pin connected. +// 3 <= 00 Input pin connected using R-O allocator. +// 4 || 00 Needs several state changes to get here. +// 5 00 || Output pin connected using our allocator +// 6 00 -> Downstream only connected +// 7 || || Undesirable but can be forced upon us. +// 8 <= || Copy forced. <= -> is preferable +// 9 <= -> OK - forced to copy. +// 10 <- <- Transform in place (ideal) +// 11 -> -> Transform in place (ideal) +// +// The object of the exercise is to ensure that we finish up in states +// 10 or 11 whenever possible. State 10 is only possible if the upstream +// filter has a R/W allocator (the AVI splitter notoriously +// doesn't) and state 11 is only possible if the downstream filter does +// offer an allocator. +// +// The transition table (entries marked * go via a reconnect) +// +// There are 8 possible transitions: +// A: Connect upstream to filter with R-O allocator that insists on using it. +// B: Connect upstream to filter with R-O allocator but chooses not to use it. +// C: Connect upstream to filter with R/W allocator and insists on using it. +// D: Connect upstream to filter with R/W allocator but chooses not to use it. +// E: Connect downstream to a filter that offers an allocator +// F: Connect downstream to a filter that does not offer an allocator +// G: disconnect upstream +// H: Disconnect downstream +// +// A B C D E F G H +// --------------------------------------------------------- +// 00 00 1 | 3 3 2 2 6 5 . . |1 00 00 +// <- 00 2 | . . . . *10/11 10 1 . |2 <- 00 +// <= 00 3 | . . . . *9/11 *7/8 1 . |3 <= 00 +// || 00 4 | . . . . *8 *7 1 . |4 || 00 +// 00 || 5 | 8 7 *10 7 . . . 1 |5 00 || +// 00 -> 6 | 9 11 *10 11 . . . 1 |6 00 -> +// || || 7 | . . . . . . 5 4 |7 || || +// <= || 8 | . . . . . . 5 3 |8 <= || +// <= -> 9 | . . . . . . 6 3 |9 <= -> +// <- <- 10| . . . . . . *5/6 2 |10 <- <- +// -> -> 11| . . . . . . 6 *2/3 |11 -> -> +// --------------------------------------------------------- +// A B C D E F G H +// +// All these states are accessible without requiring any filter to +// change its behaviour but not all transitions are accessible, for +// instance a transition from state 4 to anywhere other than +// state 8 requires that the upstream filter first offer a R-O allocator +// and then changes its mind and offer R/W. This is NOT allowable - it +// leads to things like the output pin getting a R/W allocator from +// upstream and then the input pin being told it can only have a R-O one. +// Note that you CAN change (say) the upstream filter for a different one, but +// only as a disconnect / connect, not as a Reconnect. (Exercise for +// the reader is to see how you get into state 4). +// +// The reconnection stuff goes as follows (some of the cases shown here as +// "no reconnect" may get one to finalise media type - an old story). +// If there is a reconnect where it says "no reconnect" here then the +// reconnection must not change the allocator choice. +// +// state 2: <- 00 transition E <- <- case C <- <- (no change) +// case D -> <- and then to -> -> +// +// state 2: <- 00 transition F <- <- (no reconnect) +// +// state 3: <= 00 transition E <= -> case A <= -> (no change) +// case B -> -> +// transition F <= || case A <= || (no change) +// case B || || +// +// state 4: || 00 transition E || || case B -> || and then all cases to -> -> +// F || || case B || || (no change) +// +// state 5: 00 || transition A <= || (no reconnect) +// B || || (no reconnect) +// C <- || all cases <- <- +// D || || (unfortunate, but upstream's choice) +// +// state 6: 00 -> transition A <= -> (no reconnect) +// B -> -> (no reconnect) +// C <- -> all cases <- <- +// D -> -> (no reconnect) +// +// state 10:<- <- transition G 00 <- case E 00 -> +// case F 00 || +// +// state 11:-> -> transition H -> 00 case A <= 00 (schizo) +// case B <= 00 +// case C <- 00 (schizo) +// case D <- 00 +// +// The Rules: +// To sort out media types: +// The input is reconnected +// if the input pin is connected and the output pin connects +// The output is reconnected +// If the output pin is connected +// and the input pin connects to a different media type +// +// To sort out allocators: +// The input is reconnected +// if the output disconnects and the input was using a downstream allocator +// The output pin calls SetAllocator to pass on a new allocator +// if the output is connected and +// if the input disconnects and the output was using an upstream allocator +// if the input acquires an allocator different from the output one +// and that new allocator is not R-O +// +// Data is copied (i.e. call getbuffer and copy the data before transforming it) +// if the two allocators are different. + + + +// CHAINS of filters: +// +// We sit between two filters (call them A and Z). We should finish up +// with the same allocator on both of our pins and that should be the +// same one that A and Z would have agreed on if we hadn't been in the +// way. Furthermore, it should not matter how many in-place transforms +// are in the way. Let B, C, D... be in-place transforms ("us"). +// Here's how it goes: +// +// 1. +// A connects to B. They agree on A's allocator. +// A-a->B +// +// 2. +// B connects to C. Same story. There is no point in a reconnect, but +// B will request an input reconnect anyway. +// A-a->B-a->C +// +// 3. +// C connects to Z. +// C insists on using A's allocator, but compromises by requesting a reconnect. +// of C's input. +// A-a->B-?->C-a->Z +// +// We now have pending reconnects on both A--->B and B--->C +// +// 4. +// The A--->B link is reconnected. +// A asks B for an allocator. B sees that it has a downstream connection so +// asks its downstream input pin i.e. C's input pin for an allocator. C sees +// that it too has a downstream connection so asks Z for an allocator. +// +// Even though Z's input pin is connected, it is being asked for an allocator. +// It could refuse, in which case the chain is done and will use A's allocator +// Alternatively, Z may supply one. A chooses either Z's or A's own one. +// B's input pin gets NotifyAllocator called to tell it the decision and it +// propagates this downstream by calling ReceiveAllocator on its output pin +// which calls NotifyAllocator on the next input pin downstream etc. +// If the choice is Z then it goes: +// A-z->B-a->C-a->Z +// A-z->B-z->C-a->Z +// A-z->B-z->C-z->Z +// +// And that's IT!! Any further (essentially spurious) reconnects peter out +// with no change in the chain. + +#include <streams.h> +#include <measure.h> +#include <transip.h> + + +// ================================================================= +// Implements the CTransInPlaceFilter class +// ================================================================= + +CTransInPlaceFilter::CTransInPlaceFilter + ( __in_opt LPCTSTR pName, + __inout_opt LPUNKNOWN pUnk, + REFCLSID clsid, + __inout HRESULT *phr, + bool bModifiesData + ) + : CTransformFilter(pName, pUnk, clsid), + m_bModifiesData(bModifiesData) +{ +#ifdef PERF + RegisterPerfId(); +#endif // PERF + +} // constructor + +#ifdef UNICODE +CTransInPlaceFilter::CTransInPlaceFilter + ( __in_opt LPCSTR pName, + __inout_opt LPUNKNOWN pUnk, + REFCLSID clsid, + __inout HRESULT *phr, + bool bModifiesData + ) + : CTransformFilter(pName, pUnk, clsid), + m_bModifiesData(bModifiesData) +{ +#ifdef PERF + RegisterPerfId(); +#endif // PERF + +} // constructor +#endif + +// return a non-addrefed CBasePin * for the user to addref if he holds onto it +// for longer than his pointer to us. We create the pins dynamically when they +// are asked for rather than in the constructor. This is because we want to +// give the derived class an oppportunity to return different pin objects + +// As soon as any pin is needed we create both (this is different from the +// usual transform filter) because enumerators, allocators etc are passed +// through from one pin to another and it becomes very painful if the other +// pin isn't there. If we fail to create either pin we ensure we fail both. + +CBasePin * +CTransInPlaceFilter::GetPin(int n) +{ + HRESULT hr = S_OK; + + // Create an input pin if not already done + + if (m_pInput == NULL) { + + m_pInput = new CTransInPlaceInputPin( NAME("TransInPlace input pin") + , this // Owner filter + , &hr // Result code + , L"Input" // Pin name + ); + + // Constructor for CTransInPlaceInputPin can't fail + ASSERT(SUCCEEDED(hr)); + } + + // Create an output pin if not already done + + if (m_pInput!=NULL && m_pOutput == NULL) { + + m_pOutput = new CTransInPlaceOutputPin( NAME("TransInPlace output pin") + , this // Owner filter + , &hr // Result code + , L"Output" // Pin name + ); + + // a failed return code should delete the object + + ASSERT(SUCCEEDED(hr)); + if (m_pOutput == NULL) { + delete m_pInput; + m_pInput = NULL; + } + } + + // Return the appropriate pin + + ASSERT (n>=0 && n<=1); + if (n == 0) { + return m_pInput; + } else if (n==1) { + return m_pOutput; + } else { + return NULL; + } + +} // GetPin + + + +// dir is the direction of our pin. +// pReceivePin is the pin we are connecting to. +HRESULT CTransInPlaceFilter::CompleteConnect(PIN_DIRECTION dir, IPin *pReceivePin) +{ + UNREFERENCED_PARAMETER(pReceivePin); + ASSERT(m_pInput); + ASSERT(m_pOutput); + + // if we are not part of a graph, then don't indirect the pointer + // this probably prevents use of the filter without a filtergraph + if (!m_pGraph) { + return VFW_E_NOT_IN_GRAPH; + } + + // Always reconnect the input to account for buffering changes + // + // Because we don't get to suggest a type on ReceiveConnection + // we need another way of making sure the right type gets used. + // + // One way would be to have our EnumMediaTypes return our output + // connection type first but more deterministic and simple is to + // call ReconnectEx passing the type we want to reconnect with + // via the base class ReconeectPin method. + + if (dir == PINDIR_OUTPUT) { + if( m_pInput->IsConnected() ) { + return ReconnectPin( m_pInput, &m_pOutput->CurrentMediaType() ); + } + return NOERROR; + } + + ASSERT(dir == PINDIR_INPUT); + + // Reconnect output if necessary + + if( m_pOutput->IsConnected() ) { + + if ( m_pInput->CurrentMediaType() + != m_pOutput->CurrentMediaType() + ) { + return ReconnectPin( m_pOutput, &m_pInput->CurrentMediaType() ); + } + } + return NOERROR; + +} // ComnpleteConnect + + +// +// DecideBufferSize +// +// Tell the output pin's allocator what size buffers we require. +// *pAlloc will be the allocator our output pin is using. +// + +HRESULT CTransInPlaceFilter::DecideBufferSize + ( IMemAllocator *pAlloc + , __inout ALLOCATOR_PROPERTIES *pProperties + ) +{ + ALLOCATOR_PROPERTIES Request, Actual; + HRESULT hr; + + // If we are connected upstream, get his views + if (m_pInput->IsConnected()) { + // Get the input pin allocator, and get its size and count. + // we don't care about his alignment and prefix. + + hr = InputPin()->PeekAllocator()->GetProperties(&Request); + if (FAILED(hr)) { + // Input connected but with a secretive allocator - enough! + return hr; + } + } else { + // Propose one byte + // If this isn't enough then when the other pin does get connected + // we can revise it. + ZeroMemory(&Request, sizeof(Request)); + Request.cBuffers = 1; + Request.cbBuffer = 1; + } + + + DbgLog((LOG_MEMORY,1,TEXT("Setting Allocator Requirements"))); + DbgLog((LOG_MEMORY,1,TEXT("Count %d, Size %d"), + Request.cBuffers, Request.cbBuffer)); + + // Pass the allocator requirements to our output side + // but do a little sanity checking first or we'll just hit + // asserts in the allocator. + + pProperties->cBuffers = Request.cBuffers; + pProperties->cbBuffer = Request.cbBuffer; + pProperties->cbAlign = Request.cbAlign; + if (pProperties->cBuffers<=0) {pProperties->cBuffers = 1; } + if (pProperties->cbBuffer<=0) {pProperties->cbBuffer = 1; } + hr = pAlloc->SetProperties(pProperties, &Actual); + + if (FAILED(hr)) { + return hr; + } + + DbgLog((LOG_MEMORY,1,TEXT("Obtained Allocator Requirements"))); + DbgLog((LOG_MEMORY,1,TEXT("Count %d, Size %d, Alignment %d"), + Actual.cBuffers, Actual.cbBuffer, Actual.cbAlign)); + + // Make sure we got the right alignment and at least the minimum required + + if ( (Request.cBuffers > Actual.cBuffers) + || (Request.cbBuffer > Actual.cbBuffer) + || (Request.cbAlign > Actual.cbAlign) + ) { + return E_FAIL; + } + return NOERROR; + +} // DecideBufferSize + +// +// Copy +// +// return a pointer to an identical copy of pSample +__out_opt IMediaSample * CTransInPlaceFilter::Copy(IMediaSample *pSource) +{ + IMediaSample * pDest; + + HRESULT hr; + REFERENCE_TIME tStart, tStop; + const BOOL bTime = S_OK == pSource->GetTime( &tStart, &tStop); + + // this may block for an indeterminate amount of time + hr = OutputPin()->PeekAllocator()->GetBuffer( + &pDest + , bTime ? &tStart : NULL + , bTime ? &tStop : NULL + , m_bSampleSkipped ? AM_GBF_PREVFRAMESKIPPED : 0 + ); + + if (FAILED(hr)) { + return NULL; + } + + ASSERT(pDest); + IMediaSample2 *pSample2; + if (SUCCEEDED(pDest->QueryInterface(IID_IMediaSample2, (void **)&pSample2))) { + HRESULT hrProps = pSample2->SetProperties( + FIELD_OFFSET(AM_SAMPLE2_PROPERTIES, pbBuffer), + (PBYTE)m_pInput->SampleProps()); + pSample2->Release(); + if (FAILED(hrProps)) { + pDest->Release(); + return NULL; + } + } else { + if (bTime) { + pDest->SetTime(&tStart, &tStop); + } + + if (S_OK == pSource->IsSyncPoint()) { + pDest->SetSyncPoint(TRUE); + } + if (S_OK == pSource->IsDiscontinuity() || m_bSampleSkipped) { + pDest->SetDiscontinuity(TRUE); + } + if (S_OK == pSource->IsPreroll()) { + pDest->SetPreroll(TRUE); + } + + // Copy the media type + AM_MEDIA_TYPE *pMediaType; + if (S_OK == pSource->GetMediaType(&pMediaType)) { + pDest->SetMediaType(pMediaType); + DeleteMediaType( pMediaType ); + } + + } + + m_bSampleSkipped = FALSE; + + // Copy the sample media times + REFERENCE_TIME TimeStart, TimeEnd; + if (pSource->GetMediaTime(&TimeStart,&TimeEnd) == NOERROR) { + pDest->SetMediaTime(&TimeStart,&TimeEnd); + } + + // Copy the actual data length and the actual data. + { + const long lDataLength = pSource->GetActualDataLength(); + if (FAILED(pDest->SetActualDataLength(lDataLength))) { + pDest->Release(); + return NULL; + } + + // Copy the sample data + { + BYTE *pSourceBuffer, *pDestBuffer; + long lSourceSize = pSource->GetSize(); + long lDestSize = pDest->GetSize(); + + ASSERT(lDestSize >= lSourceSize && lDestSize >= lDataLength); + + if (FAILED(pSource->GetPointer(&pSourceBuffer)) || + FAILED(pDest->GetPointer(&pDestBuffer)) || + lDestSize < lDataLength || + lDataLength < 0) { + pDest->Release(); + return NULL; + } + ASSERT(lDestSize == 0 || pSourceBuffer != NULL && pDestBuffer != NULL); + + CopyMemory( (PVOID) pDestBuffer, (PVOID) pSourceBuffer, lDataLength ); + } + } + + return pDest; + +} // Copy + + +// override this to customize the transform process + +HRESULT +CTransInPlaceFilter::Receive(IMediaSample *pSample) +{ + /* Check for other streams and pass them on */ + AM_SAMPLE2_PROPERTIES * const pProps = m_pInput->SampleProps(); + if (pProps->dwStreamId != AM_STREAM_MEDIA) { + return m_pOutput->Deliver(pSample); + } + HRESULT hr; + + // Start timing the TransInPlace (if PERF is defined) + MSR_START(m_idTransInPlace); + + if (UsingDifferentAllocators()) { + + // We have to copy the data. + + pSample = Copy(pSample); + + if (pSample==NULL) { + MSR_STOP(m_idTransInPlace); + return E_UNEXPECTED; + } + } + + // have the derived class transform the data + hr = Transform(pSample); + + // Stop the clock and log it (if PERF is defined) + MSR_STOP(m_idTransInPlace); + + if (FAILED(hr)) { + DbgLog((LOG_TRACE, 1, TEXT("Error from TransInPlace"))); + if (UsingDifferentAllocators()) { + pSample->Release(); + } + return hr; + } + + // the Transform() function can return S_FALSE to indicate that the + // sample should not be delivered; we only deliver the sample if it's + // really S_OK (same as NOERROR, of course.) + if (hr == NOERROR) { + hr = m_pOutput->Deliver(pSample); + } else { + // But it would be an error to return this private workaround + // to the caller ... + if (S_FALSE == hr) { + // S_FALSE returned from Transform is a PRIVATE agreement + // We should return NOERROR from Receive() in this cause because + // returning S_FALSE from Receive() means that this is the end + // of the stream and no more data should be sent. + m_bSampleSkipped = TRUE; + if (!m_bQualityChanged) { + NotifyEvent(EC_QUALITY_CHANGE,0,0); + m_bQualityChanged = TRUE; + } + hr = NOERROR; + } + } + + // release the output buffer. If the connected pin still needs it, + // it will have addrefed it itself. + if (UsingDifferentAllocators()) { + pSample->Release(); + } + + return hr; + +} // Receive + + + +// ================================================================= +// Implements the CTransInPlaceInputPin class +// ================================================================= + + +// constructor + +CTransInPlaceInputPin::CTransInPlaceInputPin + ( __in_opt LPCTSTR pObjectName + , __inout CTransInPlaceFilter *pFilter + , __inout HRESULT *phr + , __in_opt LPCWSTR pName + ) + : CTransformInputPin(pObjectName, + pFilter, + phr, + pName) + , m_bReadOnly(FALSE) + , m_pTIPFilter(pFilter) +{ + DbgLog((LOG_TRACE, 2 + , TEXT("CTransInPlaceInputPin::CTransInPlaceInputPin"))); + +} // constructor + + +// ================================================================= +// Implements IMemInputPin interface +// ================================================================= + + +// If the downstream filter has one then offer that (even if our own output +// pin is not using it yet. If the upstream filter chooses it then we will +// tell our output pin to ReceiveAllocator). +// Else if our output pin is using an allocator then offer that. +// ( This could mean offering the upstream filter his own allocator, +// it could mean offerring our own +// ) or it could mean offering the one from downstream +// Else fail to offer any allocator at all. + +STDMETHODIMP CTransInPlaceInputPin::GetAllocator(__deref_out IMemAllocator ** ppAllocator) +{ + CheckPointer(ppAllocator,E_POINTER); + ValidateReadWritePtr(ppAllocator,sizeof(IMemAllocator *)); + CAutoLock cObjectLock(m_pLock); + + HRESULT hr; + + if ( m_pTIPFilter->m_pOutput->IsConnected() ) { + // Store the allocator we got + hr = m_pTIPFilter->OutputPin()->ConnectedIMemInputPin() + ->GetAllocator( ppAllocator ); + if (SUCCEEDED(hr)) { + m_pTIPFilter->OutputPin()->SetAllocator( *ppAllocator ); + } + } + else { + // Help upstream filter (eg TIP filter which is having to do a copy) + // by providing a temp allocator here - we'll never use + // this allocator because when our output is connected we'll + // reconnect this pin + hr = CTransformInputPin::GetAllocator( ppAllocator ); + } + return hr; + +} // GetAllocator + + + +/* Get told which allocator the upstream output pin is actually going to use */ + + +STDMETHODIMP +CTransInPlaceInputPin::NotifyAllocator( + IMemAllocator * pAllocator, + BOOL bReadOnly) +{ + HRESULT hr = S_OK; + CheckPointer(pAllocator,E_POINTER); + ValidateReadPtr(pAllocator,sizeof(IMemAllocator)); + + CAutoLock cObjectLock(m_pLock); + + m_bReadOnly = bReadOnly; + // If we modify data then don't accept the allocator if it's + // the same as the output pin's allocator + + // If our output is not connected just accept the allocator + // We're never going to use this allocator because when our + // output pin is connected we'll reconnect this pin + if (!m_pTIPFilter->OutputPin()->IsConnected()) { + return CTransformInputPin::NotifyAllocator(pAllocator, bReadOnly); + } + + // If the allocator is read-only and we're modifying data + // and the allocator is the same as the output pin's + // then reject + if (bReadOnly && m_pTIPFilter->m_bModifiesData) { + IMemAllocator *pOutputAllocator = + m_pTIPFilter->OutputPin()->PeekAllocator(); + + // Make sure we have an output allocator + if (pOutputAllocator == NULL) { + hr = m_pTIPFilter->OutputPin()->ConnectedIMemInputPin()-> + GetAllocator(&pOutputAllocator); + if(FAILED(hr)) { + hr = CreateMemoryAllocator(&pOutputAllocator); + } + if (SUCCEEDED(hr)) { + m_pTIPFilter->OutputPin()->SetAllocator(pOutputAllocator); + pOutputAllocator->Release(); + } + } + if (pAllocator == pOutputAllocator) { + hr = E_FAIL; + } else if(SUCCEEDED(hr)) { + // Must copy so set the allocator properties on the output + ALLOCATOR_PROPERTIES Props, Actual; + hr = pAllocator->GetProperties(&Props); + if (SUCCEEDED(hr)) { + hr = pOutputAllocator->SetProperties(&Props, &Actual); + } + if (SUCCEEDED(hr)) { + if ( (Props.cBuffers > Actual.cBuffers) + || (Props.cbBuffer > Actual.cbBuffer) + || (Props.cbAlign > Actual.cbAlign) + ) { + hr = E_FAIL; + } + } + + // Set the allocator on the output pin + if (SUCCEEDED(hr)) { + hr = m_pTIPFilter->OutputPin()->ConnectedIMemInputPin() + ->NotifyAllocator( pOutputAllocator, FALSE ); + } + } + } else { + hr = m_pTIPFilter->OutputPin()->ConnectedIMemInputPin() + ->NotifyAllocator( pAllocator, bReadOnly ); + if (SUCCEEDED(hr)) { + m_pTIPFilter->OutputPin()->SetAllocator( pAllocator ); + } + } + + if (SUCCEEDED(hr)) { + + // It's possible that the old and the new are the same thing. + // AddRef before release ensures that we don't unload it. + pAllocator->AddRef(); + + if( m_pAllocator != NULL ) + m_pAllocator->Release(); + + m_pAllocator = pAllocator; // We have an allocator for the input pin + } + + return hr; + +} // NotifyAllocator + + +// EnumMediaTypes +// - pass through to our downstream filter +STDMETHODIMP CTransInPlaceInputPin::EnumMediaTypes( __deref_out IEnumMediaTypes **ppEnum ) +{ + // Can only pass through if connected + if( !m_pTIPFilter->m_pOutput->IsConnected() ) + return VFW_E_NOT_CONNECTED; + + return m_pTIPFilter->m_pOutput->GetConnected()->EnumMediaTypes( ppEnum ); + +} // EnumMediaTypes + + +// CheckMediaType +// - agree to anything if not connected, +// otherwise pass through to the downstream filter. +// This assumes that the filter does not change the media type. + +HRESULT CTransInPlaceInputPin::CheckMediaType(const CMediaType *pmt ) +{ + HRESULT hr = m_pTIPFilter->CheckInputType(pmt); + if (hr!=S_OK) return hr; + + if( m_pTIPFilter->m_pOutput->IsConnected() ) + return m_pTIPFilter->m_pOutput->GetConnected()->QueryAccept( pmt ); + else + return S_OK; + +} // CheckMediaType + + +// If upstream asks us what our requirements are, we will try to ask downstream +// if that doesn't work, we'll just take the defaults. +STDMETHODIMP +CTransInPlaceInputPin::GetAllocatorRequirements(__out ALLOCATOR_PROPERTIES *pProps) +{ + + if( m_pTIPFilter->m_pOutput->IsConnected() ) + return m_pTIPFilter->OutputPin() + ->ConnectedIMemInputPin()->GetAllocatorRequirements( pProps ); + else + return E_NOTIMPL; + +} // GetAllocatorRequirements + + +// CTransInPlaceInputPin::CompleteConnect() calls CBaseInputPin::CompleteConnect() +// and then calls CTransInPlaceFilter::CompleteConnect(). It does this because +// CTransInPlaceFilter::CompleteConnect() can reconnect a pin and we do not +// want to reconnect a pin if CBaseInputPin::CompleteConnect() fails. +HRESULT +CTransInPlaceInputPin::CompleteConnect(IPin *pReceivePin) +{ + HRESULT hr = CBaseInputPin::CompleteConnect(pReceivePin); + if (FAILED(hr)) { + return hr; + } + + return m_pTransformFilter->CompleteConnect(PINDIR_INPUT,pReceivePin); +} // CompleteConnect + + +// ================================================================= +// Implements the CTransInPlaceOutputPin class +// ================================================================= + + +// constructor + +CTransInPlaceOutputPin::CTransInPlaceOutputPin( + __in_opt LPCTSTR pObjectName, + __inout CTransInPlaceFilter *pFilter, + __inout HRESULT * phr, + __in_opt LPCWSTR pPinName) + : CTransformOutputPin( pObjectName + , pFilter + , phr + , pPinName), + m_pTIPFilter(pFilter) +{ + DbgLog(( LOG_TRACE, 2 + , TEXT("CTransInPlaceOutputPin::CTransInPlaceOutputPin"))); + +} // constructor + + +// EnumMediaTypes +// - pass through to our upstream filter +STDMETHODIMP CTransInPlaceOutputPin::EnumMediaTypes( __deref_out IEnumMediaTypes **ppEnum ) +{ + // Can only pass through if connected. + if( ! m_pTIPFilter->m_pInput->IsConnected() ) + return VFW_E_NOT_CONNECTED; + + return m_pTIPFilter->m_pInput->GetConnected()->EnumMediaTypes( ppEnum ); + +} // EnumMediaTypes + + + +// CheckMediaType +// - agree to anything if not connected, +// otherwise pass through to the upstream filter. + +HRESULT CTransInPlaceOutputPin::CheckMediaType(const CMediaType *pmt ) +{ + // Don't accept any output pin type changes if we're copying + // between allocators - it's too late to change the input + // allocator size. + if (m_pTIPFilter->UsingDifferentAllocators() && !m_pFilter->IsStopped()) { + if (*pmt == m_mt) { + return S_OK; + } else { + return VFW_E_TYPE_NOT_ACCEPTED; + } + } + + // Assumes the type does not change. That's why we're calling + // CheckINPUTType here on the OUTPUT pin. + HRESULT hr = m_pTIPFilter->CheckInputType(pmt); + if (hr!=S_OK) return hr; + + if( m_pTIPFilter->m_pInput->IsConnected() ) + return m_pTIPFilter->m_pInput->GetConnected()->QueryAccept( pmt ); + else + return S_OK; + +} // CheckMediaType + + +/* Save the allocator pointer in the output pin +*/ +void +CTransInPlaceOutputPin::SetAllocator(IMemAllocator * pAllocator) +{ + pAllocator->AddRef(); + if (m_pAllocator) { + m_pAllocator->Release(); + } + m_pAllocator = pAllocator; +} // SetAllocator + + +// CTransInPlaceOutputPin::CompleteConnect() calls CBaseOutputPin::CompleteConnect() +// and then calls CTransInPlaceFilter::CompleteConnect(). It does this because +// CTransInPlaceFilter::CompleteConnect() can reconnect a pin and we do not want to +// reconnect a pin if CBaseOutputPin::CompleteConnect() fails. +// CBaseOutputPin::CompleteConnect() often fails when our output pin is being connected +// to the Video Mixing Renderer. +HRESULT +CTransInPlaceOutputPin::CompleteConnect(IPin *pReceivePin) +{ + HRESULT hr = CBaseOutputPin::CompleteConnect(pReceivePin); + if (FAILED(hr)) { + return hr; + } + + return m_pTransformFilter->CompleteConnect(PINDIR_OUTPUT,pReceivePin); +} // CompleteConnect diff --git a/Src/Plugins/Input/in_dshow/base/transip.h b/Src/Plugins/Input/in_dshow/base/transip.h new file mode 100644 index 00000000..45fb4e99 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/transip.h @@ -0,0 +1,250 @@ +//------------------------------------------------------------------------------ +// File: TransIP.h +// +// Desc: DirectShow base classes - defines classes from which simple +// Transform-In-Place filters may be derived. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// +// The difference between this and Transfrm.h is that Transfrm copies the data. +// +// It assumes the filter has one input and one output stream, and has no +// interest in memory management, interface negotiation or anything else. +// +// Derive your class from this, and supply Transform and the media type/format +// negotiation functions. Implement that class, compile and link and +// you're done. + + +#ifndef __TRANSIP__ +#define __TRANSIP__ + +// ====================================================================== +// This is the com object that represents a simple transform filter. It +// supports IBaseFilter, IMediaFilter and two pins through nested interfaces +// ====================================================================== + +class CTransInPlaceFilter; + +// Several of the pin functions call filter functions to do the work, +// so you can often use the pin classes unaltered, just overriding the +// functions in CTransInPlaceFilter. If that's not enough and you want +// to derive your own pin class, override GetPin in the filter to supply +// your own pin classes to the filter. + +// ================================================== +// Implements the input pin +// ================================================== + +class CTransInPlaceInputPin : public CTransformInputPin +{ + +protected: + CTransInPlaceFilter * const m_pTIPFilter; // our filter + BOOL m_bReadOnly; // incoming stream is read only + +public: + + CTransInPlaceInputPin( + __in_opt LPCTSTR pObjectName, + __inout CTransInPlaceFilter *pFilter, + __inout HRESULT *phr, + __in_opt LPCWSTR pName); + + // --- IMemInputPin ----- + + // Provide an enumerator for media types by getting one from downstream + STDMETHODIMP EnumMediaTypes( __deref_out IEnumMediaTypes **ppEnum ); + + // Say whether media type is acceptable. + HRESULT CheckMediaType(const CMediaType* pmt); + + // Return our upstream allocator + STDMETHODIMP GetAllocator(__deref_out IMemAllocator ** ppAllocator); + + // get told which allocator the upstream output pin is actually + // going to use. + STDMETHODIMP NotifyAllocator(IMemAllocator * pAllocator, + BOOL bReadOnly); + + // Allow the filter to see what allocator we have + // N.B. This does NOT AddRef + __out IMemAllocator * PeekAllocator() const + { return m_pAllocator; } + + // Pass this on downstream if it ever gets called. + STDMETHODIMP GetAllocatorRequirements(__out ALLOCATOR_PROPERTIES *pProps); + + HRESULT CompleteConnect(IPin *pReceivePin); + + inline const BOOL ReadOnly() { return m_bReadOnly ; } + +}; // CTransInPlaceInputPin + +// ================================================== +// Implements the output pin +// ================================================== + +class CTransInPlaceOutputPin : public CTransformOutputPin +{ + +protected: + // m_pFilter points to our CBaseFilter + CTransInPlaceFilter * const m_pTIPFilter; + +public: + + CTransInPlaceOutputPin( + __in_opt LPCTSTR pObjectName, + __inout CTransInPlaceFilter *pFilter, + __inout HRESULT *phr, + __in_opt LPCWSTR pName); + + + // --- CBaseOutputPin ------------ + + // negotiate the allocator and its buffer size/count + // Insists on using our own allocator. (Actually the one upstream of us). + // We don't override this - instead we just agree the default + // then let the upstream filter decide for itself on reconnect + // virtual HRESULT DecideAllocator(IMemInputPin * pPin, IMemAllocator ** pAlloc); + + // Provide a media type enumerator. Get it from upstream. + STDMETHODIMP EnumMediaTypes( __deref_out IEnumMediaTypes **ppEnum ); + + // Say whether media type is acceptable. + HRESULT CheckMediaType(const CMediaType* pmt); + + // This just saves the allocator being used on the output pin + // Also called by input pin's GetAllocator() + void SetAllocator(IMemAllocator * pAllocator); + + __out_opt IMemInputPin * ConnectedIMemInputPin() + { return m_pInputPin; } + + // Allow the filter to see what allocator we have + // N.B. This does NOT AddRef + __out IMemAllocator * PeekAllocator() const + { return m_pAllocator; } + + HRESULT CompleteConnect(IPin *pReceivePin); + +}; // CTransInPlaceOutputPin + + +class AM_NOVTABLE CTransInPlaceFilter : public CTransformFilter +{ + +public: + + // map getpin/getpincount for base enum of pins to owner + // override this to return more specialised pin objects + + virtual CBasePin *GetPin(int n); + +public: + + // Set bModifiesData == false if your derived filter does + // not modify the data samples (for instance it's just copying + // them somewhere else or looking at the timestamps). + + CTransInPlaceFilter(__in_opt LPCTSTR, __inout_opt LPUNKNOWN, REFCLSID clsid, __inout HRESULT *, + bool bModifiesData = true); +#ifdef UNICODE + CTransInPlaceFilter(__in_opt LPCSTR, __inout_opt LPUNKNOWN, REFCLSID clsid, __inout HRESULT *, + bool bModifiesData = true); +#endif + // The following are defined to avoid undefined pure virtuals. + // Even if they are never called, they will give linkage warnings/errors + + // We override EnumMediaTypes to bypass the transform class enumerator + // which would otherwise call this. + HRESULT GetMediaType(int iPosition, __inout CMediaType *pMediaType) + { DbgBreak("CTransInPlaceFilter::GetMediaType should never be called"); + return E_UNEXPECTED; + } + + // This is called when we actually have to provide our own allocator. + HRESULT DecideBufferSize(IMemAllocator*, __inout ALLOCATOR_PROPERTIES *); + + // The functions which call this in CTransform are overridden in this + // class to call CheckInputType with the assumption that the type + // does not change. In Debug builds some calls will be made and + // we just ensure that they do not assert. + HRESULT CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut) + { + return S_OK; + }; + + + // ================================================================= + // ----- You may want to override this ----------------------------- + // ================================================================= + + HRESULT CompleteConnect(PIN_DIRECTION dir,IPin *pReceivePin); + + // chance to customize the transform process + virtual HRESULT Receive(IMediaSample *pSample); + + // ================================================================= + // ----- You MUST override these ----------------------------------- + // ================================================================= + + virtual HRESULT Transform(IMediaSample *pSample) PURE; + + // this goes in the factory template table to create new instances + // static CCOMObject * CreateInstance(LPUNKNOWN, HRESULT *); + + +#ifdef PERF + // Override to register performance measurement with a less generic string + // You should do this to avoid confusion with other filters + virtual void RegisterPerfId() + {m_idTransInPlace = MSR_REGISTER(TEXT("TransInPlace"));} +#endif // PERF + + +// implementation details + +protected: + + __out_opt IMediaSample * CTransInPlaceFilter::Copy(IMediaSample *pSource); + +#ifdef PERF + int m_idTransInPlace; // performance measuring id +#endif // PERF + bool m_bModifiesData; // Does this filter change the data? + + // these hold our input and output pins + + friend class CTransInPlaceInputPin; + friend class CTransInPlaceOutputPin; + + __out CTransInPlaceInputPin *InputPin() const + { + return (CTransInPlaceInputPin *)m_pInput; + }; + __out CTransInPlaceOutputPin *OutputPin() const + { + return (CTransInPlaceOutputPin *)m_pOutput; + }; + + // Helper to see if the input and output types match + BOOL TypesMatch() + { + return InputPin()->CurrentMediaType() == + OutputPin()->CurrentMediaType(); + } + + // Are the input and output allocators different? + BOOL UsingDifferentAllocators() const + { + return InputPin()->PeekAllocator() != OutputPin()->PeekAllocator(); + } +}; // CTransInPlaceFilter + +#endif /* __TRANSIP__ */ + diff --git a/Src/Plugins/Input/in_dshow/base/videoctl.cpp b/Src/Plugins/Input/in_dshow/base/videoctl.cpp new file mode 100644 index 00000000..94fa3721 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/videoctl.cpp @@ -0,0 +1,746 @@ +//------------------------------------------------------------------------------ +// File: VideoCtl.cpp +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> +#include "ddmm.h" + +// Load a string from the resource file string table. The buffer must be at +// least STR_MAX_LENGTH bytes. The easiest way to use this is to declare a +// buffer in the property page class and use it for all string loading. It +// cannot be static as multiple property pages may be active simultaneously + +LPTSTR WINAPI StringFromResource(__out_ecount(STR_MAX_LENGTH) LPTSTR pBuffer, int iResourceID) +{ + if (LoadString(g_hInst,iResourceID,pBuffer,STR_MAX_LENGTH) == 0) { + return TEXT(""); + } + return pBuffer; +} + +#ifdef UNICODE +LPSTR WINAPI StringFromResource(__out_ecount(STR_MAX_LENGTH) LPSTR pBuffer, int iResourceID) +{ + if (LoadStringA(g_hInst,iResourceID,pBuffer,STR_MAX_LENGTH) == 0) { + return ""; + } + return pBuffer; +} +#endif + + + +// Property pages typically are called through their OLE interfaces. These +// use UNICODE strings regardless of how the binary is built. So when we +// load strings from the resource file we sometimes want to convert them +// to UNICODE. This method is passed the target UNICODE buffer and does a +// convert after loading the string (if built UNICODE this is not needed) +// On WinNT we can explicitly call LoadStringW which saves two conversions + +#ifndef UNICODE + +LPWSTR WINAPI WideStringFromResource(__out_ecount(STR_MAX_LENGTH) LPWSTR pBuffer, int iResourceID) +{ + *pBuffer = 0; + + if (g_amPlatform == VER_PLATFORM_WIN32_NT) { + LoadStringW(g_hInst,iResourceID,pBuffer,STR_MAX_LENGTH); + } else { + + CHAR szBuffer[STR_MAX_LENGTH]; + DWORD dwStringLength = LoadString(g_hInst,iResourceID,szBuffer,STR_MAX_LENGTH); + // if we loaded a string convert it to wide characters, ensuring + // that we also null terminate the result. + if (dwStringLength++) { + MultiByteToWideChar(CP_ACP,0,szBuffer,dwStringLength,pBuffer,STR_MAX_LENGTH); + } + } + return pBuffer; +} + +#endif + + +// Helper function to calculate the size of the dialog + +BOOL WINAPI GetDialogSize(int iResourceID, + DLGPROC pDlgProc, + LPARAM lParam, + __out SIZE *pResult) +{ + RECT rc; + HWND hwnd; + + // Create a temporary property page + + hwnd = CreateDialogParam(g_hInst, + MAKEINTRESOURCE(iResourceID), + GetDesktopWindow(), + pDlgProc, + lParam); + if (hwnd == NULL) { + return FALSE; + } + + GetWindowRect(hwnd, &rc); + pResult->cx = rc.right - rc.left; + pResult->cy = rc.bottom - rc.top; + + DestroyWindow(hwnd); + return TRUE; +} + + +// Class that aggregates on the IDirectDraw interface. Although DirectDraw +// has the ability in its interfaces to be aggregated they're not currently +// implemented. This makes it difficult for various parts of Quartz that want +// to aggregate these interfaces. In particular the video renderer passes out +// media samples that expose IDirectDraw and IDirectDrawSurface. The filter +// graph manager also exposes IDirectDraw as a plug in distributor. For these +// objects we provide these aggregation classes that republish the interfaces + +STDMETHODIMP CAggDirectDraw::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + ASSERT(m_pDirectDraw); + + // Do we have this interface + + if (riid == IID_IDirectDraw) { + return GetInterface((IDirectDraw *)this,ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid,ppv); + } +} + + +STDMETHODIMP CAggDirectDraw::Compact() +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->Compact(); +} + + +STDMETHODIMP CAggDirectDraw::CreateClipper(DWORD dwFlags, __deref_out LPDIRECTDRAWCLIPPER *lplpDDClipper, __inout_opt IUnknown *pUnkOuter) +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->CreateClipper(dwFlags,lplpDDClipper,pUnkOuter); +} + + +STDMETHODIMP CAggDirectDraw::CreatePalette(DWORD dwFlags, + __in LPPALETTEENTRY lpColorTable, + __deref_out LPDIRECTDRAWPALETTE *lplpDDPalette, + __inout_opt IUnknown *pUnkOuter) +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->CreatePalette(dwFlags,lpColorTable,lplpDDPalette,pUnkOuter); +} + + +STDMETHODIMP CAggDirectDraw::CreateSurface(__in LPDDSURFACEDESC lpDDSurfaceDesc, + __deref_out LPDIRECTDRAWSURFACE *lplpDDSurface, + __inout_opt IUnknown *pUnkOuter) +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->CreateSurface(lpDDSurfaceDesc,lplpDDSurface,pUnkOuter); +} + + +STDMETHODIMP CAggDirectDraw::DuplicateSurface(__in LPDIRECTDRAWSURFACE lpDDSurface, + __deref_out LPDIRECTDRAWSURFACE *lplpDupDDSurface) +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->DuplicateSurface(lpDDSurface,lplpDupDDSurface); +} + + +STDMETHODIMP CAggDirectDraw::EnumDisplayModes(DWORD dwSurfaceDescCount, + __in LPDDSURFACEDESC lplpDDSurfaceDescList, + __in LPVOID lpContext, + __in LPDDENUMMODESCALLBACK lpEnumCallback) +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->EnumDisplayModes(dwSurfaceDescCount,lplpDDSurfaceDescList,lpContext,lpEnumCallback); +} + + +STDMETHODIMP CAggDirectDraw::EnumSurfaces(DWORD dwFlags, + __in LPDDSURFACEDESC lpDDSD, + __in LPVOID lpContext, + __in LPDDENUMSURFACESCALLBACK lpEnumCallback) +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->EnumSurfaces(dwFlags,lpDDSD,lpContext,lpEnumCallback); +} + + +STDMETHODIMP CAggDirectDraw::FlipToGDISurface() +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->FlipToGDISurface(); +} + + +STDMETHODIMP CAggDirectDraw::GetCaps(__out LPDDCAPS lpDDDriverCaps,__out LPDDCAPS lpDDHELCaps) +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->GetCaps(lpDDDriverCaps,lpDDHELCaps); +} + + +STDMETHODIMP CAggDirectDraw::GetDisplayMode(__out LPDDSURFACEDESC lpDDSurfaceDesc) +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->GetDisplayMode(lpDDSurfaceDesc); +} + + +STDMETHODIMP CAggDirectDraw::GetFourCCCodes(__inout LPDWORD lpNumCodes,__out_ecount(*lpNumCodes) LPDWORD lpCodes) +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->GetFourCCCodes(lpNumCodes,lpCodes); +} + + +STDMETHODIMP CAggDirectDraw::GetGDISurface(__deref_out LPDIRECTDRAWSURFACE *lplpGDIDDSurface) +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->GetGDISurface(lplpGDIDDSurface); +} + + +STDMETHODIMP CAggDirectDraw::GetMonitorFrequency(__out LPDWORD lpdwFrequency) +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->GetMonitorFrequency(lpdwFrequency); +} + + +STDMETHODIMP CAggDirectDraw::GetScanLine(__out LPDWORD lpdwScanLine) +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->GetScanLine(lpdwScanLine); +} + + +STDMETHODIMP CAggDirectDraw::GetVerticalBlankStatus(__out LPBOOL lpblsInVB) +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->GetVerticalBlankStatus(lpblsInVB); +} + + +STDMETHODIMP CAggDirectDraw::Initialize(__in GUID *lpGUID) +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->Initialize(lpGUID); +} + + +STDMETHODIMP CAggDirectDraw::RestoreDisplayMode() +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->RestoreDisplayMode(); +} + + +STDMETHODIMP CAggDirectDraw::SetCooperativeLevel(HWND hWnd,DWORD dwFlags) +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->SetCooperativeLevel(hWnd,dwFlags); +} + + +STDMETHODIMP CAggDirectDraw::SetDisplayMode(DWORD dwWidth,DWORD dwHeight,DWORD dwBpp) +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->SetDisplayMode(dwWidth,dwHeight,dwBpp); +} + + +STDMETHODIMP CAggDirectDraw::WaitForVerticalBlank(DWORD dwFlags,HANDLE hEvent) +{ + ASSERT(m_pDirectDraw); + return m_pDirectDraw->WaitForVerticalBlank(dwFlags,hEvent); +} + + +// Class that aggregates an IDirectDrawSurface interface. Although DirectDraw +// has the ability in its interfaces to be aggregated they're not currently +// implemented. This makes it difficult for various parts of Quartz that want +// to aggregate these interfaces. In particular the video renderer passes out +// media samples that expose IDirectDraw and IDirectDrawSurface. The filter +// graph manager also exposes IDirectDraw as a plug in distributor. For these +// objects we provide these aggregation classes that republish the interfaces + +STDMETHODIMP CAggDrawSurface::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) +{ + ASSERT(m_pDirectDrawSurface); + + // Do we have this interface + + if (riid == IID_IDirectDrawSurface) { + return GetInterface((IDirectDrawSurface *)this,ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid,ppv); + } +} + + +STDMETHODIMP CAggDrawSurface::AddAttachedSurface(__in LPDIRECTDRAWSURFACE lpDDSAttachedSurface) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->AddAttachedSurface(lpDDSAttachedSurface); +} + + +STDMETHODIMP CAggDrawSurface::AddOverlayDirtyRect(__in LPRECT lpRect) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->AddOverlayDirtyRect(lpRect); +} + + +STDMETHODIMP CAggDrawSurface::Blt(__in LPRECT lpDestRect, + __in LPDIRECTDRAWSURFACE lpDDSrcSurface, + __in LPRECT lpSrcRect, + DWORD dwFlags, + __in LPDDBLTFX lpDDBltFx) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->Blt(lpDestRect,lpDDSrcSurface,lpSrcRect,dwFlags,lpDDBltFx); +} + + +STDMETHODIMP CAggDrawSurface::BltBatch(__in_ecount(dwCount) LPDDBLTBATCH lpDDBltBatch,DWORD dwCount,DWORD dwFlags) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->BltBatch(lpDDBltBatch,dwCount,dwFlags); +} + + +STDMETHODIMP CAggDrawSurface::BltFast(DWORD dwX,DWORD dwY, + __in LPDIRECTDRAWSURFACE lpDDSrcSurface, + __in LPRECT lpSrcRect, + DWORD dwTrans) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->BltFast(dwX,dwY,lpDDSrcSurface,lpSrcRect,dwTrans); +} + + +STDMETHODIMP CAggDrawSurface::DeleteAttachedSurface(DWORD dwFlags, + __in LPDIRECTDRAWSURFACE lpDDSAttachedSurface) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->DeleteAttachedSurface(dwFlags,lpDDSAttachedSurface); +} + + +STDMETHODIMP CAggDrawSurface::EnumAttachedSurfaces(__in LPVOID lpContext, + __in LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->EnumAttachedSurfaces(lpContext,lpEnumSurfacesCallback); +} + + +STDMETHODIMP CAggDrawSurface::EnumOverlayZOrders(DWORD dwFlags, + __in LPVOID lpContext, + __in LPDDENUMSURFACESCALLBACK lpfnCallback) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->EnumOverlayZOrders(dwFlags,lpContext,lpfnCallback); +} + + +STDMETHODIMP CAggDrawSurface::Flip(__in LPDIRECTDRAWSURFACE lpDDSurfaceTargetOverride,DWORD dwFlags) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->Flip(lpDDSurfaceTargetOverride,dwFlags); +} + + +STDMETHODIMP CAggDrawSurface::GetAttachedSurface(__in LPDDSCAPS lpDDSCaps, + __deref_out LPDIRECTDRAWSURFACE *lplpDDAttachedSurface) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->GetAttachedSurface(lpDDSCaps,lplpDDAttachedSurface); +} + + +STDMETHODIMP CAggDrawSurface::GetBltStatus(DWORD dwFlags) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->GetBltStatus(dwFlags); +} + + +STDMETHODIMP CAggDrawSurface::GetCaps(__out LPDDSCAPS lpDDSCaps) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->GetCaps(lpDDSCaps); +} + + +STDMETHODIMP CAggDrawSurface::GetClipper(__deref_out LPDIRECTDRAWCLIPPER *lplpDDClipper) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->GetClipper(lplpDDClipper); +} + + +STDMETHODIMP CAggDrawSurface::GetColorKey(DWORD dwFlags,__out LPDDCOLORKEY lpDDColorKey) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->GetColorKey(dwFlags,lpDDColorKey); +} + + +STDMETHODIMP CAggDrawSurface::GetDC(__out HDC *lphDC) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->GetDC(lphDC); +} + + +STDMETHODIMP CAggDrawSurface::GetFlipStatus(DWORD dwFlags) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->GetFlipStatus(dwFlags); +} + + +STDMETHODIMP CAggDrawSurface::GetOverlayPosition(__out LPLONG lpdwX,__out LPLONG lpdwY) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->GetOverlayPosition(lpdwX,lpdwY); +} + + +STDMETHODIMP CAggDrawSurface::GetPalette(__deref_out LPDIRECTDRAWPALETTE *lplpDDPalette) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->GetPalette(lplpDDPalette); +} + + +STDMETHODIMP CAggDrawSurface::GetPixelFormat(__out LPDDPIXELFORMAT lpDDPixelFormat) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->GetPixelFormat(lpDDPixelFormat); +} + + +// A bit of a warning here: Our media samples in DirectShow aggregate on +// IDirectDraw and IDirectDrawSurface (ie are available through IMediaSample +// by QueryInterface). Unfortunately the underlying DirectDraw code cannot +// be aggregated so we have to use these classes. The snag is that when we +// call a different surface and pass in this interface as perhaps the source +// surface the call will fail because DirectDraw dereferences the pointer to +// get at its private data structures. Therefore we supply this workaround to give +// access to the real IDirectDraw surface. A filter can call GetSurfaceDesc +// and we will fill in the lpSurface pointer with the real underlying surface + +STDMETHODIMP CAggDrawSurface::GetSurfaceDesc(__out LPDDSURFACEDESC lpDDSurfaceDesc) +{ + ASSERT(m_pDirectDrawSurface); + + // First call down to the underlying DirectDraw + + HRESULT hr = m_pDirectDrawSurface->GetSurfaceDesc(lpDDSurfaceDesc); + if (FAILED(hr)) { + return hr; + } + + // Store the real DirectDrawSurface interface + lpDDSurfaceDesc->lpSurface = m_pDirectDrawSurface; + return hr; +} + + +STDMETHODIMP CAggDrawSurface::Initialize(__in LPDIRECTDRAW lpDD,__in LPDDSURFACEDESC lpDDSurfaceDesc) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->Initialize(lpDD,lpDDSurfaceDesc); +} + + +STDMETHODIMP CAggDrawSurface::IsLost() +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->IsLost(); +} + + +STDMETHODIMP CAggDrawSurface::Lock(__in LPRECT lpDestRect, + __inout LPDDSURFACEDESC lpDDSurfaceDesc, + DWORD dwFlags, + HANDLE hEvent) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->Lock(lpDestRect,lpDDSurfaceDesc,dwFlags,hEvent); +} + + +STDMETHODIMP CAggDrawSurface::ReleaseDC(HDC hDC) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->ReleaseDC(hDC); +} + + +STDMETHODIMP CAggDrawSurface::Restore() +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->Restore(); +} + + +STDMETHODIMP CAggDrawSurface::SetClipper(__in LPDIRECTDRAWCLIPPER lpDDClipper) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->SetClipper(lpDDClipper); +} + + +STDMETHODIMP CAggDrawSurface::SetColorKey(DWORD dwFlags,__in LPDDCOLORKEY lpDDColorKey) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->SetColorKey(dwFlags,lpDDColorKey); +} + + +STDMETHODIMP CAggDrawSurface::SetOverlayPosition(LONG dwX,LONG dwY) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->SetOverlayPosition(dwX,dwY); +} + + +STDMETHODIMP CAggDrawSurface::SetPalette(__in LPDIRECTDRAWPALETTE lpDDPalette) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->SetPalette(lpDDPalette); +} + + +STDMETHODIMP CAggDrawSurface::Unlock(__in LPVOID lpSurfaceData) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->Unlock(lpSurfaceData); +} + + +STDMETHODIMP CAggDrawSurface::UpdateOverlay(__in LPRECT lpSrcRect, + __in LPDIRECTDRAWSURFACE lpDDDestSurface, + __in LPRECT lpDestRect, + DWORD dwFlags, + __in LPDDOVERLAYFX lpDDOverlayFX) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->UpdateOverlay(lpSrcRect,lpDDDestSurface,lpDestRect,dwFlags,lpDDOverlayFX); +} + + +STDMETHODIMP CAggDrawSurface::UpdateOverlayDisplay(DWORD dwFlags) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->UpdateOverlayDisplay(dwFlags); +} + + +STDMETHODIMP CAggDrawSurface::UpdateOverlayZOrder(DWORD dwFlags,__in LPDIRECTDRAWSURFACE lpDDSReference) +{ + ASSERT(m_pDirectDrawSurface); + return m_pDirectDrawSurface->UpdateOverlayZOrder(dwFlags,lpDDSReference); +} + + +// DirectShow must work on multiple platforms. In particular, it also runs on +// Windows NT 3.51 which does not have DirectDraw capabilities. The filters +// cannot therefore link statically to the DirectDraw library. To make their +// lives that little bit easier we provide this class that manages loading +// and unloading the library and creating the initial IDirectDraw interface + +CLoadDirectDraw::CLoadDirectDraw() : + m_pDirectDraw(NULL), + m_hDirectDraw(NULL) +{ +} + + +// Destructor forces unload + +CLoadDirectDraw::~CLoadDirectDraw() +{ + ReleaseDirectDraw(); + + if (m_hDirectDraw) { + NOTE("Unloading library"); + FreeLibrary(m_hDirectDraw); + } +} + + +// We can't be sure that DirectDraw is always available so we can't statically +// link to the library. Therefore we load the library, get the function entry +// point addresses and call them to create the driver objects. We return S_OK +// if we manage to load DirectDraw correctly otherwise we return E_NOINTERFACE +// We initialise a DirectDraw instance by explicitely loading the library and +// calling GetProcAddress on the DirectDrawCreate entry point that it exports + +// On a multi monitor system, we can get the DirectDraw object for any +// monitor (device) with the optional szDevice parameter + +HRESULT CLoadDirectDraw::LoadDirectDraw(__in LPSTR szDevice) +{ + PDRAWCREATE pDrawCreate; + PDRAWENUM pDrawEnum; + LPDIRECTDRAWENUMERATEEXA pDrawEnumEx; + HRESULT hr = NOERROR; + + NOTE("Entering DoLoadDirectDraw"); + + // Is DirectDraw already loaded + + if (m_pDirectDraw) { + NOTE("Already loaded"); + ASSERT(m_hDirectDraw); + return NOERROR; + } + + // Make sure the library is available + + if(!m_hDirectDraw) + { + UINT ErrorMode = SetErrorMode(SEM_NOOPENFILEERRORBOX); + m_hDirectDraw = LoadLibrary(TEXT("DDRAW.DLL")); + SetErrorMode(ErrorMode); + + if (m_hDirectDraw == NULL) { + DbgLog((LOG_ERROR,1,TEXT("Can't load DDRAW.DLL"))); + NOTE("No library"); + return E_NOINTERFACE; + } + } + + // Get the DLL address for the creator function + + pDrawCreate = (PDRAWCREATE)GetProcAddress(m_hDirectDraw,"DirectDrawCreate"); + // force ANSI, we assume it + pDrawEnum = (PDRAWENUM)GetProcAddress(m_hDirectDraw,"DirectDrawEnumerateW"); + pDrawEnumEx = (LPDIRECTDRAWENUMERATEEXA)GetProcAddress(m_hDirectDraw, + "DirectDrawEnumerateExW"); + + // We don't NEED DirectDrawEnumerateEx, that's just for multimon stuff + if (pDrawCreate == NULL || pDrawEnum == NULL) { + DbgLog((LOG_ERROR,1,TEXT("Can't get functions: Create=%x Enum=%x"), + pDrawCreate, pDrawEnum)); + NOTE("No entry point"); + ReleaseDirectDraw(); + return E_NOINTERFACE; + } + + DbgLog((LOG_TRACE,3,TEXT("Creating DDraw for device %s"), + szDevice ? szDevice : "<NULL>")); + + // Create a DirectDraw display provider for this device, using the fancy + // multimon-aware version, if it exists + if (pDrawEnumEx) + m_pDirectDraw = DirectDrawCreateFromDeviceEx(szDevice, pDrawCreate, + pDrawEnumEx); + else + m_pDirectDraw = DirectDrawCreateFromDevice(szDevice, pDrawCreate, + pDrawEnum); + + if (m_pDirectDraw == NULL) { + DbgLog((LOG_ERROR,1,TEXT("Can't create DDraw"))); + NOTE("No instance"); + ReleaseDirectDraw(); + return E_NOINTERFACE; + } + return NOERROR; +} + + +// Called to release any DirectDraw provider we previously loaded. We may be +// called at any time especially when something goes horribly wrong and when +// we need to clean up before returning so we can't guarantee that all state +// variables are consistent so free only those really allocated allocated +// This should only be called once all reference counts have been released + +void CLoadDirectDraw::ReleaseDirectDraw() +{ + NOTE("Releasing DirectDraw driver"); + + // Release any DirectDraw provider interface + + if (m_pDirectDraw) { + NOTE("Releasing instance"); + m_pDirectDraw->Release(); + m_pDirectDraw = NULL; + } + +} + + +// Return NOERROR (S_OK) if DirectDraw has been loaded by this object + +HRESULT CLoadDirectDraw::IsDirectDrawLoaded() +{ + NOTE("Entering IsDirectDrawLoaded"); + + if (m_pDirectDraw == NULL) { + NOTE("DirectDraw not loaded"); + return S_FALSE; + } + return NOERROR; +} + + +// Return the IDirectDraw interface we look after + +LPDIRECTDRAW CLoadDirectDraw::GetDirectDraw() +{ + NOTE("Entering GetDirectDraw"); + + if (m_pDirectDraw == NULL) { + NOTE("No DirectDraw"); + return NULL; + } + + NOTE("Returning DirectDraw"); + m_pDirectDraw->AddRef(); + return m_pDirectDraw; +} + + +// Are we running on Direct Draw version 1? We need to find out as +// we rely on specific bug fixes in DirectDraw 2 for fullscreen playback. To +// find out, we simply see if it supports IDirectDraw2. Only version 2 and +// higher support this. + +BOOL CLoadDirectDraw::IsDirectDrawVersion1() +{ + + if (m_pDirectDraw == NULL) + return FALSE; + + IDirectDraw2 *p = NULL; + HRESULT hr = m_pDirectDraw->QueryInterface(IID_IDirectDraw2, (void **)&p); + if (p) + p->Release(); + if (hr == NOERROR) { + DbgLog((LOG_TRACE,3,TEXT("Direct Draw Version 2 or greater"))); + return FALSE; + } else { + DbgLog((LOG_TRACE,3,TEXT("Direct Draw Version 1"))); + return TRUE; + } +} diff --git a/Src/Plugins/Input/in_dshow/base/videoctl.h b/Src/Plugins/Input/in_dshow/base/videoctl.h new file mode 100644 index 00000000..440d81a7 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/videoctl.h @@ -0,0 +1,168 @@ +//------------------------------------------------------------------------------ +// File: VideoCtl.h +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __VIDEOCTL__ +#define __VIDEOCTL__ + +// These help with property page implementations. The first can be used to +// load any string from a resource file. The buffer to load into is passed +// as an input parameter. The same buffer is the return value if the string +// was found otherwise it returns TEXT(""). The GetDialogSize is passed the +// resource ID of a dialog box and returns the size of it in screen pixels + +#define STR_MAX_LENGTH 256 +LPTSTR WINAPI StringFromResource(__out_ecount(STR_MAX_LENGTH) LPTSTR pBuffer, int iResourceID); + +#ifdef UNICODE +#define WideStringFromResource StringFromResource +LPSTR WINAPI StringFromResource(__out_ecount(STR_MAX_LENGTH) LPSTR pBuffer, int iResourceID); +#else +LPWSTR WINAPI WideStringFromResource(__out_ecount(STR_MAX_LENGTH) LPWSTR pBuffer, int iResourceID); +#endif + + +BOOL WINAPI GetDialogSize(int iResourceID, // Dialog box resource identifier + DLGPROC pDlgProc, // Pointer to dialog procedure + LPARAM lParam, // Any user data wanted in pDlgProc + __out SIZE *pResult);// Returns the size of dialog box + +// Class that aggregates an IDirectDraw interface + +class CAggDirectDraw : public IDirectDraw, public CUnknown +{ +protected: + + LPDIRECTDRAW m_pDirectDraw; + +public: + + DECLARE_IUNKNOWN + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid,__deref_out void **ppv); + + // Constructor and destructor + + CAggDirectDraw(__in_opt LPCTSTR pName,__inout_opt LPUNKNOWN pUnk) : + CUnknown(pName,pUnk), + m_pDirectDraw(NULL) { }; + + virtual CAggDirectDraw::~CAggDirectDraw() { }; + + // Set the object we should be aggregating + void SetDirectDraw(__inout LPDIRECTDRAW pDirectDraw) { + m_pDirectDraw = pDirectDraw; + } + + // IDirectDraw methods + + STDMETHODIMP Compact(); + STDMETHODIMP CreateClipper(DWORD dwFlags,__deref_out LPDIRECTDRAWCLIPPER *lplpDDClipper,__inout_opt IUnknown *pUnkOuter); + STDMETHODIMP CreatePalette(DWORD dwFlags,__in LPPALETTEENTRY lpColorTable,__deref_out LPDIRECTDRAWPALETTE *lplpDDPalette,__inout_opt IUnknown *pUnkOuter); + STDMETHODIMP CreateSurface(__in LPDDSURFACEDESC lpDDSurfaceDesc,__deref_out LPDIRECTDRAWSURFACE *lplpDDSurface,__inout_opt IUnknown *pUnkOuter); + STDMETHODIMP DuplicateSurface(__in LPDIRECTDRAWSURFACE lpDDSurface,__deref_out LPDIRECTDRAWSURFACE *lplpDupDDSurface); + STDMETHODIMP EnumDisplayModes(DWORD dwSurfaceDescCount,__in LPDDSURFACEDESC lplpDDSurfaceDescList,__in LPVOID lpContext,__in LPDDENUMMODESCALLBACK lpEnumCallback); + STDMETHODIMP EnumSurfaces(DWORD dwFlags,__in LPDDSURFACEDESC lpDDSD,__in LPVOID lpContext,__in LPDDENUMSURFACESCALLBACK lpEnumCallback); + STDMETHODIMP FlipToGDISurface(); + STDMETHODIMP GetCaps(__out LPDDCAPS lpDDDriverCaps,__out LPDDCAPS lpDDHELCaps); + STDMETHODIMP GetDisplayMode(__out LPDDSURFACEDESC lpDDSurfaceDesc); + STDMETHODIMP GetFourCCCodes(__inout LPDWORD lpNumCodes,__out_ecount(*lpNumCodes) LPDWORD lpCodes); + STDMETHODIMP GetGDISurface(__deref_out LPDIRECTDRAWSURFACE *lplpGDIDDSurface); + STDMETHODIMP GetMonitorFrequency(__out LPDWORD lpdwFrequency); + STDMETHODIMP GetScanLine(__out LPDWORD lpdwScanLine); + STDMETHODIMP GetVerticalBlankStatus(__out LPBOOL lpblsInVB); + STDMETHODIMP Initialize(__in GUID *lpGUID); + STDMETHODIMP RestoreDisplayMode(); + STDMETHODIMP SetCooperativeLevel(HWND hWnd,DWORD dwFlags); + STDMETHODIMP SetDisplayMode(DWORD dwWidth,DWORD dwHeight,DWORD dwBpp); + STDMETHODIMP WaitForVerticalBlank(DWORD dwFlags,HANDLE hEvent); +}; + + +// Class that aggregates an IDirectDrawSurface interface + +class CAggDrawSurface : public IDirectDrawSurface, public CUnknown +{ +protected: + + LPDIRECTDRAWSURFACE m_pDirectDrawSurface; + +public: + + DECLARE_IUNKNOWN + STDMETHODIMP NonDelegatingQueryInterface(REFIID riid,__deref_out void **ppv); + + // Constructor and destructor + + CAggDrawSurface(__in_opt LPCTSTR pName,__inout_opt LPUNKNOWN pUnk) : + CUnknown(pName,pUnk), + m_pDirectDrawSurface(NULL) { }; + + virtual ~CAggDrawSurface() { }; + + // Set the object we should be aggregating + void SetDirectDrawSurface(__inout LPDIRECTDRAWSURFACE pDirectDrawSurface) { + m_pDirectDrawSurface = pDirectDrawSurface; + } + + // IDirectDrawSurface methods + + STDMETHODIMP AddAttachedSurface(__in LPDIRECTDRAWSURFACE lpDDSAttachedSurface); + STDMETHODIMP AddOverlayDirtyRect(__in LPRECT lpRect); + STDMETHODIMP Blt(__in LPRECT lpDestRect,__in LPDIRECTDRAWSURFACE lpDDSrcSurface,__in LPRECT lpSrcRect,DWORD dwFlags,__in LPDDBLTFX lpDDBltFx); + STDMETHODIMP BltBatch(__in_ecount(dwCount) LPDDBLTBATCH lpDDBltBatch,DWORD dwCount,DWORD dwFlags); + STDMETHODIMP BltFast(DWORD dwX,DWORD dwY,__in LPDIRECTDRAWSURFACE lpDDSrcSurface,__in LPRECT lpSrcRect,DWORD dwTrans); + STDMETHODIMP DeleteAttachedSurface(DWORD dwFlags,__in LPDIRECTDRAWSURFACE lpDDSAttachedSurface); + STDMETHODIMP EnumAttachedSurfaces(__in LPVOID lpContext,__in LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback); + STDMETHODIMP EnumOverlayZOrders(DWORD dwFlags,__in LPVOID lpContext,__in LPDDENUMSURFACESCALLBACK lpfnCallback); + STDMETHODIMP Flip(__in LPDIRECTDRAWSURFACE lpDDSurfaceTargetOverride,DWORD dwFlags); + STDMETHODIMP GetAttachedSurface(__in LPDDSCAPS lpDDSCaps,__deref_out LPDIRECTDRAWSURFACE *lplpDDAttachedSurface); + STDMETHODIMP GetBltStatus(DWORD dwFlags); + STDMETHODIMP GetCaps(__out LPDDSCAPS lpDDSCaps); + STDMETHODIMP GetClipper(__deref_out LPDIRECTDRAWCLIPPER *lplpDDClipper); + STDMETHODIMP GetColorKey(DWORD dwFlags,__out LPDDCOLORKEY lpDDColorKey); + STDMETHODIMP GetDC(__out HDC *lphDC); + STDMETHODIMP GetFlipStatus(DWORD dwFlags); + STDMETHODIMP GetOverlayPosition(__out LPLONG lpdwX,__out LPLONG lpdwY); + STDMETHODIMP GetPalette(__deref_out LPDIRECTDRAWPALETTE *lplpDDPalette); + STDMETHODIMP GetPixelFormat(__out LPDDPIXELFORMAT lpDDPixelFormat); + STDMETHODIMP GetSurfaceDesc(__out LPDDSURFACEDESC lpDDSurfaceDesc); + STDMETHODIMP Initialize(__in LPDIRECTDRAW lpDD,__in LPDDSURFACEDESC lpDDSurfaceDesc); + STDMETHODIMP IsLost(); + STDMETHODIMP Lock(__in LPRECT lpDestRect,__inout LPDDSURFACEDESC lpDDSurfaceDesc,DWORD dwFlags,HANDLE hEvent); + STDMETHODIMP ReleaseDC(HDC hDC); + STDMETHODIMP Restore(); + STDMETHODIMP SetClipper(__in LPDIRECTDRAWCLIPPER lpDDClipper); + STDMETHODIMP SetColorKey(DWORD dwFlags,__in LPDDCOLORKEY lpDDColorKey); + STDMETHODIMP SetOverlayPosition(LONG dwX,LONG dwY); + STDMETHODIMP SetPalette(__in LPDIRECTDRAWPALETTE lpDDPalette); + STDMETHODIMP Unlock(__in LPVOID lpSurfaceData); + STDMETHODIMP UpdateOverlay(__in LPRECT lpSrcRect,__in LPDIRECTDRAWSURFACE lpDDDestSurface,__in LPRECT lpDestRect,DWORD dwFlags,__in LPDDOVERLAYFX lpDDOverlayFX); + STDMETHODIMP UpdateOverlayDisplay(DWORD dwFlags); + STDMETHODIMP UpdateOverlayZOrder(DWORD dwFlags,__in LPDIRECTDRAWSURFACE lpDDSReference); +}; + + +class CLoadDirectDraw +{ + LPDIRECTDRAW m_pDirectDraw; // The DirectDraw driver instance + HINSTANCE m_hDirectDraw; // Handle to the loaded library + +public: + + CLoadDirectDraw(); + ~CLoadDirectDraw(); + + HRESULT LoadDirectDraw(__in LPSTR szDevice); + void ReleaseDirectDraw(); + HRESULT IsDirectDrawLoaded(); + LPDIRECTDRAW GetDirectDraw(); + BOOL IsDirectDrawVersion1(); +}; + +#endif // __VIDEOCTL__ + diff --git a/Src/Plugins/Input/in_dshow/base/vtrans.cpp b/Src/Plugins/Input/in_dshow/base/vtrans.cpp new file mode 100644 index 00000000..3df313ad --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/vtrans.cpp @@ -0,0 +1,468 @@ +//------------------------------------------------------------------------------ +// File: Vtrans.cpp +// +// Desc: DirectShow base classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> +#include <measure.h> +// #include <vtransfr.h> // now in precomp file streams.h + +CVideoTransformFilter::CVideoTransformFilter + ( __in_opt LPCTSTR pName, __inout_opt LPUNKNOWN pUnk, REFCLSID clsid) + : CTransformFilter(pName, pUnk, clsid) + , m_itrLate(0) + , m_nKeyFramePeriod(0) // No QM until we see at least 2 key frames + , m_nFramesSinceKeyFrame(0) + , m_bSkipping(FALSE) + , m_tDecodeStart(0) + , m_itrAvgDecode(300000) // 30mSec - probably allows skipping + , m_bQualityChanged(FALSE) +{ +#ifdef PERF + RegisterPerfId(); +#endif // PERF +} + + +CVideoTransformFilter::~CVideoTransformFilter() +{ + // nothing to do +} + + +// Reset our quality management state + +HRESULT CVideoTransformFilter::StartStreaming() +{ + m_itrLate = 0; + m_nKeyFramePeriod = 0; // No QM until we see at least 2 key frames + m_nFramesSinceKeyFrame = 0; + m_bSkipping = FALSE; + m_tDecodeStart = 0; + m_itrAvgDecode = 300000; // 30mSec - probably allows skipping + m_bQualityChanged = FALSE; + m_bSampleSkipped = FALSE; + return NOERROR; +} + + +// Overriden to reset quality management information + +HRESULT CVideoTransformFilter::EndFlush() +{ + { + // Synchronize + CAutoLock lck(&m_csReceive); + + // Reset our stats + // + // Note - we don't want to call derived classes here, + // we only want to reset our internal variables and this + // is a convenient way to do it + CVideoTransformFilter::StartStreaming(); + } + return CTransformFilter::EndFlush(); +} + + +HRESULT CVideoTransformFilter::AbortPlayback(HRESULT hr) +{ + NotifyEvent(EC_ERRORABORT, hr, 0); + m_pOutput->DeliverEndOfStream(); + return hr; +} + + +// Receive() +// +// Accept a sample from upstream, decide whether to process it +// or drop it. If we process it then get a buffer from the +// allocator of the downstream connection, transform it into the +// new buffer and deliver it to the downstream filter. +// If we decide not to process it then we do not get a buffer. + +// Remember that although this code will notice format changes coming into +// the input pin, it will NOT change its output format if that results +// in the filter needing to make a corresponding output format change. Your +// derived filter will have to take care of that. (eg. a palette change if +// the input and output is an 8 bit format). If the input sample is discarded +// and nothing is sent out for this Receive, please remember to put the format +// change on the first output sample that you actually do send. +// If your filter will produce the same output type even when the input type +// changes, then this base class code will do everything you need. + +HRESULT CVideoTransformFilter::Receive(IMediaSample *pSample) +{ + // If the next filter downstream is the video renderer, then it may + // be able to operate in DirectDraw mode which saves copying the data + // and gives higher performance. In that case the buffer which we + // get from GetDeliveryBuffer will be a DirectDraw buffer, and + // drawing into this buffer draws directly onto the display surface. + // This means that any waiting for the correct time to draw occurs + // during GetDeliveryBuffer, and that once the buffer is given to us + // the video renderer will count it in its statistics as a frame drawn. + // This means that any decision to drop the frame must be taken before + // calling GetDeliveryBuffer. + + ASSERT(CritCheckIn(&m_csReceive)); + AM_MEDIA_TYPE *pmtOut, *pmt; +#ifdef DEBUG + FOURCCMap fccOut; +#endif + HRESULT hr; + ASSERT(pSample); + IMediaSample * pOutSample; + + // If no output pin to deliver to then no point sending us data + ASSERT (m_pOutput != NULL) ; + + // The source filter may dynamically ask us to start transforming from a + // different media type than the one we're using now. If we don't, we'll + // draw garbage. (typically, this is a palette change in the movie, + // but could be something more sinister like the compression type changing, + // or even the video size changing) + +#define rcS1 ((VIDEOINFOHEADER *)(pmt->pbFormat))->rcSource +#define rcT1 ((VIDEOINFOHEADER *)(pmt->pbFormat))->rcTarget + + pSample->GetMediaType(&pmt); + if (pmt != NULL && pmt->pbFormat != NULL) { + + // spew some debug output + ASSERT(!IsEqualGUID(pmt->majortype, GUID_NULL)); +#ifdef DEBUG + fccOut.SetFOURCC(&pmt->subtype); + LONG lCompression = HEADER(pmt->pbFormat)->biCompression; + LONG lBitCount = HEADER(pmt->pbFormat)->biBitCount; + LONG lStride = (HEADER(pmt->pbFormat)->biWidth * lBitCount + 7) / 8; + lStride = (lStride + 3) & ~3; + DbgLog((LOG_TRACE,3,TEXT("*Changing input type on the fly to"))); + DbgLog((LOG_TRACE,3,TEXT("FourCC: %lx Compression: %lx BitCount: %ld"), + fccOut.GetFOURCC(), lCompression, lBitCount)); + DbgLog((LOG_TRACE,3,TEXT("biHeight: %ld rcDst: (%ld, %ld, %ld, %ld)"), + HEADER(pmt->pbFormat)->biHeight, + rcT1.left, rcT1.top, rcT1.right, rcT1.bottom)); + DbgLog((LOG_TRACE,3,TEXT("rcSrc: (%ld, %ld, %ld, %ld) Stride: %ld"), + rcS1.left, rcS1.top, rcS1.right, rcS1.bottom, + lStride)); +#endif + + // now switch to using the new format. I am assuming that the + // derived filter will do the right thing when its media type is + // switched and streaming is restarted. + + StopStreaming(); + m_pInput->CurrentMediaType() = *pmt; + DeleteMediaType(pmt); + // if this fails, playback will stop, so signal an error + hr = StartStreaming(); + if (FAILED(hr)) { + return AbortPlayback(hr); + } + } + + // Now that we have noticed any format changes on the input sample, it's + // OK to discard it. + + if (ShouldSkipFrame(pSample)) { + MSR_NOTE(m_idSkip); + m_bSampleSkipped = TRUE; + return NOERROR; + } + + // Set up the output sample + hr = InitializeOutputSample(pSample, &pOutSample); + + if (FAILED(hr)) { + return hr; + } + + m_bSampleSkipped = FALSE; + + // The renderer may ask us to on-the-fly to start transforming to a + // different format. If we don't obey it, we'll draw garbage + +#define rcS ((VIDEOINFOHEADER *)(pmtOut->pbFormat))->rcSource +#define rcT ((VIDEOINFOHEADER *)(pmtOut->pbFormat))->rcTarget + + pOutSample->GetMediaType(&pmtOut); + if (pmtOut != NULL && pmtOut->pbFormat != NULL) { + + // spew some debug output + ASSERT(!IsEqualGUID(pmtOut->majortype, GUID_NULL)); +#ifdef DEBUG + fccOut.SetFOURCC(&pmtOut->subtype); + LONG lCompression = HEADER(pmtOut->pbFormat)->biCompression; + LONG lBitCount = HEADER(pmtOut->pbFormat)->biBitCount; + LONG lStride = (HEADER(pmtOut->pbFormat)->biWidth * lBitCount + 7) / 8; + lStride = (lStride + 3) & ~3; + DbgLog((LOG_TRACE,3,TEXT("*Changing output type on the fly to"))); + DbgLog((LOG_TRACE,3,TEXT("FourCC: %lx Compression: %lx BitCount: %ld"), + fccOut.GetFOURCC(), lCompression, lBitCount)); + DbgLog((LOG_TRACE,3,TEXT("biHeight: %ld rcDst: (%ld, %ld, %ld, %ld)"), + HEADER(pmtOut->pbFormat)->biHeight, + rcT.left, rcT.top, rcT.right, rcT.bottom)); + DbgLog((LOG_TRACE,3,TEXT("rcSrc: (%ld, %ld, %ld, %ld) Stride: %ld"), + rcS.left, rcS.top, rcS.right, rcS.bottom, + lStride)); +#endif + + // now switch to using the new format. I am assuming that the + // derived filter will do the right thing when its media type is + // switched and streaming is restarted. + + StopStreaming(); + m_pOutput->CurrentMediaType() = *pmtOut; + DeleteMediaType(pmtOut); + hr = StartStreaming(); + + if (SUCCEEDED(hr)) { + // a new format, means a new empty buffer, so wait for a keyframe + // before passing anything on to the renderer. + // !!! a keyframe may never come, so give up after 30 frames + DbgLog((LOG_TRACE,3,TEXT("Output format change means we must wait for a keyframe"))); + m_nWaitForKey = 30; + + // if this fails, playback will stop, so signal an error + } else { + + // Must release the sample before calling AbortPlayback + // because we might be holding the win16 lock or + // ddraw lock + pOutSample->Release(); + AbortPlayback(hr); + return hr; + } + } + + // After a discontinuity, we need to wait for the next key frame + if (pSample->IsDiscontinuity() == S_OK) { + DbgLog((LOG_TRACE,3,TEXT("Non-key discontinuity - wait for keyframe"))); + m_nWaitForKey = 30; + } + + // Start timing the transform (and log it if PERF is defined) + + if (SUCCEEDED(hr)) { + m_tDecodeStart = timeGetTime(); + MSR_START(m_idTransform); + + // have the derived class transform the data + hr = Transform(pSample, pOutSample); + + // Stop the clock (and log it if PERF is defined) + MSR_STOP(m_idTransform); + m_tDecodeStart = timeGetTime()-m_tDecodeStart; + m_itrAvgDecode = m_tDecodeStart*(10000/16) + 15*(m_itrAvgDecode/16); + + // Maybe we're waiting for a keyframe still? + if (m_nWaitForKey) + m_nWaitForKey--; + if (m_nWaitForKey && pSample->IsSyncPoint() == S_OK) + m_nWaitForKey = FALSE; + + // if so, then we don't want to pass this on to the renderer + if (m_nWaitForKey && hr == NOERROR) { + DbgLog((LOG_TRACE,3,TEXT("still waiting for a keyframe"))); + hr = S_FALSE; + } + } + + if (FAILED(hr)) { + DbgLog((LOG_TRACE,1,TEXT("Error from video transform"))); + } else { + // the Transform() function can return S_FALSE to indicate that the + // sample should not be delivered; we only deliver the sample if it's + // really S_OK (same as NOERROR, of course.) + // Try not to return S_FALSE to a direct draw buffer (it's wasteful) + // Try to take the decision earlier - before you get it. + + if (hr == NOERROR) { + hr = m_pOutput->Deliver(pOutSample); + } else { + // S_FALSE returned from Transform is a PRIVATE agreement + // We should return NOERROR from Receive() in this case because returning S_FALSE + // from Receive() means that this is the end of the stream and no more data should + // be sent. + if (S_FALSE == hr) { + + // We must Release() the sample before doing anything + // like calling the filter graph because having the + // sample means we may have the DirectDraw lock + // (== win16 lock on some versions) + pOutSample->Release(); + m_bSampleSkipped = TRUE; + if (!m_bQualityChanged) { + m_bQualityChanged = TRUE; + NotifyEvent(EC_QUALITY_CHANGE,0,0); + } + return NOERROR; + } + } + } + + // release the output buffer. If the connected pin still needs it, + // it will have addrefed it itself. + pOutSample->Release(); + ASSERT(CritCheckIn(&m_csReceive)); + + return hr; +} + + + +BOOL CVideoTransformFilter::ShouldSkipFrame( IMediaSample * pIn) +{ + REFERENCE_TIME trStart, trStopAt; + HRESULT hr = pIn->GetTime(&trStart, &trStopAt); + + // Don't skip frames with no timestamps + if (hr != S_OK) + return FALSE; + + int itrFrame = (int)(trStopAt - trStart); // frame duration + + if(S_OK==pIn->IsSyncPoint()) { + MSR_INTEGER(m_idFrameType, 1); + if ( m_nKeyFramePeriod < m_nFramesSinceKeyFrame ) { + // record the max + m_nKeyFramePeriod = m_nFramesSinceKeyFrame; + } + m_nFramesSinceKeyFrame = 0; + m_bSkipping = FALSE; + } else { + MSR_INTEGER(m_idFrameType, 2); + if ( m_nFramesSinceKeyFrame>m_nKeyFramePeriod + && m_nKeyFramePeriod>0 + ) { + // We haven't seen the key frame yet, but we were clearly being + // overoptimistic about how frequent they are. + m_nKeyFramePeriod = m_nFramesSinceKeyFrame; + } + } + + + // Whatever we might otherwise decide, + // if we are taking only a small fraction of the required frame time to decode + // then any quality problems are actually coming from somewhere else. + // Could be a net problem at the source for instance. In this case there's + // no point in us skipping frames here. + if (m_itrAvgDecode*4>itrFrame) { + + // Don't skip unless we are at least a whole frame late. + // (We would skip B frames if more than 1/2 frame late, but they're safe). + if ( m_itrLate > itrFrame ) { + + // Don't skip unless the anticipated key frame would be no more than + // 1 frame early. If the renderer has not been waiting (we *guess* + // it hasn't because we're late) then it will allow frames to be + // played early by up to a frame. + + // Let T = Stream time from now to anticipated next key frame + // = (frame duration) * (KeyFramePeriod - FramesSinceKeyFrame) + // So we skip if T - Late < one frame i.e. + // (duration) * (freq - FramesSince) - Late < duration + // or (duration) * (freq - FramesSince - 1) < Late + + // We don't dare skip until we have seen some key frames and have + // some idea how often they occur and they are reasonably frequent. + if (m_nKeyFramePeriod>0) { + // It would be crazy - but we could have a stream with key frames + // a very long way apart - and if they are further than about + // 3.5 minutes apart then we could get arithmetic overflow in + // reference time units. Therefore we switch to mSec at this point + int it = (itrFrame/10000) + * (m_nKeyFramePeriod-m_nFramesSinceKeyFrame - 1); + MSR_INTEGER(m_idTimeTillKey, it); + + // For debug - might want to see the details - dump them as scratch pad +#ifdef VTRANSPERF + MSR_INTEGER(0, itrFrame); + MSR_INTEGER(0, m_nFramesSinceKeyFrame); + MSR_INTEGER(0, m_nKeyFramePeriod); +#endif + if (m_itrLate/10000 > it) { + m_bSkipping = TRUE; + // Now we are committed. Once we start skipping, we + // cannot stop until we hit a key frame. + } else { +#ifdef VTRANSPERF + MSR_INTEGER(0, 777770); // not near enough to next key +#endif + } + } else { +#ifdef VTRANSPERF + MSR_INTEGER(0, 777771); // Next key not predictable +#endif + } + } else { +#ifdef VTRANSPERF + MSR_INTEGER(0, 777772); // Less than one frame late + MSR_INTEGER(0, m_itrLate); + MSR_INTEGER(0, itrFrame); +#endif + } + } else { +#ifdef VTRANSPERF + MSR_INTEGER(0, 777773); // Decode time short - not not worth skipping + MSR_INTEGER(0, m_itrAvgDecode); + MSR_INTEGER(0, itrFrame); +#endif + } + + ++m_nFramesSinceKeyFrame; + + if (m_bSkipping) { + // We will count down the lateness as we skip each frame. + // We re-assess each frame. The key frame might not arrive when expected. + // We reset m_itrLate if we get a new Quality message, but actually that's + // not likely because we're not sending frames on to the Renderer. In + // fact if we DID get another one it would mean that there's a long + // pipe between us and the renderer and we might need an altogether + // better strategy to avoid hunting! + m_itrLate = m_itrLate - itrFrame; + } + + MSR_INTEGER(m_idLate, (int)m_itrLate/10000 ); // Note how late we think we are + if (m_bSkipping) { + if (!m_bQualityChanged) { + m_bQualityChanged = TRUE; + NotifyEvent(EC_QUALITY_CHANGE,0,0); + } + } + return m_bSkipping; +} + + +HRESULT CVideoTransformFilter::AlterQuality(Quality q) +{ + // to reduce the amount of 64 bit arithmetic, m_itrLate is an int. + // +, -, >, == etc are not too bad, but * and / are painful. + if (m_itrLate>300000000) { + // Avoid overflow and silliness - more than 30 secs late is already silly + m_itrLate = 300000000; + } else { + m_itrLate = (int)q.Late; + } + // We ignore the other fields + + // We're actually not very good at handling this. In non-direct draw mode + // most of the time can be spent in the renderer which can skip any frame. + // In that case we'd rather the renderer handled things. + // Nevertheless we will keep an eye on it and if we really start getting + // a very long way behind then we will actually skip - but we'll still tell + // the renderer (or whoever is downstream) that they should handle quality. + + return E_FAIL; // Tell the renderer to do his thing. + +} + + + +// This will avoid several hundred useless warnings if compiled -W4 by MS VC++ v4 +#pragma warning(disable:4514) + diff --git a/Src/Plugins/Input/in_dshow/base/vtrans.h b/Src/Plugins/Input/in_dshow/base/vtrans.h new file mode 100644 index 00000000..71223923 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/vtrans.h @@ -0,0 +1,143 @@ +//------------------------------------------------------------------------------ +// File: VTrans.h +// +// Desc: DirectShow base classes - defines a video transform class. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// This class is derived from CTransformFilter, but is specialised to handle +// the requirements of video quality control by frame dropping. +// This is a non-in-place transform, (i.e. it copies the data) such as a decoder. + +class CVideoTransformFilter : public CTransformFilter +{ + public: + + CVideoTransformFilter(__in_opt LPCTSTR, __inout_opt LPUNKNOWN, REFCLSID clsid); + ~CVideoTransformFilter(); + HRESULT EndFlush(); + + // ================================================================= + // ----- override these bits --------------------------------------- + // ================================================================= + // The following methods are in CTransformFilter which is inherited. + // They are mentioned here for completeness + // + // These MUST be supplied in a derived class + // + // NOTE: + // virtual HRESULT Transform(IMediaSample * pIn, IMediaSample *pOut); + // virtual HRESULT CheckInputType(const CMediaType* mtIn) PURE; + // virtual HRESULT CheckTransform + // (const CMediaType* mtIn, const CMediaType* mtOut) PURE; + // static CCOMObject * CreateInstance(LPUNKNOWN, HRESULT *); + // virtual HRESULT DecideBufferSize + // (IMemAllocator * pAllocator, ALLOCATOR_PROPERTIES *pprop) PURE; + // virtual HRESULT GetMediaType(int iPosition, CMediaType *pMediaType) PURE; + // + // These MAY also be overridden + // + // virtual HRESULT StopStreaming(); + // virtual HRESULT SetMediaType(PIN_DIRECTION direction,const CMediaType *pmt); + // virtual HRESULT CheckConnect(PIN_DIRECTION dir,IPin *pPin); + // virtual HRESULT BreakConnect(PIN_DIRECTION dir); + // virtual HRESULT CompleteConnect(PIN_DIRECTION direction,IPin *pReceivePin); + // virtual HRESULT EndOfStream(void); + // virtual HRESULT BeginFlush(void); + // virtual HRESULT EndFlush(void); + // virtual HRESULT NewSegment + // (REFERENCE_TIME tStart,REFERENCE_TIME tStop,double dRate); +#ifdef PERF + + // If you override this - ensure that you register all these ids + // as well as any of your own, + virtual void RegisterPerfId() { + m_idSkip = MSR_REGISTER(TEXT("Video Transform Skip frame")); + m_idFrameType = MSR_REGISTER(TEXT("Video transform frame type")); + m_idLate = MSR_REGISTER(TEXT("Video Transform Lateness")); + m_idTimeTillKey = MSR_REGISTER(TEXT("Video Transform Estd. time to next key")); + CTransformFilter::RegisterPerfId(); + } +#endif + + protected: + + // =========== QUALITY MANAGEMENT IMPLEMENTATION ======================== + // Frames are assumed to come in three types: + // Type 1: an AVI key frame or an MPEG I frame. + // This frame can be decoded with no history. + // Dropping this frame means that no further frame can be decoded + // until the next type 1 frame. + // Type 1 frames are sync points. + // Type 2: an AVI non-key frame or an MPEG P frame. + // This frame cannot be decoded unless the previous type 1 frame was + // decoded and all type 2 frames since have been decoded. + // Dropping this frame means that no further frame can be decoded + // until the next type 1 frame. + // Type 3: An MPEG B frame. + // This frame cannot be decoded unless the previous type 1 or 2 frame + // has been decoded AND the subsequent type 1 or 2 frame has also + // been decoded. (This requires decoding the frames out of sequence). + // Dropping this frame affects no other frames. This implementation + // does not allow for these. All non-sync-point frames are treated + // as being type 2. + // + // The spacing of frames of type 1 in a file is not guaranteed. There MUST + // be a type 1 frame at (well, near) the start of the file in order to start + // decoding at all. After that there could be one every half second or so, + // there could be one at the start of each scene (aka "cut", "shot") or + // there could be no more at all. + // If there is only a single type 1 frame then NO FRAMES CAN BE DROPPED + // without losing all the rest of the movie. There is no way to tell whether + // this is the case, so we find that we are in the gambling business. + // To try to improve the odds, we record the greatest interval between type 1s + // that we have seen and we bet on things being no worse than this in the + // future. + + // You can tell if it's a type 1 frame by calling IsSyncPoint(). + // there is no architected way to test for a type 3, so you should override + // the quality management here if you have B-frames. + + int m_nKeyFramePeriod; // the largest observed interval between type 1 frames + // 1 means every frame is type 1, 2 means every other. + + int m_nFramesSinceKeyFrame; // Used to count frames since the last type 1. + // becomes the new m_nKeyFramePeriod if greater. + + BOOL m_bSkipping; // we are skipping to the next type 1 frame + +#ifdef PERF + int m_idFrameType; // MSR id Frame type. 1=Key, 2="non-key" + int m_idSkip; // MSR id skipping + int m_idLate; // MSR id lateness + int m_idTimeTillKey; // MSR id for guessed time till next key frame. +#endif + + virtual HRESULT StartStreaming(); + + HRESULT AbortPlayback(HRESULT hr); // if something bad happens + + HRESULT Receive(IMediaSample *pSample); + + HRESULT AlterQuality(Quality q); + + BOOL ShouldSkipFrame(IMediaSample * pIn); + + int m_itrLate; // lateness from last Quality message + // (this overflows at 214 secs late). + int m_tDecodeStart; // timeGetTime when decode started. + int m_itrAvgDecode; // Average decode time in reference units. + + BOOL m_bNoSkip; // debug - no skipping. + + // We send an EC_QUALITY_CHANGE notification to the app if we have to degrade. + // We send one when we start degrading, not one for every frame, this means + // we track whether we've sent one yet. + BOOL m_bQualityChanged; + + // When non-zero, don't pass anything to renderer until next keyframe + // If there are few keys, give up and eventually draw something + int m_nWaitForKey; +}; diff --git a/Src/Plugins/Input/in_dshow/base/winctrl.cpp b/Src/Plugins/Input/in_dshow/base/winctrl.cpp new file mode 100644 index 00000000..fd650f76 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/winctrl.cpp @@ -0,0 +1,2081 @@ +//------------------------------------------------------------------------------ +// File: WinCtrl.cpp +// +// Desc: DirectShow base classes - implements video control interface class. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> +#include <intsafe.h> +#include <checkbmi.h> + +// The control interface methods require us to be connected + +#define CheckConnected(pin,code) \ +{ \ + if (pin == NULL) { \ + ASSERT(!TEXT("Pin not set")); \ + } else if (pin->IsConnected() == FALSE) { \ + return (code); \ + } \ +} + +// This checks to see whether the window has a drain. An application can in +// most environments set the owner/parent of windows so that they appear in +// a compound document context (for example). In this case, the application +// would probably like to be told of any keyboard/mouse messages. Therefore +// we pass these messages on untranslated, returning TRUE if we're successful + +BOOL WINAPI PossiblyEatMessage(HWND hwndDrain, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (hwndDrain != NULL && !InSendMessage()) + { + switch (uMsg) + { + case WM_CHAR: + case WM_DEADCHAR: + case WM_KEYDOWN: + case WM_KEYUP: + case WM_LBUTTONDBLCLK: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONDBLCLK: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_MOUSEACTIVATE: + case WM_MOUSEMOVE: + // If we pass this on we don't get any mouse clicks + //case WM_NCHITTEST: + case WM_NCLBUTTONDBLCLK: + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONUP: + case WM_NCMBUTTONDBLCLK: + case WM_NCMBUTTONDOWN: + case WM_NCMBUTTONUP: + case WM_NCMOUSEMOVE: + case WM_NCRBUTTONDBLCLK: + case WM_NCRBUTTONDOWN: + case WM_NCRBUTTONUP: + case WM_RBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_SYSCHAR: + case WM_SYSDEADCHAR: + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + + DbgLog((LOG_TRACE, 2, TEXT("Forwarding %x to drain"))); + PostMessage(hwndDrain, uMsg, wParam, lParam); + + return TRUE; + } + } + return FALSE; +} + + +// This class implements the IVideoWindow control functions (dual interface) +// we support a large number of properties and methods designed to allow the +// client (whether it be an automation controller or a C/C++ application) to +// set and get a number of window related properties such as it's position. +// We also support some methods that duplicate the properties but provide a +// more direct and efficient mechanism as many values may be changed in one + +CBaseControlWindow::CBaseControlWindow( + __inout CBaseFilter *pFilter, // Owning filter + __in CCritSec *pInterfaceLock, // Locking object + __in_opt LPCTSTR pName, // Object description + __inout_opt LPUNKNOWN pUnk, // Normal COM ownership + __inout HRESULT *phr) : // OLE return code + + CBaseVideoWindow(pName,pUnk), + m_pInterfaceLock(pInterfaceLock), + m_hwndOwner(NULL), + m_hwndDrain(NULL), + m_bAutoShow(TRUE), + m_pFilter(pFilter), + m_bCursorHidden(FALSE), + m_pPin(NULL) +{ + ASSERT(m_pFilter); + ASSERT(m_pInterfaceLock); + ASSERT(phr); + m_BorderColour = VIDEO_COLOUR; +} + + +// Set the title caption on the base window, we don't do any field checking +// as we really don't care what title they intend to have. We can always get +// it back again later with GetWindowText. The only other complication is to +// do the necessary string conversions between ANSI and OLE Unicode strings + +STDMETHODIMP CBaseControlWindow::put_Caption(__in BSTR strCaption) +{ + CheckPointer((PVOID)strCaption,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); +#ifdef UNICODE + SetWindowText(m_hwnd, strCaption); +#else + CHAR Caption[CAPTION]; + + WideCharToMultiByte(CP_ACP,0,strCaption,-1,Caption,CAPTION,NULL,NULL); + SetWindowText(m_hwnd, Caption); +#endif + return NOERROR; +} + + +// Get the current base window title caption, once again we do no real field +// checking. We allocate a string for the window title to be filled in with +// which ensures the interface doesn't fiddle around with getting memory. A +// BSTR is a normal C string with the length at position (-1), we use the +// WriteBSTR helper function to create the caption to try and avoid OLE32 + +STDMETHODIMP CBaseControlWindow::get_Caption(__out BSTR *pstrCaption) +{ + CheckPointer(pstrCaption,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + WCHAR WideCaption[CAPTION]; + +#ifdef UNICODE + GetWindowText(m_hwnd,WideCaption,CAPTION); +#else + // Convert the ASCII caption to a UNICODE string + + TCHAR Caption[CAPTION]; + GetWindowText(m_hwnd,Caption,CAPTION); + MultiByteToWideChar(CP_ACP,0,Caption,-1,WideCaption,CAPTION); +#endif + return WriteBSTR(pstrCaption,WideCaption); +} + + +// Set the window style using GWL_EXSTYLE + +STDMETHODIMP CBaseControlWindow::put_WindowStyleEx(long WindowStyleEx) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + + // Should we be taking off WS_EX_TOPMOST + + if (GetWindowLong(m_hwnd,GWL_EXSTYLE) & WS_EX_TOPMOST) { + if ((WindowStyleEx & WS_EX_TOPMOST) == 0) { + SendMessage(m_hwnd,m_ShowStageTop,(WPARAM) FALSE,(LPARAM) 0); + } + } + + // Likewise should we be adding WS_EX_TOPMOST + + if (WindowStyleEx & WS_EX_TOPMOST) { + SendMessage(m_hwnd,m_ShowStageTop,(WPARAM) TRUE,(LPARAM) 0); + WindowStyleEx &= (~WS_EX_TOPMOST); + if (WindowStyleEx == 0) return NOERROR; + } + return DoSetWindowStyle(WindowStyleEx,GWL_EXSTYLE); +} + + +// Gets the current GWL_EXSTYLE base window style + +STDMETHODIMP CBaseControlWindow::get_WindowStyleEx(__out long *pWindowStyleEx) +{ + CheckPointer(pWindowStyleEx,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + return DoGetWindowStyle(pWindowStyleEx,GWL_EXSTYLE); +} + + +// Set the window style using GWL_STYLE + +STDMETHODIMP CBaseControlWindow::put_WindowStyle(long WindowStyle) +{ + // These styles cannot be changed dynamically + + if ((WindowStyle & WS_DISABLED) || + (WindowStyle & WS_ICONIC) || + (WindowStyle & WS_MAXIMIZE) || + (WindowStyle & WS_MINIMIZE) || + (WindowStyle & WS_HSCROLL) || + (WindowStyle & WS_VSCROLL)) { + + return E_INVALIDARG; + } + + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + return DoSetWindowStyle(WindowStyle,GWL_STYLE); +} + + +// Get the current GWL_STYLE base window style + +STDMETHODIMP CBaseControlWindow::get_WindowStyle(__out long *pWindowStyle) +{ + CheckPointer(pWindowStyle,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + return DoGetWindowStyle(pWindowStyle,GWL_STYLE); +} + + +// Change the base window style or the extended styles depending on whether +// WindowLong is GWL_STYLE or GWL_EXSTYLE. We must call SetWindowPos to have +// the window displayed in it's new style after the change which is a little +// tricky if the window is not currently visible as we realise it offscreen. +// In most cases the client will call get_WindowStyle before they call this +// and then AND and OR in extra bit settings according to the requirements + +HRESULT CBaseControlWindow::DoSetWindowStyle(long Style,long WindowLong) +{ + RECT WindowRect; + + // Get the window's visibility before setting the style + BOOL bVisible = IsWindowVisible(m_hwnd); + EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect)); + + // Set the new style flags for the window + SetWindowLong(m_hwnd,WindowLong,Style); + UINT WindowFlags = SWP_SHOWWINDOW | SWP_FRAMECHANGED | SWP_NOACTIVATE; + WindowFlags |= SWP_NOZORDER | SWP_NOSIZE | SWP_NOMOVE; + + // Show the window again in the current position + + if (bVisible == TRUE) { + + SetWindowPos(m_hwnd, // Base window handle + HWND_TOP, // Just a place holder + 0,0,0,0, // Leave size and position + WindowFlags); // Just draw it again + + return NOERROR; + } + + // Move the window offscreen so the user doesn't see the changes + + MoveWindow((HWND) m_hwnd, // Base window handle + GetSystemMetrics(SM_CXSCREEN), // Current desktop width + GetSystemMetrics(SM_CYSCREEN), // Likewise it's height + WIDTH(&WindowRect), // Use the same width + HEIGHT(&WindowRect), // Keep height same to + TRUE); // May as well repaint + + // Now show the previously hidden window + + SetWindowPos(m_hwnd, // Base window handle + HWND_TOP, // Just a place holder + 0,0,0,0, // Leave size and position + WindowFlags); // Just draw it again + + ShowWindow(m_hwnd,SW_HIDE); + + if (GetParent(m_hwnd)) { + + MapWindowPoints(HWND_DESKTOP, GetParent(m_hwnd), (LPPOINT)&WindowRect, 2); + } + + MoveWindow((HWND) m_hwnd, // Base window handle + WindowRect.left, // Existing x coordinate + WindowRect.top, // Existing y coordinate + WIDTH(&WindowRect), // Use the same width + HEIGHT(&WindowRect), // Keep height same to + TRUE); // May as well repaint + + return NOERROR; +} + + +// Get the current base window style (either GWL_STYLE or GWL_EXSTYLE) + +HRESULT CBaseControlWindow::DoGetWindowStyle(__out long *pStyle,long WindowLong) +{ + *pStyle = GetWindowLong(m_hwnd,WindowLong); + return NOERROR; +} + + +// Change the visibility of the base window, this takes the same parameters +// as the ShowWindow Win32 API does, so the client can have the window hidden +// or shown, minimised to an icon, or maximised to play in full screen mode +// We pass the request on to the base window to actually make the change + +STDMETHODIMP CBaseControlWindow::put_WindowState(long WindowState) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + DoShowWindow(WindowState); + return NOERROR; +} + + +// Get the current window state, this function returns a subset of the SW bit +// settings available in ShowWindow, if the window is visible then SW_SHOW is +// set, if it is hidden then the SW_HIDDEN is set, if it is either minimised +// or maximised then the SW_MINIMIZE or SW_MAXIMIZE is set respectively. The +// other SW bit settings are really set commands not readable output values + +STDMETHODIMP CBaseControlWindow::get_WindowState(__out long *pWindowState) +{ + CheckPointer(pWindowState,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + ASSERT(pWindowState); + *pWindowState = FALSE; + + // Is the window visible, a window is termed visible if it is somewhere on + // the current desktop even if it is completely obscured by other windows + // so the flag is a style for each window set with the WS_VISIBLE bit + + if (IsWindowVisible(m_hwnd) == TRUE) { + + // Is the base window iconic + if (IsIconic(m_hwnd) == TRUE) { + *pWindowState |= SW_MINIMIZE; + } + + // Has the window been maximised + else if (IsZoomed(m_hwnd) == TRUE) { + *pWindowState |= SW_MAXIMIZE; + } + + // Window is normal + else { + *pWindowState |= SW_SHOW; + } + + } else { + *pWindowState |= SW_HIDE; + } + return NOERROR; +} + + +// This makes sure that any palette we realise in the base window (through a +// media type or through the overlay interface) is done in the background and +// is therefore mapped to existing device entries rather than taking it over +// as it will do when we this window gets the keyboard focus. An application +// uses this to make sure it doesn't have it's palette removed by the window + +STDMETHODIMP CBaseControlWindow::put_BackgroundPalette(long BackgroundPalette) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cWindowLock(&m_WindowLock); + + // Check this is a valid automation boolean type + + if (BackgroundPalette != OATRUE) { + if (BackgroundPalette != OAFALSE) { + return E_INVALIDARG; + } + } + + // Make sure the window realises any palette it has again + + m_bBackground = (BackgroundPalette == OATRUE ? TRUE : FALSE); + PostMessage(m_hwnd,m_RealizePalette,0,0); + PaintWindow(FALSE); + + return NOERROR; +} + + +// This returns the current background realisation setting + +STDMETHODIMP +CBaseControlWindow::get_BackgroundPalette(__out long *pBackgroundPalette) +{ + CheckPointer(pBackgroundPalette,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cWindowLock(&m_WindowLock); + + // Get the current background palette setting + + *pBackgroundPalette = (m_bBackground == TRUE ? OATRUE : OAFALSE); + return NOERROR; +} + + +// Change the visibility of the base window + +STDMETHODIMP CBaseControlWindow::put_Visible(long Visible) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + + // Check this is a valid automation boolean type + + if (Visible != OATRUE) { + if (Visible != OAFALSE) { + return E_INVALIDARG; + } + } + + // Convert the boolean visibility into SW_SHOW and SW_HIDE + + INT Mode = (Visible == OATRUE ? SW_SHOWNORMAL : SW_HIDE); + DoShowWindow(Mode); + return NOERROR; +} + + +// Return OATRUE if the window is currently visible otherwise OAFALSE + +STDMETHODIMP CBaseControlWindow::get_Visible(__out long *pVisible) +{ + CheckPointer(pVisible,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + + // See if the base window has a WS_VISIBLE style - this will return TRUE + // even if the window is completely obscured by other desktop windows, we + // return FALSE if the window is not showing because of earlier calls + + BOOL Mode = IsWindowVisible(m_hwnd); + *pVisible = (Mode == TRUE ? OATRUE : OAFALSE); + return NOERROR; +} + + +// Change the left position of the base window. This keeps the window width +// and height properties the same so it effectively shunts the window left or +// right accordingly - there is the Width property to change that dimension + +STDMETHODIMP CBaseControlWindow::put_Left(long Left) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + BOOL bSuccess; + RECT WindowRect; + + // Get the current window position in a RECT + EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect)); + + if (GetParent(m_hwnd)) { + + MapWindowPoints(HWND_DESKTOP, GetParent(m_hwnd), (LPPOINT)&WindowRect, 2); + } + + // Adjust the coordinates ready for SetWindowPos, the window rectangle we + // get back from GetWindowRect is in left,top,right and bottom while the + // coordinates SetWindowPos wants are left,top,width and height values + + WindowRect.bottom = WindowRect.bottom - WindowRect.top; + WindowRect.right = WindowRect.right - WindowRect.left; + UINT WindowFlags = SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE; + + bSuccess = SetWindowPos(m_hwnd, // Window handle + HWND_TOP, // Put it at the top + Left, // New left position + WindowRect.top, // Leave top alone + WindowRect.right, // The WIDTH (not right) + WindowRect.bottom, // The HEIGHT (not bottom) + WindowFlags); // Show window options + + if (bSuccess == FALSE) { + return E_INVALIDARG; + } + return NOERROR; +} + + +// Return the current base window left position + +STDMETHODIMP CBaseControlWindow::get_Left(__out long *pLeft) +{ + CheckPointer(pLeft,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + RECT WindowRect; + + EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect)); + *pLeft = WindowRect.left; + return NOERROR; +} + + +// Change the current width of the base window. This property complements the +// left position property so we must keep the left edge constant and expand or +// contract to the right, the alternative would be to change the left edge so +// keeping the right edge constant but this is maybe a little more intuitive + +STDMETHODIMP CBaseControlWindow::put_Width(long Width) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + BOOL bSuccess; + RECT WindowRect; + + // Adjust the coordinates ready for SetWindowPos, the window rectangle we + // get back from GetWindowRect is in left,top,right and bottom while the + // coordinates SetWindowPos wants are left,top,width and height values + + EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect)); + + if (GetParent(m_hwnd)) { + + MapWindowPoints(HWND_DESKTOP, GetParent(m_hwnd), (LPPOINT)&WindowRect, 2); + } + + WindowRect.bottom = WindowRect.bottom - WindowRect.top; + UINT WindowFlags = SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE; + + // This seems to have a bug in that calling SetWindowPos on a window with + // just the width changing causes it to ignore the width that you pass in + // and sets it to a mimimum value of 110 pixels wide (Windows NT 3.51) + + bSuccess = SetWindowPos(m_hwnd, // Window handle + HWND_TOP, // Put it at the top + WindowRect.left, // Leave left alone + WindowRect.top, // Leave top alone + Width, // New WIDTH dimension + WindowRect.bottom, // The HEIGHT (not bottom) + WindowFlags); // Show window options + + if (bSuccess == FALSE) { + return E_INVALIDARG; + } + return NOERROR; +} + + +// Return the current base window width + +STDMETHODIMP CBaseControlWindow::get_Width(__out long *pWidth) +{ + CheckPointer(pWidth,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + RECT WindowRect; + + EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect)); + *pWidth = WindowRect.right - WindowRect.left; + return NOERROR; +} + + +// This allows the client program to change the top position for the window in +// the same way that changing the left position does not affect the width of +// the image so changing the top position does not affect the window height + +STDMETHODIMP CBaseControlWindow::put_Top(long Top) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + BOOL bSuccess; + RECT WindowRect; + + // Get the current window position in a RECT + EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect)); + + if (GetParent(m_hwnd)) { + + MapWindowPoints(HWND_DESKTOP, GetParent(m_hwnd), (LPPOINT)&WindowRect, 2); + } + + // Adjust the coordinates ready for SetWindowPos, the window rectangle we + // get back from GetWindowRect is in left,top,right and bottom while the + // coordinates SetWindowPos wants are left,top,width and height values + + WindowRect.bottom = WindowRect.bottom - WindowRect.top; + WindowRect.right = WindowRect.right - WindowRect.left; + UINT WindowFlags = SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE; + + bSuccess = SetWindowPos(m_hwnd, // Window handle + HWND_TOP, // Put it at the top + WindowRect.left, // Leave left alone + Top, // New top position + WindowRect.right, // The WIDTH (not right) + WindowRect.bottom, // The HEIGHT (not bottom) + WindowFlags); // Show window flags + + if (bSuccess == FALSE) { + return E_INVALIDARG; + } + return NOERROR; +} + + +// Return the current base window top position + +STDMETHODIMP CBaseControlWindow::get_Top(long *pTop) +{ + CheckPointer(pTop,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + RECT WindowRect; + + EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect)); + *pTop = WindowRect.top; + return NOERROR; +} + + +// Change the height of the window, this complements the top property so when +// we change this we must keep the top position for the base window, as said +// before we could keep the bottom and grow upwards although this is perhaps +// a little more intuitive since we already have a top position property + +STDMETHODIMP CBaseControlWindow::put_Height(long Height) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + BOOL bSuccess; + RECT WindowRect; + + // Adjust the coordinates ready for SetWindowPos, the window rectangle we + // get back from GetWindowRect is in left,top,right and bottom while the + // coordinates SetWindowPos wants are left,top,width and height values + + EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect)); + + if (GetParent(m_hwnd)) { + + MapWindowPoints(HWND_DESKTOP, GetParent(m_hwnd), (LPPOINT)&WindowRect, 2); + } + + WindowRect.right = WindowRect.right - WindowRect.left; + UINT WindowFlags = SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE; + + bSuccess = SetWindowPos(m_hwnd, // Window handle + HWND_TOP, // Put it at the top + WindowRect.left, // Leave left alone + WindowRect.top, // Leave top alone + WindowRect.right, // The WIDTH (not right) + Height, // New height dimension + WindowFlags); // Show window flags + + if (bSuccess == FALSE) { + return E_INVALIDARG; + } + return NOERROR; +} + + +// Return the current base window height + +STDMETHODIMP CBaseControlWindow::get_Height(__out long *pHeight) +{ + CheckPointer(pHeight,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + RECT WindowRect; + + EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect)); + *pHeight = WindowRect.bottom - WindowRect.top; + return NOERROR; +} + + +// This can be called to change the owning window. Setting the owner is done +// through this function, however to make the window a true child window the +// style must also be set to WS_CHILD. After resetting the owner to NULL an +// application should also set the style to WS_OVERLAPPED | WS_CLIPCHILDREN. + +// We cannot lock the object here because the SetParent causes an interthread +// SendMessage to the owner window. If they are in GetState we will sit here +// incomplete with the critical section locked therefore blocking out source +// filter threads from accessing us. Because the source thread can't enter us +// it can't get buffers or call EndOfStream so the GetState will not complete + +STDMETHODIMP CBaseControlWindow::put_Owner(OAHWND Owner) +{ + // Check we are connected otherwise reject the call + + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + m_hwndOwner = (HWND) Owner; + HWND hwndParent = m_hwndOwner; + + // Add or remove WS_CHILD as appropriate + + LONG Style = GetWindowLong(m_hwnd,GWL_STYLE); + if (Owner == NULL) { + Style &= (~WS_CHILD); + } else { + Style |= (WS_CHILD); + } + SetWindowLong(m_hwnd,GWL_STYLE,Style); + + // Don't call this with the filter locked + + SetParent(m_hwnd,hwndParent); + + PaintWindow(TRUE); + NOTE1("Changed parent %lx",hwndParent); + + return NOERROR; +} + + +// This complements the put_Owner to get the current owning window property +// we always return NOERROR although the returned window handle may be NULL +// to indicate no owning window (the desktop window doesn't qualify as one) +// If an application sets the owner we call SetParent, however that returns +// NULL until the WS_CHILD bit is set on, so we store the owner internally + +STDMETHODIMP CBaseControlWindow::get_Owner(__out OAHWND *Owner) +{ + CheckPointer(Owner,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + *Owner = (OAHWND) m_hwndOwner; + return NOERROR; +} + + +// And renderer supporting IVideoWindow may have an HWND set who will get any +// keyboard and mouse messages we receive posted on to them. This is separate +// from setting an owning window. By separating the two, applications may get +// messages sent on even when they have set no owner (perhaps it's maximised) + +STDMETHODIMP CBaseControlWindow::put_MessageDrain(OAHWND Drain) +{ + // Check we are connected otherwise reject the call + + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + m_hwndDrain = (HWND) Drain; + return NOERROR; +} + + +// Return the current message drain + +STDMETHODIMP CBaseControlWindow::get_MessageDrain(__out OAHWND *Drain) +{ + CheckPointer(Drain,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + *Drain = (OAHWND) m_hwndDrain; + return NOERROR; +} + + +// This is called by the filter graph to inform us of a message we should know +// is being sent to our owning window. We have this because as a child window +// we do not get certain messages that are only sent to top level windows. We +// must see the palette changed/changing/query messages so that we know if we +// have the foreground palette or not. We pass the message on to our window +// using SendMessage - this will cause an interthread send message to occur + +STDMETHODIMP +CBaseControlWindow::NotifyOwnerMessage(OAHWND hwnd, // Window handle + long uMsg, // Message ID + LONG_PTR wParam, // Parameters + LONG_PTR lParam) // for message +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + + // Only interested in these Windows messages + + switch (uMsg) { + + case WM_SYSCOLORCHANGE: + case WM_PALETTECHANGED: + case WM_PALETTEISCHANGING: + case WM_QUERYNEWPALETTE: + case WM_DEVMODECHANGE: + case WM_DISPLAYCHANGE: + case WM_ACTIVATEAPP: + + // If we do not have an owner then ignore + + if (m_hwndOwner == NULL) { + return NOERROR; + } + SendMessage(m_hwnd,uMsg,(WPARAM)wParam,(LPARAM)lParam); + break; + + // do NOT fwd WM_MOVE. the parameters are the location of the parent + // window, NOT what the renderer should be looking at. But we need + // to make sure the overlay is moved with the parent window, so we + // do this. + case WM_MOVE: + PostMessage(m_hwnd,WM_PAINT,0,0); + break; + } + return NOERROR; +} + + +// Allow an application to have us set the base window in the foreground. We +// have this because it is difficult for one thread to do do this to a window +// owned by another thread. We ask the base window class to do the real work + +STDMETHODIMP CBaseControlWindow::SetWindowForeground(long Focus) +{ + // Check this is a valid automation boolean type + + if (Focus != OATRUE) { + if (Focus != OAFALSE) { + return E_INVALIDARG; + } + } + + // We shouldn't lock as this sends a message + + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + BOOL bFocus = (Focus == OATRUE ? TRUE : FALSE); + DoSetWindowForeground(bFocus); + + return NOERROR; +} + + +// This allows a client to set the complete window size and position in one +// atomic operation. The same affect can be had by changing each dimension +// in turn through their individual properties although some flashing will +// occur as each of them gets updated (they are better set at design time) + +STDMETHODIMP +CBaseControlWindow::SetWindowPosition(long Left,long Top,long Width,long Height) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + BOOL bSuccess; + + // Set the new size and position + UINT WindowFlags = SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE; + + ASSERT(IsWindow(m_hwnd)); + bSuccess = SetWindowPos(m_hwnd, // Window handle + HWND_TOP, // Put it at the top + Left, // Left position + Top, // Top position + Width, // Window width + Height, // Window height + WindowFlags); // Show window flags + ASSERT(bSuccess); +#ifdef DEBUG + DbgLog((LOG_TRACE, 1, TEXT("SWP failed error %d"), GetLastError())); +#endif + if (bSuccess == FALSE) { + return E_INVALIDARG; + } + return NOERROR; +} + + +// This complements the SetWindowPosition to return the current window place +// in device coordinates. As before the same information can be retrived by +// calling the property get functions individually but this is atomic and is +// therefore more suitable to a live environment rather than design time + +STDMETHODIMP +CBaseControlWindow::GetWindowPosition(__out long *pLeft,__out long *pTop,__out long *pWidth,__out long *pHeight) +{ + // Should check the pointers are not NULL + + CheckPointer(pLeft,E_POINTER); + CheckPointer(pTop,E_POINTER); + CheckPointer(pWidth,E_POINTER); + CheckPointer(pHeight,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + RECT WindowRect; + + // Get the current window coordinates + + EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect)); + + // Convert the RECT into left,top,width and height values + + *pLeft = WindowRect.left; + *pTop = WindowRect.top; + *pWidth = WindowRect.right - WindowRect.left; + *pHeight = WindowRect.bottom - WindowRect.top; + + return NOERROR; +} + + +// When a window is maximised or iconic calling GetWindowPosition will return +// the current window position (likewise for the properties). However if the +// restored size (ie the size we'll return to when normally shown) is needed +// then this should be used. When in a normal position (neither iconic nor +// maximised) then this returns the same coordinates as GetWindowPosition + +STDMETHODIMP +CBaseControlWindow::GetRestorePosition(__out long *pLeft,__out long *pTop,__out long *pWidth,__out long *pHeight) +{ + // Should check the pointers are not NULL + + CheckPointer(pLeft,E_POINTER); + CheckPointer(pTop,E_POINTER); + CheckPointer(pWidth,E_POINTER); + CheckPointer(pHeight,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + + // Use GetWindowPlacement to find the restore position + + WINDOWPLACEMENT Place; + Place.length = sizeof(WINDOWPLACEMENT); + EXECUTE_ASSERT(GetWindowPlacement(m_hwnd,&Place)); + + RECT WorkArea; + + // We must take into account any task bar present + + if (SystemParametersInfo(SPI_GETWORKAREA,0,&WorkArea,FALSE) == TRUE) { + if (GetParent(m_hwnd) == NULL) { + Place.rcNormalPosition.top += WorkArea.top; + Place.rcNormalPosition.bottom += WorkArea.top; + Place.rcNormalPosition.left += WorkArea.left; + Place.rcNormalPosition.right += WorkArea.left; + } + } + + // Convert the RECT into left,top,width and height values + + *pLeft = Place.rcNormalPosition.left; + *pTop = Place.rcNormalPosition.top; + *pWidth = Place.rcNormalPosition.right - Place.rcNormalPosition.left; + *pHeight = Place.rcNormalPosition.bottom - Place.rcNormalPosition.top; + + return NOERROR; +} + + +// Return the current border colour, if we are playing something to a subset +// of the base window display there is an outside area exposed. The default +// action is to paint this colour in the Windows background colour (defined +// as value COLOR_WINDOW) We reset to this default when we're disconnected + +STDMETHODIMP CBaseControlWindow::get_BorderColor(__out long *Color) +{ + CheckPointer(Color,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + *Color = (long) m_BorderColour; + return NOERROR; +} + + +// This can be called to set the current border colour + +STDMETHODIMP CBaseControlWindow::put_BorderColor(long Color) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + + // Have the window repainted with the new border colour + + m_BorderColour = (COLORREF) Color; + PaintWindow(TRUE); + return NOERROR; +} + + +// Delegate fullscreen handling to plug in distributor + +STDMETHODIMP CBaseControlWindow::get_FullScreenMode(__out long *FullScreenMode) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CheckPointer(FullScreenMode,E_POINTER); + return E_NOTIMPL; +} + + +// Delegate fullscreen handling to plug in distributor + +STDMETHODIMP CBaseControlWindow::put_FullScreenMode(long FullScreenMode) +{ + return E_NOTIMPL; +} + + +// This sets the auto show property, this property causes the base window to +// be displayed whenever we change state. This allows an application to have +// to do nothing to have the window appear but still allow them to change the +// default behaviour if for example they want to keep it hidden for longer + +STDMETHODIMP CBaseControlWindow::put_AutoShow(long AutoShow) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + + // Check this is a valid automation boolean type + + if (AutoShow != OATRUE) { + if (AutoShow != OAFALSE) { + return E_INVALIDARG; + } + } + + m_bAutoShow = (AutoShow == OATRUE ? TRUE : FALSE); + return NOERROR; +} + + +// This can be called to get the current auto show flag. The flag is updated +// when we connect and disconnect and through this interface all of which are +// controlled and serialised by means of the main renderer critical section + +STDMETHODIMP CBaseControlWindow::get_AutoShow(__out long *AutoShow) +{ + CheckPointer(AutoShow,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + *AutoShow = (m_bAutoShow == TRUE ? OATRUE : OAFALSE); + return NOERROR; +} + + +// Return the minimum ideal image size for the current video. This may differ +// to the actual video dimensions because we may be using DirectDraw hardware +// that has specific stretching requirements. For example the Cirrus Logic +// cards have a minimum stretch factor depending on the overlay surface size + +STDMETHODIMP +CBaseControlWindow::GetMinIdealImageSize(__out long *pWidth,__out long *pHeight) +{ + CheckPointer(pWidth,E_POINTER); + CheckPointer(pHeight,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + FILTER_STATE State; + + // Must not be stopped for this to work correctly + + m_pFilter->GetState(0,&State); + if (State == State_Stopped) { + return VFW_E_WRONG_STATE; + } + + RECT DefaultRect = GetDefaultRect(); + *pWidth = WIDTH(&DefaultRect); + *pHeight = HEIGHT(&DefaultRect); + return NOERROR; +} + + +// Return the maximum ideal image size for the current video. This may differ +// to the actual video dimensions because we may be using DirectDraw hardware +// that has specific stretching requirements. For example the Cirrus Logic +// cards have a maximum stretch factor depending on the overlay surface size + +STDMETHODIMP +CBaseControlWindow::GetMaxIdealImageSize(__out long *pWidth,__out long *pHeight) +{ + CheckPointer(pWidth,E_POINTER); + CheckPointer(pHeight,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + FILTER_STATE State; + + // Must not be stopped for this to work correctly + + m_pFilter->GetState(0,&State); + if (State == State_Stopped) { + return VFW_E_WRONG_STATE; + } + + RECT DefaultRect = GetDefaultRect(); + *pWidth = WIDTH(&DefaultRect); + *pHeight = HEIGHT(&DefaultRect); + return NOERROR; +} + + +// Allow an application to hide the cursor on our window + +STDMETHODIMP +CBaseControlWindow::HideCursor(long HideCursor) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + + // Check this is a valid automation boolean type + + if (HideCursor != OATRUE) { + if (HideCursor != OAFALSE) { + return E_INVALIDARG; + } + } + + m_bCursorHidden = (HideCursor == OATRUE ? TRUE : FALSE); + return NOERROR; +} + + +// Returns whether we have the cursor hidden or not + +STDMETHODIMP CBaseControlWindow::IsCursorHidden(__out long *CursorHidden) +{ + CheckPointer(CursorHidden,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + *CursorHidden = (m_bCursorHidden == TRUE ? OATRUE : OAFALSE); + return NOERROR; +} + + +// This class implements the IBasicVideo control functions (dual interface) +// we support a large number of properties and methods designed to allow the +// client (whether it be an automation controller or a C/C++ application) to +// set and get a number of video related properties such as the native video +// size. We support some methods that duplicate the properties but provide a +// more direct and efficient mechanism as many values may be changed in one + +CBaseControlVideo::CBaseControlVideo( + __inout CBaseFilter *pFilter, // Owning filter + __in CCritSec *pInterfaceLock, // Locking object + __in_opt LPCTSTR pName, // Object description + __inout_opt LPUNKNOWN pUnk, // Normal COM ownership + __inout HRESULT *phr) : // OLE return code + + CBaseBasicVideo(pName,pUnk), + m_pFilter(pFilter), + m_pInterfaceLock(pInterfaceLock), + m_pPin(NULL) +{ + ASSERT(m_pFilter); + ASSERT(m_pInterfaceLock); + ASSERT(phr); +} + +// Return an approximate average time per frame + +STDMETHODIMP CBaseControlVideo::get_AvgTimePerFrame(__out REFTIME *pAvgTimePerFrame) +{ + CheckPointer(pAvgTimePerFrame,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + + VIDEOINFOHEADER *pVideoInfo = GetVideoFormat(); + if (pVideoInfo == NULL) + return E_OUTOFMEMORY; + COARefTime AvgTime(pVideoInfo->AvgTimePerFrame); + *pAvgTimePerFrame = (REFTIME) AvgTime; + + return NOERROR; +} + + +// Return an approximate bit rate for the video + +STDMETHODIMP CBaseControlVideo::get_BitRate(__out long *pBitRate) +{ + CheckPointer(pBitRate,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + + VIDEOINFOHEADER *pVideoInfo = GetVideoFormat(); + if (pVideoInfo == NULL) + return E_OUTOFMEMORY; + *pBitRate = pVideoInfo->dwBitRate; + return NOERROR; +} + + +// Return an approximate bit error rate + +STDMETHODIMP CBaseControlVideo::get_BitErrorRate(__out long *pBitErrorRate) +{ + CheckPointer(pBitErrorRate,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + + VIDEOINFOHEADER *pVideoInfo = GetVideoFormat(); + if (pVideoInfo == NULL) + return E_OUTOFMEMORY; + *pBitErrorRate = pVideoInfo->dwBitErrorRate; + return NOERROR; +} + + +// This returns the current video width + +STDMETHODIMP CBaseControlVideo::get_VideoWidth(__out long *pVideoWidth) +{ + CheckPointer(pVideoWidth,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + + VIDEOINFOHEADER *pVideoInfo = GetVideoFormat(); + if (pVideoInfo == NULL) + return E_OUTOFMEMORY; + *pVideoWidth = pVideoInfo->bmiHeader.biWidth; + return NOERROR; +} + + +// This returns the current video height + +STDMETHODIMP CBaseControlVideo::get_VideoHeight(__out long *pVideoHeight) +{ + CheckPointer(pVideoHeight,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + + VIDEOINFOHEADER *pVideoInfo = GetVideoFormat(); + if (pVideoInfo == NULL) + return E_OUTOFMEMORY; + *pVideoHeight = pVideoInfo->bmiHeader.biHeight; + return NOERROR; +} + + +// This returns the current palette the video is using as an array allocated +// by the user. To remain consistent we use PALETTEENTRY fields to return the +// colours in rather than RGBQUADs that multimedia decided to use. The memory +// is allocated by the user so we simple copy each in turn. We check that the +// number of entries requested and the start position offset are both valid +// If the number of entries evaluates to zero then we return an S_FALSE code + +STDMETHODIMP CBaseControlVideo::GetVideoPaletteEntries(long StartIndex, + long Entries, + __out long *pRetrieved, + __out_ecount_part(Entries, *pRetrieved) long *pPalette) +{ + CheckPointer(pRetrieved,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + CMediaType MediaType; + + // Get the video format from the derived class + + VIDEOINFOHEADER *pVideoInfo = GetVideoFormat(); + if (pVideoInfo == NULL) + return E_OUTOFMEMORY; + BITMAPINFOHEADER *pHeader = HEADER(pVideoInfo); + + // Is the current format palettised + + if (PALETTISED(pVideoInfo) == FALSE) { + *pRetrieved = 0; + return VFW_E_NO_PALETTE_AVAILABLE; + } + + // Do they just want to know how many are available + + if (pPalette == NULL) { + *pRetrieved = pHeader->biClrUsed; + return NOERROR; + } + + // Make sure the start position is a valid offset + + if (StartIndex >= (LONG) pHeader->biClrUsed || StartIndex < 0) { + *pRetrieved = 0; + return E_INVALIDARG; + } + + // Correct the number we can retrieve + + LONG Available = (LONG) pHeader->biClrUsed - StartIndex; + *pRetrieved = max(0,min(Available,Entries)); + if (*pRetrieved == 0) { + return S_FALSE; + } + + // Copy the palette entries to the output buffer + + PALETTEENTRY *pEntries = (PALETTEENTRY *) pPalette; + RGBQUAD *pColours = COLORS(pVideoInfo) + StartIndex; + + for (LONG Count = 0;Count < *pRetrieved;Count++) { + pEntries[Count].peRed = pColours[Count].rgbRed; + pEntries[Count].peGreen = pColours[Count].rgbGreen; + pEntries[Count].peBlue = pColours[Count].rgbBlue; + pEntries[Count].peFlags = 0; + } + return NOERROR; +} + + +// This returns the current video dimensions as a method rather than a number +// of individual property get calls. For the same reasons as said before we +// cannot access the renderer media type directly as the window object thread +// may be updating it since dynamic format changes may change these values + +STDMETHODIMP CBaseControlVideo::GetVideoSize(__out long *pWidth,__out long *pHeight) +{ + CheckPointer(pWidth,E_POINTER); + CheckPointer(pHeight,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + + // Get the video format from the derived class + VIDEOINFOHEADER *pVideoInfo = GetVideoFormat(); + if (pVideoInfo == NULL) + return E_OUTOFMEMORY; + *pWidth = pVideoInfo->bmiHeader.biWidth; + *pHeight = pVideoInfo->bmiHeader.biHeight; + return NOERROR; +} + + +// Set the source video rectangle as left,top,right and bottom coordinates +// rather than left,top,width and height as per OLE automation interfaces +// Then pass the rectangle on to the window object to set the source + +STDMETHODIMP +CBaseControlVideo::SetSourcePosition(long Left,long Top,long Width,long Height) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT SourceRect; + SourceRect.left = Left; + SourceRect.top = Top; + SourceRect.right = Left + Width; + SourceRect.bottom = Top + Height; + + // Check the source rectangle is valid + + HRESULT hr = CheckSourceRect(&SourceRect); + if (FAILED(hr)) { + return hr; + } + + // Now set the source rectangle + + hr = SetSourceRect(&SourceRect); + if (FAILED(hr)) { + return hr; + } + return OnUpdateRectangles(); +} + + +// Return the source rectangle in left,top,width and height rather than the +// left,top,right and bottom values that RECT uses (and which the window +// object returns through GetSourceRect) which requires a little work + +STDMETHODIMP +CBaseControlVideo::GetSourcePosition(__out long *pLeft,__out long *pTop,__out long *pWidth,__out long *pHeight) +{ + // Should check the pointers are non NULL + + CheckPointer(pLeft,E_POINTER); + CheckPointer(pTop,E_POINTER); + CheckPointer(pWidth,E_POINTER); + CheckPointer(pHeight,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + RECT SourceRect; + + CAutoLock cInterfaceLock(m_pInterfaceLock); + GetSourceRect(&SourceRect); + + *pLeft = SourceRect.left; + *pTop = SourceRect.top; + *pWidth = WIDTH(&SourceRect); + *pHeight = HEIGHT(&SourceRect); + + return NOERROR; +} + + +// Set the video destination as left,top,right and bottom coordinates rather +// than the left,top,width and height uses as per OLE automation interfaces +// Then pass the rectangle on to the window object to set the destination + +STDMETHODIMP +CBaseControlVideo::SetDestinationPosition(long Left,long Top,long Width,long Height) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT DestinationRect; + + DestinationRect.left = Left; + DestinationRect.top = Top; + DestinationRect.right = Left + Width; + DestinationRect.bottom = Top + Height; + + // Check the target rectangle is valid + + HRESULT hr = CheckTargetRect(&DestinationRect); + if (FAILED(hr)) { + return hr; + } + + // Now set the new target rectangle + + hr = SetTargetRect(&DestinationRect); + if (FAILED(hr)) { + return hr; + } + return OnUpdateRectangles(); +} + + +// Return the destination rectangle in left,top,width and height rather than +// the left,top,right and bottom values that RECT uses (and which the window +// object returns through GetDestinationRect) which requires a little work + +STDMETHODIMP +CBaseControlVideo::GetDestinationPosition(__out long *pLeft,__out long *pTop,__out long *pWidth,__out long *pHeight) +{ + // Should check the pointers are not NULL + + CheckPointer(pLeft,E_POINTER); + CheckPointer(pTop,E_POINTER); + CheckPointer(pWidth,E_POINTER); + CheckPointer(pHeight,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + RECT DestinationRect; + + CAutoLock cInterfaceLock(m_pInterfaceLock); + GetTargetRect(&DestinationRect); + + *pLeft = DestinationRect.left; + *pTop = DestinationRect.top; + *pWidth = WIDTH(&DestinationRect); + *pHeight = HEIGHT(&DestinationRect); + + return NOERROR; +} + + +// Set the source left position, the source rectangle we get back from the +// window object is a true rectangle in left,top,right and bottom positions +// so all we have to do is to update the left position and pass it back. We +// must keep the current width constant when we're updating this property + +STDMETHODIMP CBaseControlVideo::put_SourceLeft(long SourceLeft) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT SourceRect; + GetSourceRect(&SourceRect); + SourceRect.right = SourceLeft + WIDTH(&SourceRect); + SourceRect.left = SourceLeft; + + // Check the source rectangle is valid + + HRESULT hr = CheckSourceRect(&SourceRect); + if (FAILED(hr)) { + return hr; + } + + // Now set the source rectangle + + hr = SetSourceRect(&SourceRect); + if (FAILED(hr)) { + return hr; + } + return OnUpdateRectangles(); +} + + +// Return the current left source video position + +STDMETHODIMP CBaseControlVideo::get_SourceLeft(__out long *pSourceLeft) +{ + CheckPointer(pSourceLeft,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT SourceRect; + + GetSourceRect(&SourceRect); + *pSourceLeft = SourceRect.left; + return NOERROR; +} + + +// Set the source width, we get the current source rectangle and then update +// the right position to be the left position (thereby keeping it constant) +// plus the new source width we are passed in (it expands to the right) + +STDMETHODIMP CBaseControlVideo::put_SourceWidth(long SourceWidth) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT SourceRect; + GetSourceRect(&SourceRect); + SourceRect.right = SourceRect.left + SourceWidth; + + // Check the source rectangle is valid + + HRESULT hr = CheckSourceRect(&SourceRect); + if (FAILED(hr)) { + return hr; + } + + // Now set the source rectangle + + hr = SetSourceRect(&SourceRect); + if (FAILED(hr)) { + return hr; + } + return OnUpdateRectangles(); +} + + +// Return the current source width + +STDMETHODIMP CBaseControlVideo::get_SourceWidth(__out long *pSourceWidth) +{ + CheckPointer(pSourceWidth,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT SourceRect; + + GetSourceRect(&SourceRect); + *pSourceWidth = WIDTH(&SourceRect); + return NOERROR; +} + + +// Set the source top position - changing this property does not affect the +// current source height. So changing this shunts the source rectangle up and +// down appropriately. Changing the height complements this functionality by +// keeping the top position constant and simply changing the source height + +STDMETHODIMP CBaseControlVideo::put_SourceTop(long SourceTop) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT SourceRect; + GetSourceRect(&SourceRect); + SourceRect.bottom = SourceTop + HEIGHT(&SourceRect); + SourceRect.top = SourceTop; + + // Check the source rectangle is valid + + HRESULT hr = CheckSourceRect(&SourceRect); + if (FAILED(hr)) { + return hr; + } + + // Now set the source rectangle + + hr = SetSourceRect(&SourceRect); + if (FAILED(hr)) { + return hr; + } + return OnUpdateRectangles(); +} + + +// Return the current top position + +STDMETHODIMP CBaseControlVideo::get_SourceTop(__out long *pSourceTop) +{ + CheckPointer(pSourceTop,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT SourceRect; + + GetSourceRect(&SourceRect); + *pSourceTop = SourceRect.top; + return NOERROR; +} + + +// Set the source height + +STDMETHODIMP CBaseControlVideo::put_SourceHeight(long SourceHeight) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT SourceRect; + GetSourceRect(&SourceRect); + SourceRect.bottom = SourceRect.top + SourceHeight; + + // Check the source rectangle is valid + + HRESULT hr = CheckSourceRect(&SourceRect); + if (FAILED(hr)) { + return hr; + } + + // Now set the source rectangle + + hr = SetSourceRect(&SourceRect); + if (FAILED(hr)) { + return hr; + } + return OnUpdateRectangles(); +} + + +// Return the current source height + +STDMETHODIMP CBaseControlVideo::get_SourceHeight(__out long *pSourceHeight) +{ + CheckPointer(pSourceHeight,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT SourceRect; + + GetSourceRect(&SourceRect); + *pSourceHeight = HEIGHT(&SourceRect); + return NOERROR; +} + + +// Set the target left position, the target rectangle we get back from the +// window object is a true rectangle in left,top,right and bottom positions +// so all we have to do is to update the left position and pass it back. We +// must keep the current width constant when we're updating this property + +STDMETHODIMP CBaseControlVideo::put_DestinationLeft(long DestinationLeft) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT DestinationRect; + GetTargetRect(&DestinationRect); + DestinationRect.right = DestinationLeft + WIDTH(&DestinationRect); + DestinationRect.left = DestinationLeft; + + // Check the target rectangle is valid + + HRESULT hr = CheckTargetRect(&DestinationRect); + if (FAILED(hr)) { + return hr; + } + + // Now set the new target rectangle + + hr = SetTargetRect(&DestinationRect); + if (FAILED(hr)) { + return hr; + } + return OnUpdateRectangles(); +} + + +// Return the left position for the destination rectangle + +STDMETHODIMP CBaseControlVideo::get_DestinationLeft(__out long *pDestinationLeft) +{ + CheckPointer(pDestinationLeft,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT DestinationRect; + + GetTargetRect(&DestinationRect); + *pDestinationLeft = DestinationRect.left; + return NOERROR; +} + + +// Set the destination width + +STDMETHODIMP CBaseControlVideo::put_DestinationWidth(long DestinationWidth) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT DestinationRect; + GetTargetRect(&DestinationRect); + DestinationRect.right = DestinationRect.left + DestinationWidth; + + // Check the target rectangle is valid + + HRESULT hr = CheckTargetRect(&DestinationRect); + if (FAILED(hr)) { + return hr; + } + + // Now set the new target rectangle + + hr = SetTargetRect(&DestinationRect); + if (FAILED(hr)) { + return hr; + } + return OnUpdateRectangles(); +} + + +// Return the width for the destination rectangle + +STDMETHODIMP CBaseControlVideo::get_DestinationWidth(__out long *pDestinationWidth) +{ + CheckPointer(pDestinationWidth,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT DestinationRect; + + GetTargetRect(&DestinationRect); + *pDestinationWidth = WIDTH(&DestinationRect); + return NOERROR; +} + + +// Set the target top position - changing this property does not affect the +// current target height. So changing this shunts the target rectangle up and +// down appropriately. Changing the height complements this functionality by +// keeping the top position constant and simply changing the target height + +STDMETHODIMP CBaseControlVideo::put_DestinationTop(long DestinationTop) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT DestinationRect; + GetTargetRect(&DestinationRect); + DestinationRect.bottom = DestinationTop + HEIGHT(&DestinationRect); + DestinationRect.top = DestinationTop; + + // Check the target rectangle is valid + + HRESULT hr = CheckTargetRect(&DestinationRect); + if (FAILED(hr)) { + return hr; + } + + // Now set the new target rectangle + + hr = SetTargetRect(&DestinationRect); + if (FAILED(hr)) { + return hr; + } + return OnUpdateRectangles(); +} + + +// Return the top position for the destination rectangle + +STDMETHODIMP CBaseControlVideo::get_DestinationTop(__out long *pDestinationTop) +{ + CheckPointer(pDestinationTop,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT DestinationRect; + + GetTargetRect(&DestinationRect); + *pDestinationTop = DestinationRect.top; + return NOERROR; +} + + +// Set the destination height + +STDMETHODIMP CBaseControlVideo::put_DestinationHeight(long DestinationHeight) +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT DestinationRect; + GetTargetRect(&DestinationRect); + DestinationRect.bottom = DestinationRect.top + DestinationHeight; + + // Check the target rectangle is valid + + HRESULT hr = CheckTargetRect(&DestinationRect); + if (FAILED(hr)) { + return hr; + } + + // Now set the new target rectangle + + hr = SetTargetRect(&DestinationRect); + if (FAILED(hr)) { + return hr; + } + return OnUpdateRectangles(); +} + + +// Return the height for the destination rectangle + +STDMETHODIMP CBaseControlVideo::get_DestinationHeight(__out long *pDestinationHeight) +{ + CheckPointer(pDestinationHeight,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + RECT DestinationRect; + + GetTargetRect(&DestinationRect); + *pDestinationHeight = HEIGHT(&DestinationRect); + return NOERROR; +} + + +// Reset the source rectangle to the full video dimensions + +STDMETHODIMP CBaseControlVideo::SetDefaultSourcePosition() +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + HRESULT hr = SetDefaultSourceRect(); + if (FAILED(hr)) { + return hr; + } + return OnUpdateRectangles(); +} + + +// Return S_OK if we're using the default source otherwise S_FALSE + +STDMETHODIMP CBaseControlVideo::IsUsingDefaultSource() +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + return IsDefaultSourceRect(); +} + + +// Reset the video renderer to use the entire playback area + +STDMETHODIMP CBaseControlVideo::SetDefaultDestinationPosition() +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + HRESULT hr = SetDefaultTargetRect(); + if (FAILED(hr)) { + return hr; + } + return OnUpdateRectangles(); +} + + +// Return S_OK if we're using the default target otherwise S_FALSE + +STDMETHODIMP CBaseControlVideo::IsUsingDefaultDestination() +{ + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + return IsDefaultTargetRect(); +} + + +// Return a copy of the current image in the video renderer + +STDMETHODIMP +CBaseControlVideo::GetCurrentImage(__inout long *pBufferSize,__out_bcount_part(*pBufferSize, *pBufferSize) long *pVideoImage) +{ + CheckPointer(pBufferSize,E_POINTER); + CheckConnected(m_pPin,VFW_E_NOT_CONNECTED); + CAutoLock cInterfaceLock(m_pInterfaceLock); + FILTER_STATE State; + + // Make sure we are in a paused state + + if (pVideoImage != NULL) { + m_pFilter->GetState(0,&State); + if (State != State_Paused) { + return VFW_E_NOT_PAUSED; + } + return GetStaticImage(pBufferSize,pVideoImage); + } + + // Just return the memory required + + VIDEOINFOHEADER *pVideoInfo = GetVideoFormat(); + if (pVideoInfo == NULL) + return E_OUTOFMEMORY; + RECT SourceRect; + GetSourceRect(&SourceRect); + return GetImageSize(pVideoInfo,pBufferSize,&SourceRect); +} + + +// An application has two ways of using GetCurrentImage, one is to pass a real +// buffer which should be filled with the current image. The other is to pass +// a NULL buffer pointer which is interpreted as asking us to return how much +// memory is required for the image. The constraints for when the latter can +// be called are much looser. To calculate the memory required we synthesize +// a VIDEOINFO that takes into account the source rectangle that's being used + +HRESULT CBaseControlVideo::GetImageSize(__in VIDEOINFOHEADER *pVideoInfo, + __out long *pBufferSize, + __in RECT *pSourceRect) +{ + NOTE("Entering GetImageSize"); + ASSERT(pSourceRect); + + // Check we have the correct input parameters + + if (pSourceRect == NULL || + pVideoInfo == NULL || + pBufferSize == NULL) { + + return E_UNEXPECTED; + } + + // Is the data format compatible + + if (pVideoInfo->bmiHeader.biCompression != BI_RGB) { + if (pVideoInfo->bmiHeader.biCompression != BI_BITFIELDS) { + return E_INVALIDARG; + } + } + + ASSERT(IsRectEmpty(pSourceRect) == FALSE); + + BITMAPINFOHEADER bih; + bih.biWidth = WIDTH(pSourceRect); + bih.biHeight = HEIGHT(pSourceRect); + bih.biBitCount = pVideoInfo->bmiHeader.biBitCount; + LONG Size = DIBSIZE(bih); + Size += GetBitmapFormatSize(HEADER(pVideoInfo)) - SIZE_PREHEADER; + *pBufferSize = Size; + + return NOERROR; +} + + +// Given an IMediaSample containing a linear buffer with an image and a type +// describing the bitmap make a rendering of the image into the output buffer +// This may be called by derived classes who render typical video images to +// handle the IBasicVideo GetCurrentImage method. The pVideoImage pointer may +// be NULL when passed to GetCurrentImage in which case GetImageSize will be +// called instead, which will just do the calculation of the memory required + +HRESULT CBaseControlVideo::CopyImage(IMediaSample *pMediaSample, + __in VIDEOINFOHEADER *pVideoInfo, + __inout long *pBufferSize, + __out_bcount_part(*pBufferSize, *pBufferSize) BYTE *pVideoImage, + __in RECT *pSourceRect) +{ + NOTE("Entering CopyImage"); + ASSERT(pSourceRect); + BYTE *pCurrentImage; + + // Check we have an image to copy + + if (pMediaSample == NULL || pSourceRect == NULL || + pVideoInfo == NULL || pVideoImage == NULL || + pBufferSize == NULL) { + + return E_UNEXPECTED; + } + + // Is the data format compatible + + if (pVideoInfo->bmiHeader.biCompression != BI_RGB) { + if (pVideoInfo->bmiHeader.biCompression != BI_BITFIELDS) { + return E_INVALIDARG; + } + } + + if (*pBufferSize < 0) { + return E_INVALIDARG; + } + + // Arbitrarily large size to prevent integer overflow problems + if (pVideoInfo->bmiHeader.biSize > 4096) + { + return E_INVALIDARG; + } + + ASSERT(IsRectEmpty(pSourceRect) == FALSE); + + BITMAPINFOHEADER bih; + bih.biWidth = WIDTH(pSourceRect); + bih.biHeight = HEIGHT(pSourceRect); + bih.biBitCount = pVideoInfo->bmiHeader.biBitCount; + DWORD Size = GetBitmapFormatSize(HEADER(pVideoInfo)) - SIZE_PREHEADER; + DWORD Total; + DWORD dwDibSize; + + if( !ValidateBitmapInfoHeader( HEADER(pVideoInfo), Size)) { + return E_INVALIDARG; + } + + // ValidateBitmapInfoHeader checks this but for some reason code scanning + // tools aren't picking up the annotation + __analysis_assume(Size >= sizeof(BITMAPINFOHEADER)); + + if (FAILED(SAFE_DIBSIZE(&bih, &dwDibSize))) { + return E_INVALIDARG; + } + + if (FAILED(DWordAdd(Size, dwDibSize, &Total))) { + return E_INVALIDARG; + } + + // Make sure we have a large enough buffer + + if ((DWORD)*pBufferSize < Total) { + return E_OUTOFMEMORY; + } + + // Copy the BITMAPINFO + + CopyMemory((PVOID)pVideoImage, (PVOID)&pVideoInfo->bmiHeader, Size); + ((BITMAPINFOHEADER *)pVideoImage)->biWidth = WIDTH(pSourceRect); + ((BITMAPINFOHEADER *)pVideoImage)->biHeight = HEIGHT(pSourceRect); + ((BITMAPINFOHEADER *)pVideoImage)->biSizeImage = DIBSIZE(bih); + BYTE *pImageData = pVideoImage + Size; + + // Get the pointer to it's image data + + HRESULT hr = pMediaSample->GetPointer(&pCurrentImage); + if (FAILED(hr)) { + return hr; + } + + // Now we are ready to start copying the source scan lines + + LONG ScanLine = (pVideoInfo->bmiHeader.biBitCount / 8) * WIDTH(pSourceRect); + LONG LinesToSkip = pVideoInfo->bmiHeader.biHeight; + LinesToSkip -= pSourceRect->top + HEIGHT(pSourceRect); + pCurrentImage += LinesToSkip * DIBWIDTHBYTES(pVideoInfo->bmiHeader); + pCurrentImage += pSourceRect->left * (pVideoInfo->bmiHeader.biBitCount / 8); + + // Even money on this GP faulting sometime... + + for (LONG Line = 0;Line < HEIGHT(pSourceRect);Line++) { + CopyMemory((PVOID)pImageData, (PVOID)pCurrentImage, ScanLine); + pImageData += DIBWIDTHBYTES(*(BITMAPINFOHEADER *)pVideoImage); + pCurrentImage += DIBWIDTHBYTES(pVideoInfo->bmiHeader); + } + return NOERROR; +} + + +// Called when we change media types either during connection or dynamically +// We inform the filter graph and therefore the application that the video +// size may have changed, we don't bother looking to see if it really has as +// we leave that to the application - the dimensions are the event parameters + +HRESULT CBaseControlVideo::OnVideoSizeChange() +{ + // Get the video format from the derived class + + VIDEOINFOHEADER *pVideoInfo = GetVideoFormat(); + if (pVideoInfo == NULL) + return E_OUTOFMEMORY; + WORD Width = (WORD) pVideoInfo->bmiHeader.biWidth; + WORD Height = (WORD) pVideoInfo->bmiHeader.biHeight; + + return m_pFilter->NotifyEvent(EC_VIDEO_SIZE_CHANGED, + MAKELPARAM(Width,Height), + MAKEWPARAM(0,0)); +} + + +// Set the video source rectangle. We must check the source rectangle against +// the actual video dimensions otherwise when we come to draw the pictures we +// get access violations as GDI tries to touch data outside of the image data +// Although we store the rectangle in left, top, right and bottom coordinates +// instead of left, top, width and height as OLE uses we do take into account +// that the rectangle is used up to, but not including, the right column and +// bottom row of pixels, see the Win32 documentation on RECT for more details + +HRESULT CBaseControlVideo::CheckSourceRect(__in RECT *pSourceRect) +{ + CheckPointer(pSourceRect,E_POINTER); + LONG Width,Height; + GetVideoSize(&Width,&Height); + + // Check the coordinates are greater than zero + // and that the rectangle is valid (left<right, top<bottom) + + if ((pSourceRect->left >= pSourceRect->right) || + (pSourceRect->left < 0) || + (pSourceRect->top >= pSourceRect->bottom) || + (pSourceRect->top < 0)) { + + return E_INVALIDARG; + } + + // Check the coordinates are less than the extents + + if ((pSourceRect->right > Width) || + (pSourceRect->bottom > Height)) { + + return E_INVALIDARG; + } + return NOERROR; +} + + +// Check the target rectangle has some valid coordinates, which amounts to +// little more than checking the destination rectangle isn't empty. Derived +// classes may call this when they have their SetTargetRect method called to +// check the rectangle validity, we do not update the rectangles passed in +// Although we store the rectangle in left, top, right and bottom coordinates +// instead of left, top, width and height as OLE uses we do take into account +// that the rectangle is used up to, but not including, the right column and +// bottom row of pixels, see the Win32 documentation on RECT for more details + +HRESULT CBaseControlVideo::CheckTargetRect(__in RECT *pTargetRect) +{ + // Check the pointer is valid + + if (pTargetRect == NULL) { + return E_POINTER; + } + + // These overflow the WIDTH and HEIGHT checks + + if (pTargetRect->left > pTargetRect->right || + pTargetRect->top > pTargetRect->bottom) { + return E_INVALIDARG; + } + + // Check the rectangle has valid coordinates + + if (WIDTH(pTargetRect) <= 0 || HEIGHT(pTargetRect) <= 0) { + return E_INVALIDARG; + } + + ASSERT(IsRectEmpty(pTargetRect) == FALSE); + return NOERROR; +} + diff --git a/Src/Plugins/Input/in_dshow/base/winctrl.h b/Src/Plugins/Input/in_dshow/base/winctrl.h new file mode 100644 index 00000000..f18ba823 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/winctrl.h @@ -0,0 +1,224 @@ +//------------------------------------------------------------------------------ +// File: WinCtrl.h +// +// Desc: DirectShow base classes - defines classes for video control +// interfaces. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __WINCTRL__ +#define __WINCTRL__ + +#define ABSOL(x) (x < 0 ? -x : x) +#define NEGAT(x) (x > 0 ? -x : x) + +// Helper +BOOL WINAPI PossiblyEatMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +class CBaseControlWindow : public CBaseVideoWindow, public CBaseWindow +{ +protected: + + CBaseFilter *m_pFilter; // Pointer to owning media filter + CBasePin *m_pPin; // Controls media types for connection + CCritSec *m_pInterfaceLock; // Externally defined critical section + COLORREF m_BorderColour; // Current window border colour + BOOL m_bAutoShow; // What happens when the state changes + HWND m_hwndOwner; // Owner window that we optionally have + HWND m_hwndDrain; // HWND to post any messages received + BOOL m_bCursorHidden; // Should we hide the window cursor + +public: + + // Internal methods for other objects to get information out + + HRESULT DoSetWindowStyle(long Style,long WindowLong); + HRESULT DoGetWindowStyle(__out long *pStyle,long WindowLong); + BOOL IsAutoShowEnabled() { return m_bAutoShow; }; + COLORREF GetBorderColour() { return m_BorderColour; }; + HWND GetOwnerWindow() { return m_hwndOwner; }; + BOOL IsCursorHidden() { return m_bCursorHidden; }; + + inline BOOL PossiblyEatMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) + { + return ::PossiblyEatMessage(m_hwndDrain, uMsg, wParam, lParam); + } + + // Derived classes must call this to set the pin the filter is using + // We don't have the pin passed in to the constructor (as we do with + // the CBaseFilter object) because filters typically create the + // pins dynamically when requested in CBaseFilter::GetPin. This can + // not be called from our constructor because is is a virtual method + + void SetControlWindowPin(CBasePin *pPin) { + m_pPin = pPin; + } + +public: + + CBaseControlWindow(__inout CBaseFilter *pFilter, // Owning media filter + __in CCritSec *pInterfaceLock, // Locking object + __in_opt LPCTSTR pName, // Object description + __inout_opt LPUNKNOWN pUnk, // Normal COM ownership + __inout HRESULT *phr); // OLE return code + + // These are the properties we support + + STDMETHODIMP put_Caption(__in BSTR strCaption); + STDMETHODIMP get_Caption(__out BSTR *pstrCaption); + STDMETHODIMP put_AutoShow(long AutoShow); + STDMETHODIMP get_AutoShow(__out long *AutoShow); + STDMETHODIMP put_WindowStyle(long WindowStyle); + STDMETHODIMP get_WindowStyle(__out long *pWindowStyle); + STDMETHODIMP put_WindowStyleEx(long WindowStyleEx); + STDMETHODIMP get_WindowStyleEx(__out long *pWindowStyleEx); + STDMETHODIMP put_WindowState(long WindowState); + STDMETHODIMP get_WindowState(__out long *pWindowState); + STDMETHODIMP put_BackgroundPalette(long BackgroundPalette); + STDMETHODIMP get_BackgroundPalette(__out long *pBackgroundPalette); + STDMETHODIMP put_Visible(long Visible); + STDMETHODIMP get_Visible(__out long *pVisible); + STDMETHODIMP put_Left(long Left); + STDMETHODIMP get_Left(__out long *pLeft); + STDMETHODIMP put_Width(long Width); + STDMETHODIMP get_Width(__out long *pWidth); + STDMETHODIMP put_Top(long Top); + STDMETHODIMP get_Top(__out long *pTop); + STDMETHODIMP put_Height(long Height); + STDMETHODIMP get_Height(__out long *pHeight); + STDMETHODIMP put_Owner(OAHWND Owner); + STDMETHODIMP get_Owner(__out OAHWND *Owner); + STDMETHODIMP put_MessageDrain(OAHWND Drain); + STDMETHODIMP get_MessageDrain(__out OAHWND *Drain); + STDMETHODIMP get_BorderColor(__out long *Color); + STDMETHODIMP put_BorderColor(long Color); + STDMETHODIMP get_FullScreenMode(__out long *FullScreenMode); + STDMETHODIMP put_FullScreenMode(long FullScreenMode); + + // And these are the methods + + STDMETHODIMP SetWindowForeground(long Focus); + STDMETHODIMP NotifyOwnerMessage(OAHWND hwnd,long uMsg,LONG_PTR wParam,LONG_PTR lParam); + STDMETHODIMP GetMinIdealImageSize(__out long *pWidth,__out long *pHeight); + STDMETHODIMP GetMaxIdealImageSize(__out long *pWidth,__out long *pHeight); + STDMETHODIMP SetWindowPosition(long Left,long Top,long Width,long Height); + STDMETHODIMP GetWindowPosition(__out long *pLeft,__out long *pTop,__out long *pWidth,__out long *pHeight); + STDMETHODIMP GetRestorePosition(__out long *pLeft,__out long *pTop,__out long *pWidth,__out long *pHeight); + STDMETHODIMP HideCursor(long HideCursor); + STDMETHODIMP IsCursorHidden(__out long *CursorHidden); +}; + +// This class implements the IBasicVideo interface + +class CBaseControlVideo : public CBaseBasicVideo +{ +protected: + + CBaseFilter *m_pFilter; // Pointer to owning media filter + CBasePin *m_pPin; // Controls media types for connection + CCritSec *m_pInterfaceLock; // Externally defined critical section + +public: + + // Derived classes must provide these for the implementation + + virtual HRESULT IsDefaultTargetRect() PURE; + virtual HRESULT SetDefaultTargetRect() PURE; + virtual HRESULT SetTargetRect(RECT *pTargetRect) PURE; + virtual HRESULT GetTargetRect(RECT *pTargetRect) PURE; + virtual HRESULT IsDefaultSourceRect() PURE; + virtual HRESULT SetDefaultSourceRect() PURE; + virtual HRESULT SetSourceRect(RECT *pSourceRect) PURE; + virtual HRESULT GetSourceRect(RECT *pSourceRect) PURE; + virtual HRESULT GetStaticImage(__inout long *pBufferSize,__out_bcount_part(*pBufferSize, *pBufferSize) long *pDIBImage) PURE; + + // Derived classes must override this to return a VIDEOINFO representing + // the video format. We cannot call IPin ConnectionMediaType to get this + // format because various filters dynamically change the type when using + // DirectDraw such that the format shows the position of the logical + // bitmap in a frame buffer surface, so the size might be returned as + // 1024x768 pixels instead of 320x240 which is the real video dimensions + + __out virtual VIDEOINFOHEADER *GetVideoFormat() PURE; + + // Helper functions for creating memory renderings of a DIB image + + HRESULT GetImageSize(__in VIDEOINFOHEADER *pVideoInfo, + __out LONG *pBufferSize, + __in RECT *pSourceRect); + + HRESULT CopyImage(IMediaSample *pMediaSample, + __in VIDEOINFOHEADER *pVideoInfo, + __inout LONG *pBufferSize, + __out_bcount_part(*pBufferSize, *pBufferSize) BYTE *pVideoImage, + __in RECT *pSourceRect); + + // Override this if you want notifying when the rectangles change + virtual HRESULT OnUpdateRectangles() { return NOERROR; }; + virtual HRESULT OnVideoSizeChange(); + + // Derived classes must call this to set the pin the filter is using + // We don't have the pin passed in to the constructor (as we do with + // the CBaseFilter object) because filters typically create the + // pins dynamically when requested in CBaseFilter::GetPin. This can + // not be called from our constructor because is is a virtual method + + void SetControlVideoPin(__inout CBasePin *pPin) { + m_pPin = pPin; + } + + // Helper methods for checking rectangles + virtual HRESULT CheckSourceRect(__in RECT *pSourceRect); + virtual HRESULT CheckTargetRect(__in RECT *pTargetRect); + +public: + + CBaseControlVideo(__inout CBaseFilter *pFilter, // Owning media filter + __in CCritSec *pInterfaceLock, // Serialise interface + __in_opt LPCTSTR pName, // Object description + __inout_opt LPUNKNOWN pUnk, // Normal COM ownership + __inout HRESULT *phr); // OLE return code + + // These are the properties we support + + STDMETHODIMP get_AvgTimePerFrame(__out REFTIME *pAvgTimePerFrame); + STDMETHODIMP get_BitRate(__out long *pBitRate); + STDMETHODIMP get_BitErrorRate(__out long *pBitErrorRate); + STDMETHODIMP get_VideoWidth(__out long *pVideoWidth); + STDMETHODIMP get_VideoHeight(__out long *pVideoHeight); + STDMETHODIMP put_SourceLeft(long SourceLeft); + STDMETHODIMP get_SourceLeft(__out long *pSourceLeft); + STDMETHODIMP put_SourceWidth(long SourceWidth); + STDMETHODIMP get_SourceWidth(__out long *pSourceWidth); + STDMETHODIMP put_SourceTop(long SourceTop); + STDMETHODIMP get_SourceTop(__out long *pSourceTop); + STDMETHODIMP put_SourceHeight(long SourceHeight); + STDMETHODIMP get_SourceHeight(__out long *pSourceHeight); + STDMETHODIMP put_DestinationLeft(long DestinationLeft); + STDMETHODIMP get_DestinationLeft(__out long *pDestinationLeft); + STDMETHODIMP put_DestinationWidth(long DestinationWidth); + STDMETHODIMP get_DestinationWidth(__out long *pDestinationWidth); + STDMETHODIMP put_DestinationTop(long DestinationTop); + STDMETHODIMP get_DestinationTop(__out long *pDestinationTop); + STDMETHODIMP put_DestinationHeight(long DestinationHeight); + STDMETHODIMP get_DestinationHeight(__out long *pDestinationHeight); + + // And these are the methods + + STDMETHODIMP GetVideoSize(__out long *pWidth,__out long *pHeight); + STDMETHODIMP SetSourcePosition(long Left,long Top,long Width,long Height); + STDMETHODIMP GetSourcePosition(__out long *pLeft,__out long *pTop,__out long *pWidth,__out long *pHeight); + STDMETHODIMP GetVideoPaletteEntries(long StartIndex,long Entries,__out long *pRetrieved,__out_ecount_part(Entries, *pRetrieved) long *pPalette); + STDMETHODIMP SetDefaultSourcePosition(); + STDMETHODIMP IsUsingDefaultSource(); + STDMETHODIMP SetDestinationPosition(long Left,long Top,long Width,long Height); + STDMETHODIMP GetDestinationPosition(__out long *pLeft,__out long *pTop,__out long *pWidth,__out long *pHeight); + STDMETHODIMP SetDefaultDestinationPosition(); + STDMETHODIMP IsUsingDefaultDestination(); + STDMETHODIMP GetCurrentImage(__inout long *pBufferSize,__out_bcount_part(*pBufferSize, *pBufferSize) long *pVideoImage); +}; + +#endif // __WINCTRL__ + diff --git a/Src/Plugins/Input/in_dshow/base/winutil.cpp b/Src/Plugins/Input/in_dshow/base/winutil.cpp new file mode 100644 index 00000000..a9b4876e --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/winutil.cpp @@ -0,0 +1,2746 @@ +//------------------------------------------------------------------------------ +// File: WinUtil.cpp +// +// Desc: DirectShow base classes - implements generic window handler class. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> +#include <limits.h> +#include <dvdmedia.h> +#include <strsafe.h> +#include <checkbmi.h> + +static UINT MsgDestroy; + +// Constructor + +CBaseWindow::CBaseWindow(BOOL bDoGetDC, bool bDoPostToDestroy) : + m_hInstance(g_hInst), + m_hwnd(NULL), + m_hdc(NULL), + m_bActivated(FALSE), + m_pClassName(NULL), + m_ClassStyles(0), + m_WindowStyles(0), + m_WindowStylesEx(0), + m_ShowStageMessage(0), + m_ShowStageTop(0), + m_MemoryDC(NULL), + m_hPalette(NULL), + m_bBackground(FALSE), +#ifdef DEBUG + m_bRealizing(FALSE), +#endif + m_bNoRealize(FALSE), + m_bDoPostToDestroy(bDoPostToDestroy) +{ + m_bDoGetDC = bDoGetDC; +} + + +// Prepare a window by spinning off a worker thread to do the creation and +// also poll the message input queue. We leave this to be called by derived +// classes because they might want to override methods like MessageLoop and +// InitialiseWindow, if we do this during construction they'll ALWAYS call +// this base class methods. We make the worker thread create the window so +// it owns it rather than the filter graph thread which is constructing us + +HRESULT CBaseWindow::PrepareWindow() +{ + if (m_hwnd) return NOERROR; + ASSERT(m_hwnd == NULL); + ASSERT(m_hdc == NULL); + + // Get the derived object's window and class styles + + m_pClassName = GetClassWindowStyles(&m_ClassStyles, + &m_WindowStyles, + &m_WindowStylesEx); + if (m_pClassName == NULL) { + return E_FAIL; + } + + // Register our special private messages + m_ShowStageMessage = RegisterWindowMessage(SHOWSTAGE); + + // RegisterWindowMessage() returns 0 if an error occurs. + if (0 == m_ShowStageMessage) { + return AmGetLastErrorToHResult(); + } + + m_ShowStageTop = RegisterWindowMessage(SHOWSTAGETOP); + if (0 == m_ShowStageTop) { + return AmGetLastErrorToHResult(); + } + + m_RealizePalette = RegisterWindowMessage(REALIZEPALETTE); + if (0 == m_RealizePalette) { + return AmGetLastErrorToHResult(); + } + + MsgDestroy = RegisterWindowMessage(TEXT("AM_DESTROY")); + if (0 == MsgDestroy) { + return AmGetLastErrorToHResult(); + } + + return DoCreateWindow(); +} + + +// Destructor just a placeholder so that we know it becomes virtual +// Derived classes MUST call DoneWithWindow in their destructors so +// that no messages arrive after the derived class constructor ends + +#ifdef DEBUG +CBaseWindow::~CBaseWindow() +{ + ASSERT(m_hwnd == NULL); + ASSERT(m_hdc == NULL); +} +#endif + + +// We use the sync worker event to have the window destroyed. All we do is +// signal the event and wait on the window thread handle. Trying to send it +// messages causes too many problems, furthermore to be on the safe side we +// just wait on the thread handle while it returns WAIT_TIMEOUT or there is +// a sent message to process on this thread. If the constructor failed to +// create the thread in the first place then the loop will get terminated + +HRESULT CBaseWindow::DoneWithWindow() +{ + if (!IsWindow(m_hwnd) || (GetWindowThreadProcessId(m_hwnd, NULL) != GetCurrentThreadId())) { + + if (IsWindow(m_hwnd)) { + + // This code should only be executed if the window exists and if the window's + // messages are processed on a different thread. + ASSERT(GetWindowThreadProcessId(m_hwnd, NULL) != GetCurrentThreadId()); + + if (m_bDoPostToDestroy) { + + HRESULT hr = S_OK; + CAMEvent m_evDone(FALSE, &hr); + if (FAILED(hr)) { + return hr; + } + + // We must post a message to destroy the window + // That way we can't be in the middle of processing a + // message posted to our window when we do go away + // Sending a message gives less synchronization. + PostMessage(m_hwnd, MsgDestroy, (WPARAM)(HANDLE)m_evDone, 0); + WaitDispatchingMessages(m_evDone, INFINITE); + } else { + SendMessage(m_hwnd, MsgDestroy, 0, 0); + } + } + + // + // This is not a leak, the window manager automatically free's + // hdc's that were got via GetDC, which is the case here. + // We set it to NULL so that we don't get any asserts later. + // + m_hdc = NULL; + + // + // We need to free this DC though because USER32 does not know + // anything about it. + // + if (m_MemoryDC) + { + EXECUTE_ASSERT(DeleteDC(m_MemoryDC)); + m_MemoryDC = NULL; + } + + // Reset the window variables + m_hwnd = NULL; + + return NOERROR; + } + const HWND hwnd = m_hwnd; + if (hwnd == NULL) { + return NOERROR; + } + + InactivateWindow(); + NOTE("Inactivated"); + + // Reset the window styles before destruction + + SetWindowLong(hwnd,GWL_STYLE,m_WindowStyles); + ASSERT(GetParent(hwnd) == NULL); + NOTE1("Reset window styles %d",m_WindowStyles); + + // UnintialiseWindow sets m_hwnd to NULL so save a copy + UninitialiseWindow(); + DbgLog((LOG_TRACE, 2, TEXT("Destroying 0x%8.8X"), hwnd)); + if (!DestroyWindow(hwnd)) { + DbgLog((LOG_TRACE, 0, TEXT("DestroyWindow %8.8X failed code %d"), + hwnd, GetLastError())); + DbgBreak(""); + } + + // Reset our state so we can be prepared again + + m_pClassName = NULL; + m_ClassStyles = 0; + m_WindowStyles = 0; + m_WindowStylesEx = 0; + m_ShowStageMessage = 0; + m_ShowStageTop = 0; + + return NOERROR; +} + + +// Called at the end to put the window in an inactive state. The pending list +// will always have been cleared by this time so event if the worker thread +// gets has been signaled and gets in to render something it will find both +// the state has been changed and that there are no available sample images +// Since we wait on the window thread to complete we don't lock the object + +HRESULT CBaseWindow::InactivateWindow() +{ + // Has the window been activated + if (m_bActivated == FALSE) { + return S_FALSE; + } + + m_bActivated = FALSE; + ShowWindow(m_hwnd,SW_HIDE); + return NOERROR; +} + + +HRESULT CBaseWindow::CompleteConnect() +{ + m_bActivated = FALSE; + return NOERROR; +} + +// This displays a normal window. We ask the base window class for default +// sizes which unless overriden will return DEFWIDTH and DEFHEIGHT. We go +// through a couple of extra hoops to get the client area the right size +// as the object specifies which accounts for the AdjustWindowRectEx calls +// We also DWORD align the left and top coordinates of the window here to +// maximise the chance of being able to use DCI/DirectDraw primary surface + +HRESULT CBaseWindow::ActivateWindow() +{ + // Has the window been sized and positioned already + + if (m_bActivated == TRUE || GetParent(m_hwnd) != NULL) { + + SetWindowPos(m_hwnd, // Our window handle + HWND_TOP, // Put it at the top + 0, 0, 0, 0, // Leave in current position + SWP_NOMOVE | // Don't change it's place + SWP_NOSIZE); // Change Z-order only + + m_bActivated = TRUE; + return S_FALSE; + } + + // Calculate the desired client rectangle + + RECT WindowRect, ClientRect = GetDefaultRect(); + GetWindowRect(m_hwnd,&WindowRect); + AdjustWindowRectEx(&ClientRect,GetWindowLong(m_hwnd,GWL_STYLE), + FALSE,GetWindowLong(m_hwnd,GWL_EXSTYLE)); + + // Align left and top edges on DWORD boundaries + + UINT WindowFlags = (SWP_NOACTIVATE | SWP_FRAMECHANGED); + WindowRect.left -= (WindowRect.left & 3); + WindowRect.top -= (WindowRect.top & 3); + + SetWindowPos(m_hwnd, // Window handle + HWND_TOP, // Put it at the top + WindowRect.left, // Align left edge + WindowRect.top, // And also top place + WIDTH(&ClientRect), // Horizontal size + HEIGHT(&ClientRect), // Vertical size + WindowFlags); // Don't show window + + m_bActivated = TRUE; + return NOERROR; +} + + +// This can be used to DWORD align the window for maximum performance + +HRESULT CBaseWindow::PerformanceAlignWindow() +{ + RECT ClientRect,WindowRect; + GetWindowRect(m_hwnd,&WindowRect); + ASSERT(m_bActivated == TRUE); + + // Don't do this if we're owned + + if (GetParent(m_hwnd)) { + return NOERROR; + } + + // Align left and top edges on DWORD boundaries + + GetClientRect(m_hwnd, &ClientRect); + MapWindowPoints(m_hwnd, HWND_DESKTOP, (LPPOINT) &ClientRect, 2); + WindowRect.left -= (ClientRect.left & 3); + WindowRect.top -= (ClientRect.top & 3); + UINT WindowFlags = (SWP_NOACTIVATE | SWP_NOSIZE); + + SetWindowPos(m_hwnd, // Window handle + HWND_TOP, // Put it at the top + WindowRect.left, // Align left edge + WindowRect.top, // And also top place + (int) 0,(int) 0, // Ignore these sizes + WindowFlags); // Don't show window + + return NOERROR; +} + + +// Install a palette into the base window - we may be called by a different +// thread to the one that owns the window. We have to be careful how we do +// the palette realisation as we could be a different thread to the window +// which would cause an inter thread send message. Therefore we realise the +// palette by sending it a special message but without the window locked + +HRESULT CBaseWindow::SetPalette(HPALETTE hPalette) +{ + // We must own the window lock during the change + { + CAutoLock cWindowLock(&m_WindowLock); + CAutoLock cPaletteLock(&m_PaletteLock); + ASSERT(hPalette); + m_hPalette = hPalette; + } + return SetPalette(); +} + + +HRESULT CBaseWindow::SetPalette() +{ + if (!m_bNoRealize) { + SendMessage(m_hwnd, m_RealizePalette, 0, 0); + return S_OK; + } else { + // Just select the palette + ASSERT(m_hdc); + ASSERT(m_MemoryDC); + + CAutoLock cPaletteLock(&m_PaletteLock); + SelectPalette(m_hdc,m_hPalette,m_bBackground); + SelectPalette(m_MemoryDC,m_hPalette,m_bBackground); + + return S_OK; + } +} + + +void CBaseWindow::UnsetPalette() +{ + CAutoLock cWindowLock(&m_WindowLock); + CAutoLock cPaletteLock(&m_PaletteLock); + + // Get a standard VGA colour palette + + HPALETTE hPalette = (HPALETTE) GetStockObject(DEFAULT_PALETTE); + ASSERT(hPalette); + + SelectPalette(GetWindowHDC(), hPalette, TRUE); + SelectPalette(GetMemoryHDC(), hPalette, TRUE); + + m_hPalette = NULL; +} + + +void CBaseWindow::LockPaletteLock() +{ + m_PaletteLock.Lock(); +} + + +void CBaseWindow::UnlockPaletteLock() +{ + m_PaletteLock.Unlock(); +} + + +// Realise our palettes in the window and device contexts + +HRESULT CBaseWindow::DoRealisePalette(BOOL bForceBackground) +{ + { + CAutoLock cPaletteLock(&m_PaletteLock); + + if (m_hPalette == NULL) { + return NOERROR; + } + + // Realize the palette on the window thread + ASSERT(m_hdc); + ASSERT(m_MemoryDC); + + SelectPalette(m_hdc,m_hPalette,m_bBackground || bForceBackground); + SelectPalette(m_MemoryDC,m_hPalette,m_bBackground); + } + + // If we grab a critical section here we can deadlock + // with the window thread because one of the side effects + // of RealizePalette is to send a WM_PALETTECHANGED message + // to every window in the system. In our handling + // of WM_PALETTECHANGED we used to grab this CS too. + // The really bad case is when our renderer calls DoRealisePalette() + // while we're in the middle of processing a palette change + // for another window. + // So don't hold the critical section while actually realising + // the palette. In any case USER is meant to manage palette + // handling - we shouldn't have to serialize everything as well + ASSERT(CritCheckOut(&m_WindowLock)); + ASSERT(CritCheckOut(&m_PaletteLock)); + + EXECUTE_ASSERT(RealizePalette(m_hdc) != GDI_ERROR); + EXECUTE_ASSERT(RealizePalette(m_MemoryDC) != GDI_ERROR); + + return (GdiFlush() == FALSE ? S_FALSE : S_OK); +} + + +// This is the global window procedure + +LRESULT CALLBACK WndProc(HWND hwnd, // Window handle + UINT uMsg, // Message ID + WPARAM wParam, // First parameter + LPARAM lParam) // Other parameter +{ + + // Get the window long that holds our window object pointer + // If it is NULL then we are initialising the window in which + // case the object pointer has been passed in the window creation + // structure. IF we get any messages before WM_NCCREATE we will + // pass them to DefWindowProc. + + CBaseWindow *pBaseWindow = _GetWindowLongPtr<CBaseWindow*>(hwnd,0); + + if (pBaseWindow == NULL) { + + // Get the structure pointer from the create struct. + // We can only do this for WM_NCCREATE which should be one of + // the first messages we receive. Anything before this will + // have to be passed to DefWindowProc (i.e. WM_GETMINMAXINFO) + + // If the message is WM_NCCREATE we set our pBaseWindow pointer + // and will then place it in the window structure + + // turn off WS_EX_LAYOUTRTL style for quartz windows + if (uMsg == WM_NCCREATE) { + SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) & ~0x400000); + } + + if ((uMsg != WM_NCCREATE) + || (NULL == (pBaseWindow = *(CBaseWindow**) ((LPCREATESTRUCT)lParam)->lpCreateParams))) + { + return(DefWindowProc(hwnd, uMsg, wParam, lParam)); + } + + // Set the window LONG to be the object who created us +#ifdef DEBUG + SetLastError(0); // because of the way SetWindowLong works +#endif + + LONG_PTR rc = _SetWindowLongPtr(hwnd, (DWORD) 0, pBaseWindow); + + +#ifdef DEBUG + if (0 == rc) { + // SetWindowLong MIGHT have failed. (Read the docs which admit + // that it is awkward to work out if you have had an error.) + LONG lasterror = GetLastError(); + ASSERT(0 == lasterror); + // If this is not the case we have not set the pBaseWindow pointer + // into the window structure and we will blow up. + } +#endif + + } + // See if this is the packet of death + if (uMsg == MsgDestroy && uMsg != 0) { + pBaseWindow->DoneWithWindow(); + if (pBaseWindow->m_bDoPostToDestroy) { + EXECUTE_ASSERT(SetEvent((HANDLE)wParam)); + } + return 0; + } + return pBaseWindow->OnReceiveMessage(hwnd,uMsg,wParam,lParam); +} + + +// When the window size changes we adjust our member variables that +// contain the dimensions of the client rectangle for our window so +// that we come to render an image we will know whether to stretch + +BOOL CBaseWindow::OnSize(LONG Width, LONG Height) +{ + m_Width = Width; + m_Height = Height; + return TRUE; +} + + +// This function handles the WM_CLOSE message + +BOOL CBaseWindow::OnClose() +{ + ShowWindow(m_hwnd,SW_HIDE); + return TRUE; +} + + +// This is called by the worker window thread when it receives a terminate +// message from the window object destructor to delete all the resources we +// allocated during initialisation. By the time the worker thread exits all +// processing will have been completed as the source filter disconnection +// flushes the image pending sample, therefore the GdiFlush should succeed + +HRESULT CBaseWindow::UninitialiseWindow() +{ + // Have we already cleaned up + + if (m_hwnd == NULL) { + ASSERT(m_hdc == NULL); + ASSERT(m_MemoryDC == NULL); + return NOERROR; + } + + // Release the window resources + + EXECUTE_ASSERT(GdiFlush()); + + if (m_hdc) + { + EXECUTE_ASSERT(ReleaseDC(m_hwnd,m_hdc)); + m_hdc = NULL; + } + + if (m_MemoryDC) + { + EXECUTE_ASSERT(DeleteDC(m_MemoryDC)); + m_MemoryDC = NULL; + } + + // Reset the window variables + m_hwnd = NULL; + + return NOERROR; +} + + +// This is called by the worker window thread after it has created the main +// window and it wants to initialise the rest of the owner objects window +// variables such as the device contexts. We execute this function with the +// critical section still locked. Nothing in this function must generate any +// SendMessage calls to the window because this is executing on the window +// thread so the message will never be processed and we will deadlock + +HRESULT CBaseWindow::InitialiseWindow(HWND hwnd) +{ + // Initialise the window variables + + ASSERT(IsWindow(hwnd)); + m_hwnd = hwnd; + + if (m_bDoGetDC) + { + EXECUTE_ASSERT(m_hdc = GetDC(hwnd)); + EXECUTE_ASSERT(m_MemoryDC = CreateCompatibleDC(m_hdc)); + + EXECUTE_ASSERT(SetStretchBltMode(m_hdc,COLORONCOLOR)); + EXECUTE_ASSERT(SetStretchBltMode(m_MemoryDC,COLORONCOLOR)); + } + + return NOERROR; +} + +HRESULT CBaseWindow::DoCreateWindow() +{ + WNDCLASS wndclass; // Used to register classes + BOOL bRegistered; // Is this class registered + HWND hwnd; // Handle to our window + + bRegistered = GetClassInfo(m_hInstance, // Module instance + m_pClassName, // Window class + &wndclass); // Info structure + + // if the window is to be used for drawing puposes and we are getting a DC + // for the entire lifetime of the window then changes the class style to do + // say so. If we don't set this flag then the DC comes from the cache and is + // really bad. + if (m_bDoGetDC) + { + m_ClassStyles |= CS_OWNDC; + } + + if (bRegistered == FALSE) { + + // Register the renderer window class + + wndclass.lpszClassName = m_pClassName; + wndclass.style = m_ClassStyles; + wndclass.lpfnWndProc = WndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = sizeof(CBaseWindow *); + wndclass.hInstance = m_hInstance; + wndclass.hIcon = NULL; + wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); + wndclass.hbrBackground = (HBRUSH) NULL; + wndclass.lpszMenuName = NULL; + + RegisterClassW(&wndclass); + } + + // Create the frame window. Pass the pBaseWindow information in the + // CreateStruct which allows our message handling loop to get hold of + // the pBaseWindow pointer. + + CBaseWindow *pBaseWindow = this; // The owner window object + hwnd = CreateWindowEx(m_WindowStylesEx, // Extended styles + m_pClassName, // Registered name + TEXT("ActiveMovie Window"), // Window title + m_WindowStyles, // Window styles + CW_USEDEFAULT, // Start x position + CW_USEDEFAULT, // Start y position + DEFWIDTH, // Window width + DEFHEIGHT, // Window height + NULL, // Parent handle + NULL, // Menu handle + m_hInstance, // Instance handle + &pBaseWindow); // Creation data + + // If we failed signal an error to the object constructor (based on the + // last Win32 error on this thread) then signal the constructor thread + // to continue, release the mutex to let others have a go and exit + + if (hwnd == NULL) { + DWORD Error = GetLastError(); + return AmHresultFromWin32(Error); + } + + // Check the window LONG is the object who created us + ASSERT(GetWindowLongPtr(hwnd, 0) == (LONG_PTR)this); + + // Initialise the window and then signal the constructor so that it can + // continue and then finally unlock the object's critical section. The + // window class is left registered even after we terminate the thread + // as we don't know when the last window has been closed. So we allow + // the operating system to free the class resources as appropriate + + InitialiseWindow(hwnd); + + DbgLog((LOG_TRACE, 2, TEXT("Created window class (%s) HWND(%8.8X)"), + m_pClassName, hwnd)); + + return S_OK; +} + + +// The base class provides some default handling and calls DefWindowProc + +LRESULT CBaseWindow::OnReceiveMessage(HWND hwnd, // Window handle + UINT uMsg, // Message ID + WPARAM wParam, // First parameter + LPARAM lParam) // Other parameter +{ + ASSERT(IsWindow(hwnd)); + + if (PossiblyEatMessage(uMsg, wParam, lParam)) + return 0; + + // This is sent by the IVideoWindow SetWindowForeground method. If the + // window is invisible we will show it and make it topmost without the + // foreground focus. If the window is visible it will also be made the + // topmost window without the foreground focus. If wParam is TRUE then + // for both cases the window will be forced into the foreground focus + + if (uMsg == m_ShowStageMessage) { + + BOOL bVisible = IsWindowVisible(hwnd); + SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | + (bVisible ? SWP_NOACTIVATE : 0)); + + // Should we bring the window to the foreground + if (wParam == TRUE) { + SetForegroundWindow(hwnd); + } + return (LRESULT) 1; + } + + // When we go fullscreen we have to add the WS_EX_TOPMOST style to the + // video window so that it comes out above any task bar (this is more + // relevant to WindowsNT than Windows95). However the SetWindowPos call + // must be on the same thread as that which created the window. The + // wParam parameter can be TRUE or FALSE to set and reset the topmost + + if (uMsg == m_ShowStageTop) { + HWND HwndTop = (wParam == TRUE ? HWND_TOPMOST : HWND_NOTOPMOST); + BOOL bVisible = IsWindowVisible(hwnd); + SetWindowPos(hwnd, HwndTop, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | + (wParam == TRUE ? SWP_SHOWWINDOW : 0) | + (bVisible ? SWP_NOACTIVATE : 0)); + return (LRESULT) 1; + } + + // New palette stuff + if (uMsg == m_RealizePalette) { + ASSERT(m_hwnd == hwnd); + return OnPaletteChange(m_hwnd,WM_QUERYNEWPALETTE); + } + + switch (uMsg) { + + // Repaint the window if the system colours change + + case WM_SYSCOLORCHANGE: + + InvalidateRect(hwnd,NULL,FALSE); + return (LRESULT) 1; + + // Somebody has changed the palette + case WM_PALETTECHANGED: + + OnPaletteChange((HWND)wParam,uMsg); + return (LRESULT) 0; + + // We are about to receive the keyboard focus so we ask GDI to realise + // our logical palette again and hopefully it will be fully installed + // without any mapping having to be done during any picture rendering + + case WM_QUERYNEWPALETTE: + ASSERT(m_hwnd == hwnd); + return OnPaletteChange(m_hwnd,uMsg); + + // do NOT fwd WM_MOVE. the parameters are the location of the parent + // window, NOT what the renderer should be looking at. But we need + // to make sure the overlay is moved with the parent window, so we + // do this. + case WM_MOVE: + if (IsWindowVisible(m_hwnd)) { + PostMessage(m_hwnd,WM_PAINT,0,0); + } + break; + + // Store the width and height as useful base class members + + case WM_SIZE: + + OnSize(LOWORD(lParam), HIWORD(lParam)); + return (LRESULT) 0; + + // Intercept the WM_CLOSE messages to hide the window + + case WM_CLOSE: + + OnClose(); + return (LRESULT) 0; + } + return DefWindowProc(hwnd,uMsg,wParam,lParam); +} + + +// This handles the Windows palette change messages - if we do realise our +// palette then we return TRUE otherwise we return FALSE. If our window is +// foreground application then we should get first choice of colours in the +// system palette entries. We get best performance when our logical palette +// includes the standard VGA colours (at the beginning and end) otherwise +// GDI may have to map from our palette to the device palette while drawing + +LRESULT CBaseWindow::OnPaletteChange(HWND hwnd,UINT Message) +{ + // First check we are not changing the palette during closedown + + if (m_hwnd == NULL || hwnd == NULL) { + return (LRESULT) 0; + } + ASSERT(!m_bRealizing); + + // Should we realise our palette again + + if ((Message == WM_QUERYNEWPALETTE || hwnd != m_hwnd)) { + // It seems that even if we're invisible that we can get asked + // to realize our palette and this can cause really ugly side-effects + // Seems like there's another bug but this masks it a least for the + // shutting down case. + if (!IsWindowVisible(m_hwnd)) { + DbgLog((LOG_TRACE, 1, TEXT("Realizing when invisible!"))); + return (LRESULT) 0; + } + + // Avoid recursion with multiple graphs in the same app +#ifdef DEBUG + m_bRealizing = TRUE; +#endif + DoRealisePalette(Message != WM_QUERYNEWPALETTE); +#ifdef DEBUG + m_bRealizing = FALSE; +#endif + + // Should we redraw the window with the new palette + if (Message == WM_PALETTECHANGED) { + InvalidateRect(m_hwnd,NULL,FALSE); + } + } + + return (LRESULT) 1; +} + + +// Determine if the window exists. + +bool CBaseWindow::WindowExists() +{ + return !!IsWindow(m_hwnd); +} + + +// Return the default window rectangle + +RECT CBaseWindow::GetDefaultRect() +{ + RECT DefaultRect = {0,0,DEFWIDTH,DEFHEIGHT}; + ASSERT(m_hwnd); + // ASSERT(m_hdc); + return DefaultRect; +} + + +// Return the current window width + +LONG CBaseWindow::GetWindowWidth() +{ + ASSERT(m_hwnd); + // ASSERT(m_hdc); + return m_Width; +} + + +// Return the current window height + +LONG CBaseWindow::GetWindowHeight() +{ + ASSERT(m_hwnd); + // ASSERT(m_hdc); + return m_Height; +} + + +// Return the window handle + +HWND CBaseWindow::GetWindowHWND() +{ + ASSERT(m_hwnd); + // ASSERT(m_hdc); + return m_hwnd; +} + + +// Return the window drawing device context + +HDC CBaseWindow::GetWindowHDC() +{ + ASSERT(m_hwnd); + ASSERT(m_hdc); + return m_hdc; +} + + +// Return the offscreen window drawing device context + +HDC CBaseWindow::GetMemoryHDC() +{ + ASSERT(m_hwnd); + ASSERT(m_MemoryDC); + return m_MemoryDC; +} + + +#ifdef DEBUG +HPALETTE CBaseWindow::GetPalette() +{ + // The palette lock should always be held when accessing + // m_hPalette. + ASSERT(CritCheckIn(&m_PaletteLock)); + return m_hPalette; +} +#endif // DEBUG + + +// This is available to clients who want to change the window visiblity. It's +// little more than an indirection to the Win32 ShowWindow although these is +// some benefit in going through here as this function may change sometime + +HRESULT CBaseWindow::DoShowWindow(LONG ShowCmd) +{ + ShowWindow(m_hwnd,ShowCmd); + return NOERROR; +} + + +// Generate a WM_PAINT message for the video window + +void CBaseWindow::PaintWindow(BOOL bErase) +{ + InvalidateRect(m_hwnd,NULL,bErase); +} + + +// Allow an application to have us set the video window in the foreground. We +// have this because it is difficult for one thread to do do this to a window +// owned by another thread. Rather than expose the message we use to execute +// the inter thread send message we provide the interface function. All we do +// is to SendMessage to the video window renderer thread with a WM_SHOWSTAGE + +void CBaseWindow::DoSetWindowForeground(BOOL bFocus) +{ + SendMessage(m_hwnd,m_ShowStageMessage,(WPARAM) bFocus,(LPARAM) 0); +} + + +// Constructor initialises the owning object pointer. Since we are a worker +// class for the main window object we have relatively few state variables to +// look after. We are given device context handles to use later on as well as +// the source and destination rectangles (but reset them here just in case) + +CDrawImage::CDrawImage(__inout CBaseWindow *pBaseWindow) : + m_pBaseWindow(pBaseWindow), + m_hdc(NULL), + m_MemoryDC(NULL), + m_bStretch(FALSE), + m_pMediaType(NULL), + m_bUsingImageAllocator(FALSE) +{ + ASSERT(pBaseWindow); + ResetPaletteVersion(); + SetRectEmpty(&m_TargetRect); + SetRectEmpty(&m_SourceRect); + + m_perfidRenderTime = MSR_REGISTER(TEXT("Single Blt time")); +} + + +// Overlay the image time stamps on the picture. Access to this method is +// serialised by the caller. We display the sample start and end times on +// top of the video using TextOut on the device context we are handed. If +// there isn't enough room in the window for the times we don't show them + +void CDrawImage::DisplaySampleTimes(IMediaSample *pSample) +{ +#ifdef DEBUG + // + // Only allow the "annoying" time messages if the users has turned the + // logging "way up" + // + BOOL bAccept = DbgCheckModuleLevel(LOG_TRACE, 5); + if (bAccept == FALSE) { + return; + } +#endif + + TCHAR szTimes[TIMELENGTH]; // Time stamp strings + ASSERT(pSample); // Quick sanity check + RECT ClientRect; // Client window size + SIZE Size; // Size of text output + + // Get the time stamps and window size + + pSample->GetTime((REFERENCE_TIME*)&m_StartSample, (REFERENCE_TIME*)&m_EndSample); + HWND hwnd = m_pBaseWindow->GetWindowHWND(); + EXECUTE_ASSERT(GetClientRect(hwnd,&ClientRect)); + + // Format the sample time stamps + + (void)StringCchPrintf(szTimes,NUMELMS(szTimes),TEXT("%08d : %08d"), + m_StartSample.Millisecs(), + m_EndSample.Millisecs()); + + ASSERT(lstrlen(szTimes) < TIMELENGTH); + + // Put the times in the middle at the bottom of the window + + GetTextExtentPoint32(m_hdc,szTimes,lstrlen(szTimes),&Size); + INT XPos = ((ClientRect.right - ClientRect.left) - Size.cx) / 2; + INT YPos = ((ClientRect.bottom - ClientRect.top) - Size.cy) * 4 / 5; + + // Check the window is big enough to have sample times displayed + + if ((XPos > 0) && (YPos > 0)) { + TextOut(m_hdc,XPos,YPos,szTimes,lstrlen(szTimes)); + } +} + + +// This is called when the drawing code sees that the image has a down level +// palette cookie. We simply call the SetDIBColorTable Windows API with the +// palette that is found after the BITMAPINFOHEADER - we return no errors + +void CDrawImage::UpdateColourTable(HDC hdc,__in BITMAPINFOHEADER *pbmi) +{ + ASSERT(pbmi->biClrUsed); + RGBQUAD *pColourTable = (RGBQUAD *)(pbmi+1); + + // Set the new palette in the device context + + UINT uiReturn = SetDIBColorTable(hdc,(UINT) 0, + pbmi->biClrUsed, + pColourTable); + + // Should always succeed but check in debug builds + ASSERT(uiReturn == pbmi->biClrUsed); +} + + +// No source rectangle scaling is done by the base class + +RECT CDrawImage::ScaleSourceRect(const RECT *pSource) +{ + ASSERT(pSource); + return *pSource; +} + + +// This is called when the funky output pin uses our allocator. The samples we +// allocate are special because the memory is shared between us and GDI thus +// removing one copy when we ask for the image to be rendered. The source type +// information is in the main renderer m_mtIn field which is initialised when +// the media type is agreed in SetMediaType, the media type may be changed on +// the fly if, for example, the source filter needs to change the palette + +void CDrawImage::FastRender(IMediaSample *pMediaSample) +{ + BITMAPINFOHEADER *pbmi; // Image format data + DIBDATA *pDibData; // Stores DIB information + BYTE *pImage; // Pointer to image data + HBITMAP hOldBitmap; // Store the old bitmap + CImageSample *pSample; // Pointer to C++ object + + ASSERT(m_pMediaType); + + // From the untyped source format block get the VIDEOINFO and subsequently + // the BITMAPINFOHEADER structure. We can cast the IMediaSample interface + // to a CImageSample object so we can retrieve it's DIBSECTION details + + pbmi = HEADER(m_pMediaType->Format()); + pSample = (CImageSample *) pMediaSample; + pDibData = pSample->GetDIBData(); + hOldBitmap = (HBITMAP) SelectObject(m_MemoryDC,pDibData->hBitmap); + + // Get a pointer to the real image data + + HRESULT hr = pMediaSample->GetPointer(&pImage); + if (FAILED(hr)) { + return; + } + + // Do we need to update the colour table, we increment our palette cookie + // each time we get a dynamic format change. The sample palette cookie is + // stored in the DIBDATA structure so we try to keep the fields in sync + // By the time we get to draw the images the format change will be done + // so all we do is ask the renderer for what it's palette version is + + if (pDibData->PaletteVersion < GetPaletteVersion()) { + ASSERT(pbmi->biBitCount <= iPALETTE); + UpdateColourTable(m_MemoryDC,pbmi); + pDibData->PaletteVersion = GetPaletteVersion(); + } + + // This allows derived classes to change the source rectangle that we do + // the drawing with. For example a renderer may ask a codec to stretch + // the video from 320x240 to 640x480, in which case the source we see in + // here will still be 320x240, although the source we want to draw with + // should be scaled up to 640x480. The base class implementation of this + // method does nothing but return the same rectangle as we are passed in + + RECT SourceRect = ScaleSourceRect(&m_SourceRect); + + // Is the window the same size as the video + + if (m_bStretch == FALSE) { + + // Put the image straight into the window + + BitBlt( + (HDC) m_hdc, // Target device HDC + m_TargetRect.left, // X sink position + m_TargetRect.top, // Y sink position + m_TargetRect.right - m_TargetRect.left, // Destination width + m_TargetRect.bottom - m_TargetRect.top, // Destination height + m_MemoryDC, // Source device context + SourceRect.left, // X source position + SourceRect.top, // Y source position + SRCCOPY); // Simple copy + + } else { + + // Stretch the image when copying to the window + + StretchBlt( + (HDC) m_hdc, // Target device HDC + m_TargetRect.left, // X sink position + m_TargetRect.top, // Y sink position + m_TargetRect.right - m_TargetRect.left, // Destination width + m_TargetRect.bottom - m_TargetRect.top, // Destination height + m_MemoryDC, // Source device HDC + SourceRect.left, // X source position + SourceRect.top, // Y source position + SourceRect.right - SourceRect.left, // Source width + SourceRect.bottom - SourceRect.top, // Source height + SRCCOPY); // Simple copy + } + + // This displays the sample times over the top of the image. This used to + // draw the times into the offscreen device context however that actually + // writes the text into the image data buffer which may not be writable + + #ifdef DEBUG + DisplaySampleTimes(pMediaSample); + #endif + + // Put the old bitmap back into the device context so we don't leak + SelectObject(m_MemoryDC,hOldBitmap); +} + + +// This is called when there is a sample ready to be drawn, unfortunately the +// output pin was being rotten and didn't choose our super excellent shared +// memory DIB allocator so we have to do this slow render using boring old GDI +// SetDIBitsToDevice and StretchDIBits. The down side of using these GDI +// functions is that the image data has to be copied across from our address +// space into theirs before going to the screen (although in reality the cost +// is small because all they do is to map the buffer into their address space) + +void CDrawImage::SlowRender(IMediaSample *pMediaSample) +{ + // Get the BITMAPINFOHEADER for the connection + + ASSERT(m_pMediaType); + BITMAPINFOHEADER *pbmi = HEADER(m_pMediaType->Format()); + BYTE *pImage; + + // Get the image data buffer + + HRESULT hr = pMediaSample->GetPointer(&pImage); + if (FAILED(hr)) { + return; + } + + // This allows derived classes to change the source rectangle that we do + // the drawing with. For example a renderer may ask a codec to stretch + // the video from 320x240 to 640x480, in which case the source we see in + // here will still be 320x240, although the source we want to draw with + // should be scaled up to 640x480. The base class implementation of this + // method does nothing but return the same rectangle as we are passed in + + RECT SourceRect = ScaleSourceRect(&m_SourceRect); + + LONG lAdjustedSourceTop = SourceRect.top; + // if the origin of bitmap is bottom-left, adjust soruce_rect_top + // to be the bottom-left corner instead of the top-left. + if (pbmi->biHeight > 0) { + lAdjustedSourceTop = pbmi->biHeight - SourceRect.bottom; + } + // Is the window the same size as the video + + if (m_bStretch == FALSE) { + + // Put the image straight into the window + + SetDIBitsToDevice( + (HDC) m_hdc, // Target device HDC + m_TargetRect.left, // X sink position + m_TargetRect.top, // Y sink position + m_TargetRect.right - m_TargetRect.left, // Destination width + m_TargetRect.bottom - m_TargetRect.top, // Destination height + SourceRect.left, // X source position + lAdjustedSourceTop, // Adjusted Y source position + (UINT) 0, // Start scan line + pbmi->biHeight, // Scan lines present + pImage, // Image data + (BITMAPINFO *) pbmi, // DIB header + DIB_RGB_COLORS); // Type of palette + + } else { + + // Stretch the image when copying to the window + + StretchDIBits( + (HDC) m_hdc, // Target device HDC + m_TargetRect.left, // X sink position + m_TargetRect.top, // Y sink position + m_TargetRect.right - m_TargetRect.left, // Destination width + m_TargetRect.bottom - m_TargetRect.top, // Destination height + SourceRect.left, // X source position + lAdjustedSourceTop, // Adjusted Y source position + SourceRect.right - SourceRect.left, // Source width + SourceRect.bottom - SourceRect.top, // Source height + pImage, // Image data + (BITMAPINFO *) pbmi, // DIB header + DIB_RGB_COLORS, // Type of palette + SRCCOPY); // Simple image copy + } + + // This shows the sample reference times over the top of the image which + // looks a little flickery. I tried using GdiSetBatchLimit and GdiFlush to + // control the screen updates but it doesn't quite work as expected and + // only partially reduces the flicker. I also tried using a memory context + // and combining the two in that before doing a final BitBlt operation to + // the screen, unfortunately this has considerable performance penalties + // and also means that this code is not executed when compiled retail + + #ifdef DEBUG + DisplaySampleTimes(pMediaSample); + #endif +} + + +// This is called with an IMediaSample interface on the image to be drawn. We +// decide on the drawing mechanism based on who's allocator we are using. We +// may be called when the window wants an image painted by WM_PAINT messages +// We can't realise the palette here because we have the renderer lock, any +// call to realise may cause an interthread send message to the window thread +// which may in turn be waiting to get the renderer lock before servicing it + +BOOL CDrawImage::DrawImage(IMediaSample *pMediaSample) +{ + ASSERT(m_hdc); + ASSERT(m_MemoryDC); + NotifyStartDraw(); + + // If the output pin used our allocator then the samples passed are in + // fact CVideoSample objects that contain CreateDIBSection data that we + // use to do faster image rendering, they may optionally also contain a + // DirectDraw surface pointer in which case we do not do the drawing + + if (m_bUsingImageAllocator == FALSE) { + SlowRender(pMediaSample); + EXECUTE_ASSERT(GdiFlush()); + NotifyEndDraw(); + return TRUE; + } + + // This is a DIBSECTION buffer + + FastRender(pMediaSample); + EXECUTE_ASSERT(GdiFlush()); + NotifyEndDraw(); + return TRUE; +} + + +BOOL CDrawImage::DrawVideoImageHere( + HDC hdc, + IMediaSample *pMediaSample, + __in LPRECT lprcSrc, + __in LPRECT lprcDst + ) +{ + ASSERT(m_pMediaType); + BITMAPINFOHEADER *pbmi = HEADER(m_pMediaType->Format()); + BYTE *pImage; + + // Get the image data buffer + + HRESULT hr = pMediaSample->GetPointer(&pImage); + if (FAILED(hr)) { + return FALSE; + } + + RECT SourceRect; + RECT TargetRect; + + if (lprcSrc) { + SourceRect = *lprcSrc; + } + else SourceRect = ScaleSourceRect(&m_SourceRect); + + if (lprcDst) { + TargetRect = *lprcDst; + } + else TargetRect = m_TargetRect; + + LONG lAdjustedSourceTop = SourceRect.top; + // if the origin of bitmap is bottom-left, adjust soruce_rect_top + // to be the bottom-left corner instead of the top-left. + if (pbmi->biHeight > 0) { + lAdjustedSourceTop = pbmi->biHeight - SourceRect.bottom; + } + + + // Stretch the image when copying to the DC + + BOOL bRet = (0 != StretchDIBits(hdc, + TargetRect.left, + TargetRect.top, + TargetRect.right - TargetRect.left, + TargetRect.bottom - TargetRect.top, + SourceRect.left, + lAdjustedSourceTop, + SourceRect.right - SourceRect.left, + SourceRect.bottom - SourceRect.top, + pImage, + (BITMAPINFO *)pbmi, + DIB_RGB_COLORS, + SRCCOPY)); + return bRet; +} + + +// This is called by the owning window object after it has created the window +// and it's drawing contexts. We are constructed with the base window we'll +// be drawing into so when given the notification we retrive the device HDCs +// to draw with. We cannot call these in our constructor as they are virtual + +void CDrawImage::SetDrawContext() +{ + m_MemoryDC = m_pBaseWindow->GetMemoryHDC(); + m_hdc = m_pBaseWindow->GetWindowHDC(); +} + + +// This is called to set the target rectangle in the video window, it will be +// called whenever a WM_SIZE message is retrieved from the message queue. We +// simply store the rectangle and use it later when we do the drawing calls + +void CDrawImage::SetTargetRect(__in RECT *pTargetRect) +{ + ASSERT(pTargetRect); + m_TargetRect = *pTargetRect; + SetStretchMode(); +} + + +// Return the current target rectangle + +void CDrawImage::GetTargetRect(__out RECT *pTargetRect) +{ + ASSERT(pTargetRect); + *pTargetRect = m_TargetRect; +} + + +// This is called when we want to change the section of the image to draw. We +// use this information in the drawing operation calls later on. We must also +// see if the source and destination rectangles have the same dimensions. If +// not we must stretch during the drawing rather than a direct pixel copy + +void CDrawImage::SetSourceRect(__in RECT *pSourceRect) +{ + ASSERT(pSourceRect); + m_SourceRect = *pSourceRect; + SetStretchMode(); +} + + +// Return the current source rectangle + +void CDrawImage::GetSourceRect(__out RECT *pSourceRect) +{ + ASSERT(pSourceRect); + *pSourceRect = m_SourceRect; +} + + +// This is called when either the source or destination rectanges change so we +// can update the stretch flag. If the rectangles don't match we stretch the +// video during the drawing otherwise we call the fast pixel copy functions +// NOTE the source and/or the destination rectangle may be completely empty + +void CDrawImage::SetStretchMode() +{ + // Calculate the overall rectangle dimensions + + LONG SourceWidth = m_SourceRect.right - m_SourceRect.left; + LONG SinkWidth = m_TargetRect.right - m_TargetRect.left; + LONG SourceHeight = m_SourceRect.bottom - m_SourceRect.top; + LONG SinkHeight = m_TargetRect.bottom - m_TargetRect.top; + + m_bStretch = TRUE; + if (SourceWidth == SinkWidth) { + if (SourceHeight == SinkHeight) { + m_bStretch = FALSE; + } + } +} + + +// Tell us whose allocator we are using. This should be called with TRUE if +// the filter agrees to use an allocator based around the CImageAllocator +// SDK base class - whose image buffers are made through CreateDIBSection. +// Otherwise this should be called with FALSE and we will draw the images +// using SetDIBitsToDevice and StretchDIBitsToDevice. None of these calls +// can handle buffers which have non zero strides (like DirectDraw uses) + +void CDrawImage::NotifyAllocator(BOOL bUsingImageAllocator) +{ + m_bUsingImageAllocator = bUsingImageAllocator; +} + + +// Are we using the image DIBSECTION allocator + +BOOL CDrawImage::UsingImageAllocator() +{ + return m_bUsingImageAllocator; +} + + +// We need the media type of the connection so that we can get the BITMAPINFO +// from it. We use that in the calls to draw the image such as StretchDIBits +// and also when updating the colour table held in shared memory DIBSECTIONs + +void CDrawImage::NotifyMediaType(__in CMediaType *pMediaType) +{ + m_pMediaType = pMediaType; +} + + +// We store in this object a cookie maintaining the current palette version. +// Each time a palettised format is changed we increment this value so that +// when we come to draw the images we look at the colour table value they +// have and if less than the current we know to update it. This version is +// only needed and indeed used when working with shared memory DIBSECTIONs + +LONG CDrawImage::GetPaletteVersion() +{ + return m_PaletteVersion; +} + + +// Resets the current palette version number + +void CDrawImage::ResetPaletteVersion() +{ + m_PaletteVersion = PALETTE_VERSION; +} + + +// Increment the current palette version + +void CDrawImage::IncrementPaletteVersion() +{ + m_PaletteVersion++; +} + + +// Constructor must initialise the base allocator. Each sample we create has a +// palette version cookie on board. When the source filter changes the palette +// during streaming the window object increments an internal cookie counter it +// keeps as well. When it comes to render the samples it looks at the cookie +// values and if they don't match then it knows to update the sample's colour +// table. However we always create samples with a cookie of PALETTE_VERSION +// If there have been multiple format changes and we disconnect and reconnect +// thereby causing the samples to be reallocated we will create them with a +// cookie much lower than the current version, this isn't a problem since it +// will be seen by the window object and the versions will then be updated + +CImageAllocator::CImageAllocator(__inout CBaseFilter *pFilter, + __in_opt LPCTSTR pName, + __inout HRESULT *phr) : + CBaseAllocator(pName,NULL,phr,TRUE,TRUE), + m_pFilter(pFilter) +{ + ASSERT(phr); + ASSERT(pFilter); +} + + +// Check our DIB buffers have been released + +#ifdef DEBUG +CImageAllocator::~CImageAllocator() +{ + ASSERT(m_bCommitted == FALSE); +} +#endif + + +// Called from destructor and also from base class to free resources. We work +// our way through the list of media samples deleting the DIBSECTION created +// for each. All samples should be back in our list so there is no chance a +// filter is still using one to write on the display or hold on a pending list + +void CImageAllocator::Free() +{ + ASSERT(m_lAllocated == m_lFree.GetCount()); + EXECUTE_ASSERT(GdiFlush()); + CImageSample *pSample; + DIBDATA *pDibData; + + while (m_lFree.GetCount() != 0) { + pSample = (CImageSample *) m_lFree.RemoveHead(); + pDibData = pSample->GetDIBData(); + EXECUTE_ASSERT(DeleteObject(pDibData->hBitmap)); + EXECUTE_ASSERT(CloseHandle(pDibData->hMapping)); + delete pSample; + } + + m_lAllocated = 0; +} + + +// Prepare the allocator by checking all the input parameters + +STDMETHODIMP CImageAllocator::CheckSizes(__in ALLOCATOR_PROPERTIES *pRequest) +{ + // Check we have a valid connection + + if (m_pMediaType == NULL) { + return VFW_E_NOT_CONNECTED; + } + + // NOTE We always create a DIB section with the source format type which + // may contain a source palette. When we do the BitBlt drawing operation + // the target display device may contain a different palette (we may not + // have the focus) in which case GDI will do after the palette mapping + + VIDEOINFOHEADER *pVideoInfo = (VIDEOINFOHEADER *) m_pMediaType->Format(); + + // When we call CreateDIBSection it implicitly maps only enough memory + // for the image as defined by thee BITMAPINFOHEADER. If the user asks + // for an image smaller than this then we reject the call, if they ask + // for an image larger than this then we return what they can have + + if ((DWORD) pRequest->cbBuffer < pVideoInfo->bmiHeader.biSizeImage) { + return E_INVALIDARG; + } + + // Reject buffer prefixes + + if (pRequest->cbPrefix > 0) { + return E_INVALIDARG; + } + + pRequest->cbBuffer = pVideoInfo->bmiHeader.biSizeImage; + return NOERROR; +} + + +// Agree the number of media sample buffers and their sizes. The base class +// this allocator is derived from allows samples to be aligned only on byte +// boundaries NOTE the buffers are not allocated until the Commit call + +STDMETHODIMP CImageAllocator::SetProperties( + __in ALLOCATOR_PROPERTIES * pRequest, + __out ALLOCATOR_PROPERTIES * pActual) +{ + ALLOCATOR_PROPERTIES Adjusted = *pRequest; + + // Check the parameters fit with the current connection + + HRESULT hr = CheckSizes(&Adjusted); + if (FAILED(hr)) { + return hr; + } + return CBaseAllocator::SetProperties(&Adjusted, pActual); +} + + +// Commit the memory by allocating the agreed number of media samples. For +// each sample we are committed to creating we have a CImageSample object +// that we use to manage it's resources. This is initialised with a DIBDATA +// structure that contains amongst other things the GDI DIBSECTION handle +// We will access the renderer media type during this so we must have locked +// (to prevent the format changing for example). The class overrides Commit +// and Decommit to do this locking (base class Commit in turn calls Alloc) + +HRESULT CImageAllocator::Alloc(void) +{ + ASSERT(m_pMediaType); + CImageSample *pSample; + DIBDATA DibData; + + // Check the base allocator says it's ok to continue + + HRESULT hr = CBaseAllocator::Alloc(); + if (FAILED(hr)) { + return hr; + } + + // We create a new memory mapped object although we don't map it into our + // address space because GDI does that in CreateDIBSection. It is possible + // that we run out of resources before creating all the samples in which + // case the available sample list is left with those already created + + ASSERT(m_lAllocated == 0); + while (m_lAllocated < m_lCount) { + + // Create and initialise a shared memory GDI buffer + + hr = CreateDIB(m_lSize,DibData); + if (FAILED(hr)) { + return hr; + } + + // Create the sample object and pass it the DIBDATA + + pSample = CreateImageSample(DibData.pBase,m_lSize); + if (pSample == NULL) { + EXECUTE_ASSERT(DeleteObject(DibData.hBitmap)); + EXECUTE_ASSERT(CloseHandle(DibData.hMapping)); + return E_OUTOFMEMORY; + } + + // Add the completed sample to the available list + + pSample->SetDIBData(&DibData); + m_lFree.Add(pSample); + m_lAllocated++; + } + return NOERROR; +} + + +// We have a virtual method that allocates the samples so that a derived class +// may override it and allocate more specialised sample objects. So long as it +// derives its samples from CImageSample then all this code will still work ok + +CImageSample *CImageAllocator::CreateImageSample(__in_bcount(Length) LPBYTE pData,LONG Length) +{ + HRESULT hr = NOERROR; + CImageSample *pSample; + + // Allocate the new sample and check the return codes + + pSample = new CImageSample((CBaseAllocator *) this, // Base class + NAME("Video sample"), // DEBUG name + (HRESULT *) &hr, // Return code + (LPBYTE) pData, // DIB address + (LONG) Length); // Size of DIB + + if (pSample == NULL || FAILED(hr)) { + delete pSample; + return NULL; + } + return pSample; +} + + +// This function allocates a shared memory block for use by the source filter +// generating DIBs for us to render. The memory block is created in shared +// memory so that GDI doesn't have to copy the memory when we do a BitBlt + +HRESULT CImageAllocator::CreateDIB(LONG InSize,DIBDATA &DibData) +{ + BITMAPINFO *pbmi; // Format information for pin + BYTE *pBase; // Pointer to the actual image + HANDLE hMapping; // Handle to mapped object + HBITMAP hBitmap; // DIB section bitmap handle + + // Create a file mapping object and map into our address space + + hMapping = CreateFileMapping(hMEMORY, // Use system page file + NULL, // No security attributes + PAGE_READWRITE, // Full access to memory + (DWORD) 0, // Less than 4Gb in size + InSize, // Size of buffer + NULL); // No name to section + if (hMapping == NULL) { + DWORD Error = GetLastError(); + return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, Error); + } + + // NOTE We always create a DIB section with the source format type which + // may contain a source palette. When we do the BitBlt drawing operation + // the target display device may contain a different palette (we may not + // have the focus) in which case GDI will do after the palette mapping + + pbmi = (BITMAPINFO *) HEADER(m_pMediaType->Format()); + if (m_pMediaType == NULL) { + DbgBreak("Invalid media type"); + } + + hBitmap = CreateDIBSection((HDC) NULL, // NO device context + pbmi, // Format information + DIB_RGB_COLORS, // Use the palette + (VOID **) &pBase, // Pointer to image data + hMapping, // Mapped memory handle + (DWORD) 0); // Offset into memory + + if (hBitmap == NULL || pBase == NULL) { + EXECUTE_ASSERT(CloseHandle(hMapping)); + DWORD Error = GetLastError(); + return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, Error); + } + + // Initialise the DIB information structure + + DibData.hBitmap = hBitmap; + DibData.hMapping = hMapping; + DibData.pBase = pBase; + DibData.PaletteVersion = PALETTE_VERSION; + GetObject(hBitmap,sizeof(DIBSECTION),(VOID *)&DibData.DibSection); + + return NOERROR; +} + + +// We use the media type during the DIBSECTION creation + +void CImageAllocator::NotifyMediaType(__in CMediaType *pMediaType) +{ + m_pMediaType = pMediaType; +} + + +// Overriden to increment the owning object's reference count + +STDMETHODIMP_(ULONG) CImageAllocator::NonDelegatingAddRef() +{ + return m_pFilter->AddRef(); +} + + +// Overriden to decrement the owning object's reference count + +STDMETHODIMP_(ULONG) CImageAllocator::NonDelegatingRelease() +{ + return m_pFilter->Release(); +} + + +// If you derive a class from CMediaSample that has to transport specialised +// member variables and entry points then there are three alternate solutions +// The first is to create a memory buffer larger than actually required by the +// sample and store your information either at the beginning of it or at the +// end, the former being moderately safer allowing for misbehaving transform +// filters. You then adjust the buffer address when you create the base media +// sample. This has the disadvantage of breaking up the memory allocated to +// the samples into separate blocks. The second solution is to implement a +// class derived from CMediaSample and support additional interface(s) that +// convey your private data. This means defining a custom interface. The final +// alternative is to create a class that inherits from CMediaSample and adds +// the private data structures, when you get an IMediaSample in your Receive() +// call check to see if your allocator is being used, and if it is then cast +// the IMediaSample into one of your objects. Additional checks can be made +// to ensure the sample's this pointer is known to be one of your own objects + +CImageSample::CImageSample(__inout CBaseAllocator *pAllocator, + __in_opt LPCTSTR pName, + __inout HRESULT *phr, + __in_bcount(length) LPBYTE pBuffer, + LONG length) : + CMediaSample(pName,pAllocator,phr,pBuffer,length), + m_bInit(FALSE) +{ + ASSERT(pAllocator); + ASSERT(pBuffer); +} + + +// Set the shared memory DIB information + +void CImageSample::SetDIBData(__in DIBDATA *pDibData) +{ + ASSERT(pDibData); + m_DibData = *pDibData; + m_bInit = TRUE; +} + + +// Retrieve the shared memory DIB data + +__out DIBDATA *CImageSample::GetDIBData() +{ + ASSERT(m_bInit == TRUE); + return &m_DibData; +} + + +// This class handles the creation of a palette. It is fairly specialist and +// is intended to simplify palette management for video renderer filters. It +// is for this reason that the constructor requires three other objects with +// which it interacts, namely a base media filter, a base window and a base +// drawing object although the base window or the draw object may be NULL to +// ignore that part of us. We try not to create and install palettes unless +// absolutely necessary as they typically require WM_PALETTECHANGED messages +// to be sent to every window thread in the system which is very expensive + +CImagePalette::CImagePalette(__inout CBaseFilter *pBaseFilter, + __inout CBaseWindow *pBaseWindow, + __inout CDrawImage *pDrawImage) : + m_pBaseWindow(pBaseWindow), + m_pFilter(pBaseFilter), + m_pDrawImage(pDrawImage), + m_hPalette(NULL) +{ + ASSERT(m_pFilter); +} + + +// Destructor + +#ifdef DEBUG +CImagePalette::~CImagePalette() +{ + ASSERT(m_hPalette == NULL); +} +#endif + + +// We allow dynamic format changes of the palette but rather than change the +// palette every time we call this to work out whether an update is required. +// If the original type didn't use a palette and the new one does (or vica +// versa) then we return TRUE. If neither formats use a palette we'll return +// FALSE. If both formats use a palette we compare their colours and return +// FALSE if they match. This therefore short circuits palette creation unless +// absolutely necessary since installing palettes is an expensive operation + +BOOL CImagePalette::ShouldUpdate(const VIDEOINFOHEADER *pNewInfo, + const VIDEOINFOHEADER *pOldInfo) +{ + // We may not have a current format yet + + if (pOldInfo == NULL) { + return TRUE; + } + + // Do both formats not require a palette + + if (ContainsPalette(pNewInfo) == FALSE) { + if (ContainsPalette(pOldInfo) == FALSE) { + return FALSE; + } + } + + // Compare the colours to see if they match + + DWORD VideoEntries = pNewInfo->bmiHeader.biClrUsed; + if (ContainsPalette(pNewInfo) == TRUE) + if (ContainsPalette(pOldInfo) == TRUE) + if (pOldInfo->bmiHeader.biClrUsed == VideoEntries) + if (pOldInfo->bmiHeader.biClrUsed > 0) + if (memcmp((PVOID) GetBitmapPalette(pNewInfo), + (PVOID) GetBitmapPalette(pOldInfo), + VideoEntries * sizeof(RGBQUAD)) == 0) { + + return FALSE; + } + return TRUE; +} + + +// This is normally called when the input pin type is set to install a palette +// We will typically be called from two different places. The first is when we +// have negotiated a palettised media type after connection, the other is when +// we receive a new type during processing with an updated palette in which +// case we must remove and release the resources held by the current palette + +// We can be passed an optional device name if we wish to prepare a palette +// for a specific monitor on a multi monitor system + +HRESULT CImagePalette::PreparePalette(const CMediaType *pmtNew, + const CMediaType *pmtOld, + __in LPSTR szDevice) +{ + const VIDEOINFOHEADER *pNewInfo = (VIDEOINFOHEADER *) pmtNew->Format(); + const VIDEOINFOHEADER *pOldInfo = (VIDEOINFOHEADER *) pmtOld->Format(); + ASSERT(pNewInfo); + + // This is an performance optimisation, when we get a media type we check + // to see if the format requires a palette change. If either we need one + // when previously we didn't or vica versa then this returns TRUE, if we + // previously needed a palette and we do now it compares their colours + + if (ShouldUpdate(pNewInfo,pOldInfo) == FALSE) { + NOTE("No update needed"); + return S_FALSE; + } + + // We must notify the filter graph that the application may have changed + // the palette although in practice we don't bother checking to see if it + // is really different. If it tries to get the palette either the window + // or renderer lock will ensure it doesn't get in until we are finished + + RemovePalette(); + m_pFilter->NotifyEvent(EC_PALETTE_CHANGED,0,0); + + // Do we need a palette for the new format + + if (ContainsPalette(pNewInfo) == FALSE) { + NOTE("New has no palette"); + return S_FALSE; + } + + if (m_pBaseWindow) { + m_pBaseWindow->LockPaletteLock(); + } + + // If we're changing the palette on the fly then we increment our palette + // cookie which is compared against the cookie also stored in all of our + // DIBSECTION media samples. If they don't match when we come to draw it + // then we know the sample is out of date and we'll update it's palette + + NOTE("Making new colour palette"); + m_hPalette = MakePalette(pNewInfo, szDevice); + ASSERT(m_hPalette != NULL); + + if (m_pBaseWindow) { + m_pBaseWindow->UnlockPaletteLock(); + } + + // The window in which the new palette is to be realised may be a NULL + // pointer to signal that no window is in use, if so we don't call it + // Some filters just want to use this object to create/manage palettes + + if (m_pBaseWindow) m_pBaseWindow->SetPalette(m_hPalette); + + // This is the only time where we need access to the draw object to say + // to it that a new palette will be arriving on a sample real soon. The + // constructor may take a NULL pointer in which case we don't call this + + if (m_pDrawImage) m_pDrawImage->IncrementPaletteVersion(); + return NOERROR; +} + + +// Helper function to copy a palette out of any kind of VIDEOINFO (ie it may +// be YUV or true colour) into a palettised VIDEOINFO. We use this changing +// palettes on DirectDraw samples as a source filter can attach a palette to +// any buffer (eg YUV) and hand it back. We make a new palette out of that +// format and then copy the palette colours into the current connection type + +HRESULT CImagePalette::CopyPalette(const CMediaType *pSrc,__out CMediaType *pDest) +{ + // Reset the destination palette before starting + + VIDEOINFOHEADER *pDestInfo = (VIDEOINFOHEADER *) pDest->Format(); + pDestInfo->bmiHeader.biClrUsed = 0; + pDestInfo->bmiHeader.biClrImportant = 0; + + // Does the destination have a palette + + if (PALETTISED(pDestInfo) == FALSE) { + NOTE("No destination palette"); + return S_FALSE; + } + + // Does the source contain a palette + + const VIDEOINFOHEADER *pSrcInfo = (VIDEOINFOHEADER *) pSrc->Format(); + if (ContainsPalette(pSrcInfo) == FALSE) { + NOTE("No source palette"); + return S_FALSE; + } + + // The number of colours may be zero filled + + DWORD PaletteEntries = pSrcInfo->bmiHeader.biClrUsed; + if (PaletteEntries == 0) { + DWORD Maximum = (1 << pSrcInfo->bmiHeader.biBitCount); + NOTE1("Setting maximum colours (%d)",Maximum); + PaletteEntries = Maximum; + } + + // Make sure the destination has enough room for the palette + + ASSERT(pSrcInfo->bmiHeader.biClrUsed <= iPALETTE_COLORS); + ASSERT(pSrcInfo->bmiHeader.biClrImportant <= PaletteEntries); + ASSERT(COLORS(pDestInfo) == GetBitmapPalette(pDestInfo)); + pDestInfo->bmiHeader.biClrUsed = PaletteEntries; + pDestInfo->bmiHeader.biClrImportant = pSrcInfo->bmiHeader.biClrImportant; + ULONG BitmapSize = GetBitmapFormatSize(HEADER(pSrcInfo)); + + if (pDest->FormatLength() < BitmapSize) { + NOTE("Reallocating destination"); + pDest->ReallocFormatBuffer(BitmapSize); + } + + // Now copy the palette colours across + + CopyMemory((PVOID) COLORS(pDestInfo), + (PVOID) GetBitmapPalette(pSrcInfo), + PaletteEntries * sizeof(RGBQUAD)); + + return NOERROR; +} + + +// This is normally called when the palette is changed (typically during a +// dynamic format change) to remove any palette we previously installed. We +// replace it (if necessary) in the video window with a standard VGA palette +// that should always be available even if this is a true colour display + +HRESULT CImagePalette::RemovePalette() +{ + if (m_pBaseWindow) { + m_pBaseWindow->LockPaletteLock(); + } + + // Do we have a palette to remove + + if (m_hPalette != NULL) { + + if (m_pBaseWindow) { + // Make sure that the window's palette handle matches + // our palette handle. + ASSERT(m_hPalette == m_pBaseWindow->GetPalette()); + + m_pBaseWindow->UnsetPalette(); + } + + EXECUTE_ASSERT(DeleteObject(m_hPalette)); + m_hPalette = NULL; + } + + if (m_pBaseWindow) { + m_pBaseWindow->UnlockPaletteLock(); + } + + return NOERROR; +} + + +// Called to create a palette for the object, the data structure used by GDI +// to describe a palette is a LOGPALETTE, this includes a variable number of +// PALETTEENTRY fields which are the colours, we have to convert the RGBQUAD +// colour fields we are handed in a BITMAPINFO from the media type into these +// This handles extraction of palettes from true colour and YUV media formats + +// We can be passed an optional device name if we wish to prepare a palette +// for a specific monitor on a multi monitor system + +HPALETTE CImagePalette::MakePalette(const VIDEOINFOHEADER *pVideoInfo, __in LPSTR szDevice) +{ + ASSERT(ContainsPalette(pVideoInfo) == TRUE); + ASSERT(pVideoInfo->bmiHeader.biClrUsed <= iPALETTE_COLORS); + BITMAPINFOHEADER *pHeader = HEADER(pVideoInfo); + + const RGBQUAD *pColours; // Pointer to the palette + LOGPALETTE *lp; // Used to create a palette + HPALETTE hPalette; // Logical palette object + + lp = (LOGPALETTE *) new BYTE[sizeof(LOGPALETTE) + SIZE_PALETTE]; + if (lp == NULL) { + return NULL; + } + + // Unfortunately for some hare brained reason a GDI palette entry (a + // PALETTEENTRY structure) is different to a palette entry from a DIB + // format (a RGBQUAD structure) so we have to do the field conversion + // The VIDEOINFO containing the palette may be a true colour type so + // we use GetBitmapPalette to skip over any bit fields if they exist + + lp->palVersion = PALVERSION; + lp->palNumEntries = (USHORT) pHeader->biClrUsed; + if (lp->palNumEntries == 0) lp->palNumEntries = (1 << pHeader->biBitCount); + pColours = GetBitmapPalette(pVideoInfo); + + for (DWORD dwCount = 0;dwCount < lp->palNumEntries;dwCount++) { + lp->palPalEntry[dwCount].peRed = pColours[dwCount].rgbRed; + lp->palPalEntry[dwCount].peGreen = pColours[dwCount].rgbGreen; + lp->palPalEntry[dwCount].peBlue = pColours[dwCount].rgbBlue; + lp->palPalEntry[dwCount].peFlags = 0; + } + + MakeIdentityPalette(lp->palPalEntry, lp->palNumEntries, szDevice); + + // Create a logical palette + + hPalette = CreatePalette(lp); + ASSERT(hPalette != NULL); + delete[] lp; + return hPalette; +} + + +// GDI does a fair job of compressing the palette entries you give it, so for +// example if you have five entries with an RGB colour (0,0,0) it will remove +// all but one of them. When you subsequently draw an image it will map from +// your logical palette to the compressed device palette. This function looks +// to see if it is trying to be an identity palette and if so sets the flags +// field in the PALETTEENTRYs so they remain expanded to boost performance + +// We can be passed an optional device name if we wish to prepare a palette +// for a specific monitor on a multi monitor system + +HRESULT CImagePalette::MakeIdentityPalette(__inout_ecount_full(iColours) PALETTEENTRY *pEntry,INT iColours, __in LPSTR szDevice) +{ + PALETTEENTRY SystemEntries[10]; // System palette entries + BOOL bIdentityPalette = TRUE; // Is an identity palette + ASSERT(iColours <= iPALETTE_COLORS); // Should have a palette + const int PalLoCount = 10; // First ten reserved colours + const int PalHiStart = 246; // Last VGA palette entries + + // Does this have the full colour range + + if (iColours < 10) { + return S_FALSE; + } + + // Apparently some displays have odd numbers of system colours + + // Get a DC on the right monitor - it's ugly, but this is the way you have + // to do it + HDC hdc; + if (szDevice == NULL || lstrcmpiLocaleIndependentA(szDevice, "DISPLAY") == 0) + hdc = CreateDCA("DISPLAY", NULL, NULL, NULL); + else + hdc = CreateDCA(NULL, szDevice, NULL, NULL); + if (NULL == hdc) { + return E_OUTOFMEMORY; + } + INT Reserved = GetDeviceCaps(hdc,NUMRESERVED); + if (Reserved != 20) { + DeleteDC(hdc); + return S_FALSE; + } + + // Compare our palette against the first ten system entries. The reason I + // don't do a memory compare between our two arrays of colours is because + // I am not sure what will be in the flags fields for the system entries + + UINT Result = GetSystemPaletteEntries(hdc,0,PalLoCount,SystemEntries); + for (UINT Count = 0;Count < Result;Count++) { + if (SystemEntries[Count].peRed != pEntry[Count].peRed || + SystemEntries[Count].peGreen != pEntry[Count].peGreen || + SystemEntries[Count].peBlue != pEntry[Count].peBlue) { + bIdentityPalette = FALSE; + } + } + + // And likewise compare against the last ten entries + + Result = GetSystemPaletteEntries(hdc,PalHiStart,PalLoCount,SystemEntries); + for (UINT Count = 0;Count < Result;Count++) { + if (INT(Count) + PalHiStart < iColours) { + if (SystemEntries[Count].peRed != pEntry[PalHiStart + Count].peRed || + SystemEntries[Count].peGreen != pEntry[PalHiStart + Count].peGreen || + SystemEntries[Count].peBlue != pEntry[PalHiStart + Count].peBlue) { + bIdentityPalette = FALSE; + } + } + } + + // If not an identity palette then return S_FALSE + + DeleteDC(hdc); + if (bIdentityPalette == FALSE) { + return S_FALSE; + } + + // Set the non VGA entries so that GDI doesn't map them + + for (UINT Count = PalLoCount;INT(Count) < min(PalHiStart,iColours);Count++) { + pEntry[Count].peFlags = PC_NOCOLLAPSE; + } + return NOERROR; +} + + +// Constructor initialises the VIDEOINFO we keep storing the current display +// format. The format can be changed at any time, to reset the format held +// by us call the RefreshDisplayType directly (it's a public method). Since +// more than one thread will typically call us (ie window threads resetting +// the type and source threads in the type checking methods) we have a lock + +CImageDisplay::CImageDisplay() +{ + RefreshDisplayType(NULL); +} + + + +// This initialises the format we hold which contains the display device type +// We do a conversion on the display device type in here so that when we start +// type checking input formats we can assume that certain fields have been set +// correctly, an example is when we make the 16 bit mask fields explicit. This +// is normally called when we receive WM_DEVMODECHANGED device change messages + +// The optional szDeviceName parameter tells us which monitor we are interested +// in for a multi monitor system + +HRESULT CImageDisplay::RefreshDisplayType(__in_opt LPSTR szDeviceName) +{ + CAutoLock cDisplayLock(this); + + // Set the preferred format type + + ZeroMemory((PVOID)&m_Display,sizeof(VIDEOINFOHEADER)+sizeof(TRUECOLORINFO)); + m_Display.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + m_Display.bmiHeader.biBitCount = FALSE; + + // Get the bit depth of a device compatible bitmap + + // get caps of whichever monitor they are interested in (multi monitor) + HDC hdcDisplay; + // it's ugly, but this is the way you have to do it + if (szDeviceName == NULL || lstrcmpiLocaleIndependentA(szDeviceName, "DISPLAY") == 0) + hdcDisplay = CreateDCA("DISPLAY", NULL, NULL, NULL); + else + hdcDisplay = CreateDCA(NULL, szDeviceName, NULL, NULL); + if (hdcDisplay == NULL) { + ASSERT(FALSE); + DbgLog((LOG_ERROR,1,TEXT("ACK! Can't get a DC for %hs"), + szDeviceName ? szDeviceName : "<NULL>")); + return E_FAIL; + } else { + DbgLog((LOG_TRACE,3,TEXT("Created a DC for %s"), + szDeviceName ? szDeviceName : "<NULL>")); + } + HBITMAP hbm = CreateCompatibleBitmap(hdcDisplay,1,1); + if ( hbm ) + { + GetDIBits(hdcDisplay,hbm,0,1,NULL,(BITMAPINFO *)&m_Display.bmiHeader,DIB_RGB_COLORS); + + // This call will get the colour table or the proper bitfields + GetDIBits(hdcDisplay,hbm,0,1,NULL,(BITMAPINFO *)&m_Display.bmiHeader,DIB_RGB_COLORS); + DeleteObject(hbm); + } + DeleteDC(hdcDisplay); + + // Complete the display type initialisation + + ASSERT(CheckHeaderValidity(&m_Display)); + UpdateFormat(&m_Display); + DbgLog((LOG_TRACE,3,TEXT("New DISPLAY bit depth =%d"), + m_Display.bmiHeader.biBitCount)); + return NOERROR; +} + + +// We assume throughout this code that any bitfields masks are allowed no +// more than eight bits to store a colour component. This checks that the +// bit count assumption is enforced and also makes sure that all the bits +// set are contiguous. We return a boolean TRUE if the field checks out ok + +BOOL CImageDisplay::CheckBitFields(const VIDEOINFO *pInput) +{ + DWORD *pBitFields = (DWORD *) BITMASKS(pInput); + + for (INT iColour = iRED;iColour <= iBLUE;iColour++) { + + // First of all work out how many bits are set + + DWORD SetBits = CountSetBits(pBitFields[iColour]); + if (SetBits > iMAXBITS || SetBits == 0) { + NOTE1("Bit fields for component %d invalid",iColour); + return FALSE; + } + + // Next work out the number of zero bits prefix + DWORD PrefixBits = CountPrefixBits(pBitFields[iColour]); + + // This is going to see if all the bits set are contiguous (as they + // should be). We know how much to shift them right by from the + // count of prefix bits. The number of bits set defines a mask, we + // invert this (ones complement) and AND it with the shifted bit + // fields. If the result is NON zero then there are bit(s) sticking + // out the left hand end which means they are not contiguous + + DWORD TestField = pBitFields[iColour] >> PrefixBits; + DWORD Mask = ULONG_MAX << SetBits; + if (TestField & Mask) { + NOTE1("Bit fields for component %d not contiguous",iColour); + return FALSE; + } + } + return TRUE; +} + + +// This counts the number of bits set in the input field + +DWORD CImageDisplay::CountSetBits(DWORD Field) +{ + // This is a relatively well known bit counting algorithm + + DWORD Count = 0; + DWORD init = Field; + + // Until the input is exhausted, count the number of bits + + while (init) { + init = init & (init - 1); // Turn off the bottommost bit + Count++; + } + return Count; +} + + +// This counts the number of zero bits upto the first one set NOTE the input +// field should have been previously checked to ensure there is at least one +// set although if we don't find one set we return the impossible value 32 + +DWORD CImageDisplay::CountPrefixBits(DWORD Field) +{ + DWORD Mask = 1; + DWORD Count = 0; + + while (TRUE) { + if (Field & Mask) { + return Count; + } + Count++; + + ASSERT(Mask != 0x80000000); + if (Mask == 0x80000000) { + return Count; + } + Mask <<= 1; + } +} + + +// This is called to check the BITMAPINFOHEADER for the input type. There are +// many implicit dependancies between the fields in a header structure which +// if we validate now make for easier manipulation in subsequent handling. We +// also check that the BITMAPINFOHEADER matches it's specification such that +// fields likes the number of planes is one, that it's structure size is set +// correctly and that the bitmap dimensions have not been set as negative + +BOOL CImageDisplay::CheckHeaderValidity(const VIDEOINFO *pInput) +{ + // Check the bitmap width and height are not negative. + + if (pInput->bmiHeader.biWidth <= 0 || + pInput->bmiHeader.biHeight <= 0) { + NOTE("Invalid bitmap dimensions"); + return FALSE; + } + + // Check the compression is either BI_RGB or BI_BITFIELDS + + if (pInput->bmiHeader.biCompression != BI_RGB) { + if (pInput->bmiHeader.biCompression != BI_BITFIELDS) { + NOTE("Invalid compression format"); + return FALSE; + } + } + + // If BI_BITFIELDS compression format check the colour depth + + if (pInput->bmiHeader.biCompression == BI_BITFIELDS) { + if (pInput->bmiHeader.biBitCount != 16) { + if (pInput->bmiHeader.biBitCount != 32) { + NOTE("BI_BITFIELDS not 16/32 bit depth"); + return FALSE; + } + } + } + + // Check the assumptions about the layout of the bit fields + + if (pInput->bmiHeader.biCompression == BI_BITFIELDS) { + if (CheckBitFields(pInput) == FALSE) { + NOTE("Bit fields are not valid"); + return FALSE; + } + } + + // Are the number of planes equal to one + + if (pInput->bmiHeader.biPlanes != 1) { + NOTE("Number of planes not one"); + return FALSE; + } + + // Check the image size is consistent (it can be zero) + + if (pInput->bmiHeader.biSizeImage != GetBitmapSize(&pInput->bmiHeader)) { + if (pInput->bmiHeader.biSizeImage) { + NOTE("Image size incorrectly set"); + return FALSE; + } + } + + // Check the size of the structure + + if (pInput->bmiHeader.biSize != sizeof(BITMAPINFOHEADER)) { + NOTE("Size of BITMAPINFOHEADER wrong"); + return FALSE; + } + return CheckPaletteHeader(pInput); +} + + +// This runs a few simple tests against the palette fields in the input to +// see if it looks vaguely correct. The tests look at the number of palette +// colours present, the number considered important and the biCompression +// field which should always be BI_RGB as no other formats are meaningful + +BOOL CImageDisplay::CheckPaletteHeader(const VIDEOINFO *pInput) +{ + // The checks here are for palettised videos only + + if (PALETTISED(pInput) == FALSE) { + if (pInput->bmiHeader.biClrUsed) { + NOTE("Invalid palette entries"); + return FALSE; + } + return TRUE; + } + + // Compression type of BI_BITFIELDS is meaningless for palette video + + if (pInput->bmiHeader.biCompression != BI_RGB) { + NOTE("Palettised video must be BI_RGB"); + return FALSE; + } + + // Check the number of palette colours is correct + + if (pInput->bmiHeader.biClrUsed > PALETTE_ENTRIES(pInput)) { + NOTE("Too many colours in palette"); + return FALSE; + } + + // The number of important colours shouldn't exceed the number used + + if (pInput->bmiHeader.biClrImportant > pInput->bmiHeader.biClrUsed) { + NOTE("Too many important colours"); + return FALSE; + } + return TRUE; +} + + +// Return the format of the video display + +const VIDEOINFO *CImageDisplay::GetDisplayFormat() +{ + return &m_Display; +} + + +// Return TRUE if the display uses a palette + +BOOL CImageDisplay::IsPalettised() +{ + return PALETTISED(&m_Display); +} + + +// Return the bit depth of the current display setting + +WORD CImageDisplay::GetDisplayDepth() +{ + return m_Display.bmiHeader.biBitCount; +} + + +// Initialise the optional fields in a VIDEOINFO. These are mainly to do with +// the source and destination rectangles and palette information such as the +// number of colours present. It simplifies our code just a little if we don't +// have to keep checking for all the different valid permutations in a header +// every time we want to do anything with it (an example would be creating a +// palette). We set the base class media type before calling this function so +// that the media types between the pins match after a connection is made + +HRESULT CImageDisplay::UpdateFormat(__inout VIDEOINFO *pVideoInfo) +{ + ASSERT(pVideoInfo); + + BITMAPINFOHEADER *pbmi = HEADER(pVideoInfo); + SetRectEmpty(&pVideoInfo->rcSource); + SetRectEmpty(&pVideoInfo->rcTarget); + + // Set the number of colours explicitly + + if (PALETTISED(pVideoInfo)) { + if (pVideoInfo->bmiHeader.biClrUsed == 0) { + pVideoInfo->bmiHeader.biClrUsed = PALETTE_ENTRIES(pVideoInfo); + } + } + + // The number of important colours shouldn't exceed the number used, on + // some displays the number of important colours is not initialised when + // retrieving the display type so we set the colours used correctly + + if (pVideoInfo->bmiHeader.biClrImportant > pVideoInfo->bmiHeader.biClrUsed) { + pVideoInfo->bmiHeader.biClrImportant = PALETTE_ENTRIES(pVideoInfo); + } + + // Change the image size field to be explicit + + if (pVideoInfo->bmiHeader.biSizeImage == 0) { + pVideoInfo->bmiHeader.biSizeImage = GetBitmapSize(&pVideoInfo->bmiHeader); + } + return NOERROR; +} + + +// Lots of video rendering filters want code to check proposed formats are ok +// This checks the VIDEOINFO we are passed as a media type. If the media type +// is a valid media type then we return NOERROR otherwise E_INVALIDARG. Note +// however we only accept formats that can be easily displayed in the display +// so if we are on a 16 bit device we will not accept 24 bit images. The one +// complexity is that most displays draw 8 bit palettised images efficiently +// Also if the input format is less colour bits per pixel then we also accept + +HRESULT CImageDisplay::CheckVideoType(const VIDEOINFO *pInput) +{ + // First of all check the VIDEOINFOHEADER looks correct + + if (CheckHeaderValidity(pInput) == FALSE) { + return E_INVALIDARG; + } + + // Virtually all devices support palettised images efficiently + + if (m_Display.bmiHeader.biBitCount == pInput->bmiHeader.biBitCount) { + if (PALETTISED(pInput) == TRUE) { + ASSERT(PALETTISED(&m_Display) == TRUE); + NOTE("(Video) Type connection ACCEPTED"); + return NOERROR; + } + } + + + // Is the display depth greater than the input format + + if (m_Display.bmiHeader.biBitCount > pInput->bmiHeader.biBitCount) { + NOTE("(Video) Mismatch agreed"); + return NOERROR; + } + + // Is the display depth less than the input format + + if (m_Display.bmiHeader.biBitCount < pInput->bmiHeader.biBitCount) { + NOTE("(Video) Format mismatch"); + return E_INVALIDARG; + } + + + // Both input and display formats are either BI_RGB or BI_BITFIELDS + + ASSERT(m_Display.bmiHeader.biBitCount == pInput->bmiHeader.biBitCount); + ASSERT(PALETTISED(pInput) == FALSE); + ASSERT(PALETTISED(&m_Display) == FALSE); + + // BI_RGB 16 bit representation is implicitly RGB555, and likewise BI_RGB + // 24 bit representation is RGB888. So we initialise a pointer to the bit + // fields they really mean and check against the display device format + // This is only going to be called when both formats are equal bits pixel + + const DWORD *pInputMask = GetBitMasks(pInput); + const DWORD *pDisplayMask = GetBitMasks((VIDEOINFO *)&m_Display); + + if (pInputMask[iRED] != pDisplayMask[iRED] || + pInputMask[iGREEN] != pDisplayMask[iGREEN] || + pInputMask[iBLUE] != pDisplayMask[iBLUE]) { + + NOTE("(Video) Bit field mismatch"); + return E_INVALIDARG; + } + + NOTE("(Video) Type connection ACCEPTED"); + return NOERROR; +} + + +// Return the bit masks for the true colour VIDEOINFO provided + +const DWORD *CImageDisplay::GetBitMasks(const VIDEOINFO *pVideoInfo) +{ + static const DWORD FailMasks[] = {0,0,0}; + + if (pVideoInfo->bmiHeader.biCompression == BI_BITFIELDS) { + return BITMASKS(pVideoInfo); + } + + ASSERT(pVideoInfo->bmiHeader.biCompression == BI_RGB); + + switch (pVideoInfo->bmiHeader.biBitCount) { + case 16: return bits555; + case 24: return bits888; + case 32: return bits888; + default: return FailMasks; + } +} + + +// Check to see if we can support media type pmtIn as proposed by the output +// pin - We first check that the major media type is video and also identify +// the media sub type. Then we thoroughly check the VIDEOINFO type provided +// As well as the contained VIDEOINFO being correct the major type must be +// video, the subtype a recognised video format and the type GUID correct + +HRESULT CImageDisplay::CheckMediaType(const CMediaType *pmtIn) +{ + // Does this have a VIDEOINFOHEADER format block + + const GUID *pFormatType = pmtIn->FormatType(); + if (*pFormatType != FORMAT_VideoInfo) { + NOTE("Format GUID not a VIDEOINFOHEADER"); + return E_INVALIDARG; + } + ASSERT(pmtIn->Format()); + + // Check the format looks reasonably ok + + ULONG Length = pmtIn->FormatLength(); + if (Length < SIZE_VIDEOHEADER) { + NOTE("Format smaller than a VIDEOHEADER"); + return E_FAIL; + } + + VIDEOINFO *pInput = (VIDEOINFO *) pmtIn->Format(); + + // Check the major type is MEDIATYPE_Video + + const GUID *pMajorType = pmtIn->Type(); + if (*pMajorType != MEDIATYPE_Video) { + NOTE("Major type not MEDIATYPE_Video"); + return E_INVALIDARG; + } + + // Check we can identify the media subtype + + const GUID *pSubType = pmtIn->Subtype(); + if (GetBitCount(pSubType) == USHRT_MAX) { + NOTE("Invalid video media subtype"); + return E_INVALIDARG; + } + return CheckVideoType(pInput); +} + + +// Given a video format described by a VIDEOINFO structure we return the mask +// that is used to obtain the range of acceptable colours for this type, for +// example, the mask for a 24 bit true colour format is 0xFF in all cases. A +// 16 bit 5:6:5 display format uses 0xF8, 0xFC and 0xF8, therefore given any +// RGB triplets we can AND them with these fields to find one that is valid + +BOOL CImageDisplay::GetColourMask(__out DWORD *pMaskRed, + __out DWORD *pMaskGreen, + __out DWORD *pMaskBlue) +{ + CAutoLock cDisplayLock(this); + *pMaskRed = 0xFF; + *pMaskGreen = 0xFF; + *pMaskBlue = 0xFF; + + // If this format is palettised then it doesn't have bit fields + + if (m_Display.bmiHeader.biBitCount < 16) { + return FALSE; + } + + // If this is a 24 bit true colour display then it can handle all the + // possible colour component ranges described by a byte. It is never + // allowed for a 24 bit colour depth image to have BI_BITFIELDS set + + if (m_Display.bmiHeader.biBitCount == 24) { + ASSERT(m_Display.bmiHeader.biCompression == BI_RGB); + return TRUE; + } + + // Calculate the mask based on the format's bit fields + + const DWORD *pBitFields = (DWORD *) GetBitMasks((VIDEOINFO *)&m_Display); + DWORD *pOutputMask[] = { pMaskRed, pMaskGreen, pMaskBlue }; + + // We know from earlier testing that there are no more than iMAXBITS + // bits set in the mask and that they are all contiguous. All that + // therefore remains is to shift them into the correct position + + for (INT iColour = iRED;iColour <= iBLUE;iColour++) { + + // This works out how many bits there are and where they live + + DWORD PrefixBits = CountPrefixBits(pBitFields[iColour]); + DWORD SetBits = CountSetBits(pBitFields[iColour]); + + // The first shift moves the bit field so that it is right justified + // in the DWORD, after which we then shift it back left which then + // puts the leading bit in the bytes most significant bit position + + *(pOutputMask[iColour]) = pBitFields[iColour] >> PrefixBits; + *(pOutputMask[iColour]) <<= (iMAXBITS - SetBits); + } + return TRUE; +} + + +/* Helper to convert to VIDEOINFOHEADER2 +*/ +STDAPI ConvertVideoInfoToVideoInfo2(__inout AM_MEDIA_TYPE *pmt) +{ + if (pmt->formattype != FORMAT_VideoInfo) { + return E_INVALIDARG; + } + if (NULL == pmt->pbFormat || pmt->cbFormat < sizeof(VIDEOINFOHEADER)) { + return E_INVALIDARG; + } + VIDEOINFO *pVideoInfo = (VIDEOINFO *)pmt->pbFormat; + DWORD dwNewSize; + HRESULT hr = DWordAdd(pmt->cbFormat, sizeof(VIDEOINFOHEADER2) - sizeof(VIDEOINFOHEADER), &dwNewSize); + if (FAILED(hr)) { + return hr; + } + PVOID pvNew = CoTaskMemAlloc(dwNewSize); + if (pvNew == NULL) { + return E_OUTOFMEMORY; + } + CopyMemory(pvNew, pmt->pbFormat, FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader)); + ZeroMemory((PBYTE)pvNew + FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader), + sizeof(VIDEOINFOHEADER2) - sizeof(VIDEOINFOHEADER)); + CopyMemory((PBYTE)pvNew + FIELD_OFFSET(VIDEOINFOHEADER2, bmiHeader), + pmt->pbFormat + FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader), + pmt->cbFormat - FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader)); + VIDEOINFOHEADER2 *pVideoInfo2 = (VIDEOINFOHEADER2 *)pvNew; + pVideoInfo2->dwPictAspectRatioX = (DWORD)pVideoInfo2->bmiHeader.biWidth; + pVideoInfo2->dwPictAspectRatioY = (DWORD)abs(pVideoInfo2->bmiHeader.biHeight); + pmt->formattype = FORMAT_VideoInfo2; + CoTaskMemFree(pmt->pbFormat); + pmt->pbFormat = (PBYTE)pvNew; + pmt->cbFormat += sizeof(VIDEOINFOHEADER2) - sizeof(VIDEOINFOHEADER); + return S_OK; +} + + +// Check a media type containing VIDEOINFOHEADER +STDAPI CheckVideoInfoType(const AM_MEDIA_TYPE *pmt) +{ + if (NULL == pmt || NULL == pmt->pbFormat) { + return E_POINTER; + } + if (pmt->majortype != MEDIATYPE_Video || + pmt->formattype != FORMAT_VideoInfo || + pmt->cbFormat < sizeof(VIDEOINFOHEADER)) { + return VFW_E_TYPE_NOT_ACCEPTED; + } + const VIDEOINFOHEADER *pHeader = (const VIDEOINFOHEADER *)pmt->pbFormat; + if (!ValidateBitmapInfoHeader( + &pHeader->bmiHeader, + pmt->cbFormat - FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader))) { + return VFW_E_TYPE_NOT_ACCEPTED; + } + + return S_OK; +} + +// Check a media type containing VIDEOINFOHEADER2 +STDAPI CheckVideoInfo2Type(const AM_MEDIA_TYPE *pmt) +{ + if (NULL == pmt || NULL == pmt->pbFormat) { + return E_POINTER; + } + if (pmt->majortype != MEDIATYPE_Video || + pmt->formattype != FORMAT_VideoInfo2 || + pmt->cbFormat < sizeof(VIDEOINFOHEADER2)) { + return VFW_E_TYPE_NOT_ACCEPTED; + } + const VIDEOINFOHEADER2 *pHeader = (const VIDEOINFOHEADER2 *)pmt->pbFormat; + if (!ValidateBitmapInfoHeader( + &pHeader->bmiHeader, + pmt->cbFormat - FIELD_OFFSET(VIDEOINFOHEADER2, bmiHeader))) { + return VFW_E_TYPE_NOT_ACCEPTED; + } + + return S_OK; +} diff --git a/Src/Plugins/Input/in_dshow/base/winutil.h b/Src/Plugins/Input/in_dshow/base/winutil.h new file mode 100644 index 00000000..641b53a4 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/winutil.h @@ -0,0 +1,419 @@ +//------------------------------------------------------------------------------ +// File: WinUtil.h +// +// Desc: DirectShow base classes - defines generic handler classes. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +// Make sure that you call PrepareWindow to initialise the window after +// the object has been constructed. It is a separate method so that +// derived classes can override useful methods like MessageLoop. Also +// any derived class must call DoneWithWindow in its destructor. If it +// doesn't a message may be retrieved and call a derived class member +// function while a thread is executing the base class destructor code + +#ifndef __WINUTIL__ +#define __WINUTIL__ + +const int DEFWIDTH = 320; // Initial window width +const int DEFHEIGHT = 240; // Initial window height +const int CAPTION = 256; // Maximum length of caption +const int TIMELENGTH = 50; // Maximum length of times +const int PROFILESTR = 128; // Normal profile string +const WORD PALVERSION = 0x300; // GDI palette version +const LONG PALETTE_VERSION = (LONG) 1; // Initial palette version +const COLORREF VIDEO_COLOUR = 0; // Defaults to black background +const HANDLE hMEMORY = (HANDLE) (-1); // Says to open as memory file + +#define WIDTH(x) ((*(x)).right - (*(x)).left) +#define HEIGHT(x) ((*(x)).bottom - (*(x)).top) +#define SHOWSTAGE TEXT("WM_SHOWSTAGE") +#define SHOWSTAGETOP TEXT("WM_SHOWSTAGETOP") +#define REALIZEPALETTE TEXT("WM_REALIZEPALETTE") + +class AM_NOVTABLE CBaseWindow +{ +protected: + + HINSTANCE m_hInstance; // Global module instance handle + HWND m_hwnd; // Handle for our window + HDC m_hdc; // Device context for the window + LONG m_Width; // Client window width + LONG m_Height; // Client window height + BOOL m_bActivated; // Has the window been activated + LPTSTR m_pClassName; // Static string holding class name + DWORD m_ClassStyles; // Passed in to our constructor + DWORD m_WindowStyles; // Likewise the initial window styles + DWORD m_WindowStylesEx; // And the extended window styles + UINT m_ShowStageMessage; // Have the window shown with focus + UINT m_ShowStageTop; // Makes the window WS_EX_TOPMOST + UINT m_RealizePalette; // Makes us realize our new palette + HDC m_MemoryDC; // Used for fast BitBlt operations + HPALETTE m_hPalette; // Handle to any palette we may have + BYTE m_bNoRealize; // Don't realize palette now + BYTE m_bBackground; // Should we realise in background + BYTE m_bRealizing; // already realizing the palette + CCritSec m_WindowLock; // Serialise window object access + BOOL m_bDoGetDC; // Should this window get a DC + bool m_bDoPostToDestroy; // Use PostMessage to destroy + CCritSec m_PaletteLock; // This lock protects m_hPalette. + // It should be held anytime the + // program use the value of m_hPalette. + + // Maps windows message procedure into C++ methods + friend LRESULT CALLBACK WndProc(HWND hwnd, // Window handle + UINT uMsg, // Message ID + WPARAM wParam, // First parameter + LPARAM lParam); // Other parameter + + virtual LRESULT OnPaletteChange(HWND hwnd, UINT Message); + +public: + + CBaseWindow(BOOL bDoGetDC = TRUE, bool bPostToDestroy = false); + +#ifdef DEBUG + virtual ~CBaseWindow(); +#endif + + virtual HRESULT DoneWithWindow(); + virtual HRESULT PrepareWindow(); + virtual HRESULT InactivateWindow(); + virtual HRESULT ActivateWindow(); + virtual BOOL OnSize(LONG Width, LONG Height); + virtual BOOL OnClose(); + virtual RECT GetDefaultRect(); + virtual HRESULT UninitialiseWindow(); + virtual HRESULT InitialiseWindow(HWND hwnd); + + HRESULT CompleteConnect(); + HRESULT DoCreateWindow(); + + HRESULT PerformanceAlignWindow(); + HRESULT DoShowWindow(LONG ShowCmd); + void PaintWindow(BOOL bErase); + void DoSetWindowForeground(BOOL bFocus); + virtual HRESULT SetPalette(HPALETTE hPalette); + void SetRealize(BOOL bRealize) + { + m_bNoRealize = !bRealize; + } + + // Jump over to the window thread to set the current palette + HRESULT SetPalette(); + void UnsetPalette(void); + virtual HRESULT DoRealisePalette(BOOL bForceBackground = FALSE); + + void LockPaletteLock(); + void UnlockPaletteLock(); + + virtual BOOL PossiblyEatMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) + { return FALSE; }; + + // Access our window information + + bool WindowExists(); + LONG GetWindowWidth(); + LONG GetWindowHeight(); + HWND GetWindowHWND(); + HDC GetMemoryHDC(); + HDC GetWindowHDC(); + + #ifdef DEBUG + HPALETTE GetPalette(); + #endif // DEBUG + + // This is the window procedure the derived object should override + + virtual LRESULT OnReceiveMessage(HWND hwnd, // Window handle + UINT uMsg, // Message ID + WPARAM wParam, // First parameter + LPARAM lParam); // Other parameter + + // Must be overriden to return class and window styles + + virtual LPTSTR GetClassWindowStyles( + __out DWORD *pClassStyles, // Class styles + __out DWORD *pWindowStyles, // Window styles + __out DWORD *pWindowStylesEx) PURE; // Extended styles +}; + + +// This helper class is entirely subservient to the owning CBaseWindow object +// All this object does is to split out the actual drawing operation from the +// main object (because it was becoming too large). We have a number of entry +// points to set things like the draw device contexts, to implement the actual +// drawing and to set the destination rectangle in the client window. We have +// no critical section locking in this class because we are used exclusively +// by the owning window object which looks after serialising calls into us + +// If you want to use this class make sure you call NotifyAllocator once the +// allocate has been agreed, also call NotifyMediaType with a pointer to a +// NON stack based CMediaType once that has been set (we keep a pointer to +// the original rather than taking a copy). When the palette changes call +// IncrementPaletteVersion (easiest thing to do is to also call this method +// in the SetMediaType method most filters implement). Finally before you +// start rendering anything call SetDrawContext so that we can get the HDCs +// for drawing from the CBaseWindow object we are given during construction + +class CDrawImage +{ +protected: + + CBaseWindow *m_pBaseWindow; // Owning video window object + CRefTime m_StartSample; // Start time for the current sample + CRefTime m_EndSample; // And likewise it's end sample time + HDC m_hdc; // Main window device context + HDC m_MemoryDC; // Offscreen draw device context + RECT m_TargetRect; // Target destination rectangle + RECT m_SourceRect; // Source image rectangle + BOOL m_bStretch; // Do we have to stretch the images + BOOL m_bUsingImageAllocator; // Are the samples shared DIBSECTIONs + CMediaType *m_pMediaType; // Pointer to the current format + int m_perfidRenderTime; // Time taken to render an image + LONG m_PaletteVersion; // Current palette version cookie + + // Draw the video images in the window + + void SlowRender(IMediaSample *pMediaSample); + void FastRender(IMediaSample *pMediaSample); + void DisplaySampleTimes(IMediaSample *pSample); + void UpdateColourTable(HDC hdc,__in BITMAPINFOHEADER *pbmi); + void SetStretchMode(); + +public: + + // Used to control the image drawing + + CDrawImage(__inout CBaseWindow *pBaseWindow); + BOOL DrawImage(IMediaSample *pMediaSample); + BOOL DrawVideoImageHere(HDC hdc, IMediaSample *pMediaSample, + __in LPRECT lprcSrc, __in LPRECT lprcDst); + void SetDrawContext(); + void SetTargetRect(__in RECT *pTargetRect); + void SetSourceRect(__in RECT *pSourceRect); + void GetTargetRect(__out RECT *pTargetRect); + void GetSourceRect(__out RECT *pSourceRect); + virtual RECT ScaleSourceRect(const RECT *pSource); + + // Handle updating palettes as they change + + LONG GetPaletteVersion(); + void ResetPaletteVersion(); + void IncrementPaletteVersion(); + + // Tell us media types and allocator assignments + + void NotifyAllocator(BOOL bUsingImageAllocator); + void NotifyMediaType(__in CMediaType *pMediaType); + BOOL UsingImageAllocator(); + + // Called when we are about to draw an image + + void NotifyStartDraw() { + MSR_START(m_perfidRenderTime); + }; + + // Called when we complete an image rendering + + void NotifyEndDraw() { + MSR_STOP(m_perfidRenderTime); + }; +}; + + +// This is the structure used to keep information about each GDI DIB. All the +// samples we create from our allocator will have a DIBSECTION allocated to +// them. When we receive the sample we know we can BitBlt straight to an HDC + +typedef struct tagDIBDATA { + + LONG PaletteVersion; // Current palette version in use + DIBSECTION DibSection; // Details of DIB section allocated + HBITMAP hBitmap; // Handle to bitmap for drawing + HANDLE hMapping; // Handle to shared memory block + BYTE *pBase; // Pointer to base memory address + +} DIBDATA; + + +// This class inherits from CMediaSample and uses all of it's methods but it +// overrides the constructor to initialise itself with the DIBDATA structure +// When we come to render an IMediaSample we will know if we are using our own +// allocator, and if we are, we can cast the IMediaSample to a pointer to one +// of these are retrieve the DIB section information and hence the HBITMAP + +class CImageSample : public CMediaSample +{ +protected: + + DIBDATA m_DibData; // Information about the DIBSECTION + BOOL m_bInit; // Is the DIB information setup + +public: + + // Constructor + + CImageSample(__inout CBaseAllocator *pAllocator, + __in_opt LPCTSTR pName, + __inout HRESULT *phr, + __in_bcount(length) LPBYTE pBuffer, + LONG length); + + // Maintain the DIB/DirectDraw state + + void SetDIBData(__in DIBDATA *pDibData); + __out DIBDATA *GetDIBData(); +}; + + +// This is an allocator based on the abstract CBaseAllocator base class that +// allocates sample buffers in shared memory. The number and size of these +// are determined when the output pin calls Prepare on us. The shared memory +// blocks are used in subsequent calls to GDI CreateDIBSection, once that +// has been done the output pin can fill the buffers with data which will +// then be handed to GDI through BitBlt calls and thereby remove one copy + +class CImageAllocator : public CBaseAllocator +{ +protected: + + CBaseFilter *m_pFilter; // Delegate reference counts to + CMediaType *m_pMediaType; // Pointer to the current format + + // Used to create and delete samples + + HRESULT Alloc(); + void Free(); + + // Manage the shared DIBSECTION and DCI/DirectDraw buffers + + HRESULT CreateDIB(LONG InSize,DIBDATA &DibData); + STDMETHODIMP CheckSizes(__in ALLOCATOR_PROPERTIES *pRequest); + virtual CImageSample *CreateImageSample(__in_bcount(Length) LPBYTE pData,LONG Length); + +public: + + // Constructor and destructor + + CImageAllocator(__inout CBaseFilter *pFilter,__in_opt LPCTSTR pName,__inout HRESULT *phr); +#ifdef DEBUG + ~CImageAllocator(); +#endif + + STDMETHODIMP_(ULONG) NonDelegatingAddRef(); + STDMETHODIMP_(ULONG) NonDelegatingRelease(); + void NotifyMediaType(__in CMediaType *pMediaType); + + // Agree the number of buffers to be used and their size + + STDMETHODIMP SetProperties( + __in ALLOCATOR_PROPERTIES *pRequest, + __out ALLOCATOR_PROPERTIES *pActual); +}; + + +// This class is a fairly specialised helper class for image renderers that +// have to create and manage palettes. The CBaseWindow class looks after +// realising palettes once they have been installed. This class can be used +// to create the palette handles from a media format (which must contain a +// VIDEOINFO structure in the format block). We try to make the palette an +// identity palette to maximise performance and also only change palettes +// if actually required to (we compare palette colours before updating). +// All the methods are virtual so that they can be overriden if so required + +class CImagePalette +{ +protected: + + CBaseWindow *m_pBaseWindow; // Window to realise palette in + CBaseFilter *m_pFilter; // Media filter to send events + CDrawImage *m_pDrawImage; // Object who will be drawing + HPALETTE m_hPalette; // The palette handle we own + +public: + + CImagePalette(__inout CBaseFilter *pBaseFilter, + __inout CBaseWindow *pBaseWindow, + __inout CDrawImage *pDrawImage); + +#ifdef DEBUG + virtual ~CImagePalette(); +#endif + + static HPALETTE MakePalette(const VIDEOINFOHEADER *pVideoInfo, __in LPSTR szDevice); + HRESULT RemovePalette(); + static HRESULT MakeIdentityPalette(__inout_ecount_full(iColours) PALETTEENTRY *pEntry,INT iColours, __in LPSTR szDevice); + HRESULT CopyPalette(const CMediaType *pSrc,__out CMediaType *pDest); + BOOL ShouldUpdate(const VIDEOINFOHEADER *pNewInfo,const VIDEOINFOHEADER *pOldInfo); + HRESULT PreparePalette(const CMediaType *pmtNew,const CMediaType *pmtOld,__in LPSTR szDevice); + + BOOL DrawVideoImageHere(HDC hdc, IMediaSample *pMediaSample, __in LPRECT lprcSrc, __in LPRECT lprcDst) + { + return m_pDrawImage->DrawVideoImageHere(hdc, pMediaSample, lprcSrc,lprcDst); + } +}; + + +// Another helper class really for video based renderers. Most such renderers +// need to know what the display format is to some degree or another. This +// class initialises itself with the display format. The format can be asked +// for through GetDisplayFormat and various other accessor functions. If a +// filter detects a display format change (perhaps it gets a WM_DEVMODECHANGE +// message then it can call RefreshDisplayType to reset that format). Also +// many video renderers will want to check formats as they are proposed by +// source filters. This class provides methods to check formats and only +// accept those video formats that can be efficiently drawn using GDI calls + +class CImageDisplay : public CCritSec +{ +protected: + + // This holds the display format; biSize should not be too big, so we can + // safely use the VIDEOINFO structure + VIDEOINFO m_Display; + + static DWORD CountSetBits(const DWORD Field); + static DWORD CountPrefixBits(const DWORD Field); + static BOOL CheckBitFields(const VIDEOINFO *pInput); + +public: + + // Constructor and destructor + + CImageDisplay(); + + // Used to manage BITMAPINFOHEADERs and the display format + + const VIDEOINFO *GetDisplayFormat(); + HRESULT RefreshDisplayType(__in_opt LPSTR szDeviceName); + static BOOL CheckHeaderValidity(const VIDEOINFO *pInput); + static BOOL CheckPaletteHeader(const VIDEOINFO *pInput); + BOOL IsPalettised(); + WORD GetDisplayDepth(); + + // Provide simple video format type checking + + HRESULT CheckMediaType(const CMediaType *pmtIn); + HRESULT CheckVideoType(const VIDEOINFO *pInput); + HRESULT UpdateFormat(__inout VIDEOINFO *pVideoInfo); + const DWORD *GetBitMasks(const VIDEOINFO *pVideoInfo); + + BOOL GetColourMask(__out DWORD *pMaskRed, + __out DWORD *pMaskGreen, + __out DWORD *pMaskBlue); +}; + +// Convert a FORMAT_VideoInfo to FORMAT_VideoInfo2 +STDAPI ConvertVideoInfoToVideoInfo2(__inout AM_MEDIA_TYPE *pmt); + +// Check a media type containing VIDEOINFOHEADER +STDAPI CheckVideoInfoType(const AM_MEDIA_TYPE *pmt); + +// Check a media type containing VIDEOINFOHEADER +STDAPI CheckVideoInfo2Type(const AM_MEDIA_TYPE *pmt); + +#endif // __WINUTIL__ + diff --git a/Src/Plugins/Input/in_dshow/base/wxdebug.cpp b/Src/Plugins/Input/in_dshow/base/wxdebug.cpp new file mode 100644 index 00000000..ae27874b --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/wxdebug.cpp @@ -0,0 +1,1474 @@ +//------------------------------------------------------------------------------ +// File: WXDebug.cpp +// +// Desc: DirectShow base classes - implements ActiveX system debugging +// facilities. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +//#define _WINDLL + +#include <streams.h> +#include <stdarg.h> +#include <stdio.h> +#include <dvdmedia.h> + +#ifdef DEBUG +#ifdef UNICODE +#ifndef _UNICODE +#define _UNICODE +#endif // _UNICODE +#endif // UNICODE +#endif // DEBUG + +#include <tchar.h> +#include <strsafe.h> + +#ifdef DEBUG +static void DisplayBITMAPINFO(const BITMAPINFOHEADER* pbmi); +static void DisplayRECT(LPCTSTR szLabel, const RECT& rc); + +// The Win32 wsprintf() function writes a maximum of 1024 characters to it's output buffer. +// See the documentation for wsprintf()'s lpOut parameter for more information. +const INT iDEBUGINFO = 1024; // Used to format strings + +/* For every module and executable we store a debugging level for each of + the five categories (eg LOG_ERROR and LOG_TIMING). This makes it easy + to isolate and debug individual modules without seeing everybody elses + spurious debug output. The keys are stored in the registry under the + HKEY_LOCAL_MACHINE\SOFTWARE\Debug\<Module Name>\<KeyName> key values + NOTE these must be in the same order as their enumeration definition */ + +const LPCTSTR pKeyNames[] = { + TEXT("TIMING"), // Timing and performance measurements + TEXT("TRACE"), // General step point call tracing + TEXT("MEMORY"), // Memory and object allocation/destruction + TEXT("LOCKING"), // Locking/unlocking of critical sections + TEXT("ERROR"), // Debug error notification + TEXT("CUSTOM1"), + TEXT("CUSTOM2"), + TEXT("CUSTOM3"), + TEXT("CUSTOM4"), + TEXT("CUSTOM5") + }; + +const TCHAR CAutoTrace::_szEntering[] = TEXT("->: %s"); +const TCHAR CAutoTrace::_szLeaving[] = TEXT("<-: %s"); + +const INT iMAXLEVELS = NUMELMS(pKeyNames); // Maximum debug categories + +HINSTANCE m_hInst; // Module instance handle +TCHAR m_ModuleName[iDEBUGINFO]; // Cut down module name +DWORD m_Levels[iMAXLEVELS]; // Debug level per category +CRITICAL_SECTION m_CSDebug; // Controls access to list +DWORD m_dwNextCookie; // Next active object ID +ObjectDesc *pListHead = NULL; // First active object +DWORD m_dwObjectCount; // Active object count +BOOL m_bInit = FALSE; // Have we been initialised +HANDLE m_hOutput = INVALID_HANDLE_VALUE; // Optional output written here +DWORD dwWaitTimeout = INFINITE; // Default timeout value +DWORD dwTimeOffset; // Time of first DbgLog call +bool g_fUseKASSERT = false; // don't create messagebox +bool g_fDbgInDllEntryPoint = false; +bool g_fAutoRefreshLevels = false; + +LPCTSTR pBaseKey = TEXT("SOFTWARE\\Microsoft\\DirectShow\\Debug"); +LPCTSTR pGlobalKey = TEXT("GLOBAL"); +static CHAR *pUnknownName = "UNKNOWN"; + +LPCTSTR TimeoutName = TEXT("TIMEOUT"); + +/* This sets the instance handle that the debug library uses to find + the module's file name from the Win32 GetModuleFileName function */ + +void WINAPI DbgInitialise(HINSTANCE hInst) +{ + InitializeCriticalSection(&m_CSDebug); + m_bInit = TRUE; + + m_hInst = hInst; + DbgInitModuleName(); + if (GetProfileInt(m_ModuleName, TEXT("BreakOnLoad"), 0)) + DebugBreak(); + DbgInitModuleSettings(false); + DbgInitGlobalSettings(true); + dwTimeOffset = timeGetTime(); +} + + +/* This is called to clear up any resources the debug library uses - at the + moment we delete our critical section and the object list. The values we + retrieve from the registry are all done during initialisation but we don't + go looking for update notifications while we are running, if the values + are changed then the application has to be restarted to pick them up */ + +void WINAPI DbgTerminate() +{ + if (m_hOutput != INVALID_HANDLE_VALUE) { + EXECUTE_ASSERT(CloseHandle(m_hOutput)); + m_hOutput = INVALID_HANDLE_VALUE; + } + DeleteCriticalSection(&m_CSDebug); + m_bInit = FALSE; +} + + +/* This is called by DbgInitLogLevels to read the debug settings + for each logging category for this module from the registry */ + +void WINAPI DbgInitKeyLevels(HKEY hKey, bool fTakeMax) +{ + LONG lReturn; // Create key return value + LONG lKeyPos; // Current key category + DWORD dwKeySize; // Size of the key value + DWORD dwKeyType; // Receives it's type + DWORD dwKeyValue; // This fields value + + /* Try and read a value for each key position in turn */ + for (lKeyPos = 0;lKeyPos < iMAXLEVELS;lKeyPos++) { + + dwKeySize = sizeof(DWORD); + lReturn = RegQueryValueEx( + hKey, // Handle to an open key + pKeyNames[lKeyPos], // Subkey name derivation + NULL, // Reserved field + &dwKeyType, // Returns the field type + (LPBYTE) &dwKeyValue, // Returns the field's value + &dwKeySize ); // Number of bytes transferred + + /* If either the key was not available or it was not a DWORD value + then we ensure only the high priority debug logging is output + but we try and update the field to a zero filled DWORD value */ + + if (lReturn != ERROR_SUCCESS || dwKeyType != REG_DWORD) { + + dwKeyValue = 0; + lReturn = RegSetValueEx( + hKey, // Handle of an open key + pKeyNames[lKeyPos], // Address of subkey name + (DWORD) 0, // Reserved field + REG_DWORD, // Type of the key field + (PBYTE) &dwKeyValue, // Value for the field + sizeof(DWORD)); // Size of the field buffer + + if (lReturn != ERROR_SUCCESS) { + DbgLog((LOG_ERROR,1,TEXT("Could not create subkey %s"),pKeyNames[lKeyPos])); + dwKeyValue = 0; + } + } + if(fTakeMax) + { + m_Levels[lKeyPos] = max(dwKeyValue,m_Levels[lKeyPos]); + } + else + { + if((m_Levels[lKeyPos] & LOG_FORCIBLY_SET) == 0) { + m_Levels[lKeyPos] = dwKeyValue; + } + } + } + + /* Read the timeout value for catching hangs */ + dwKeySize = sizeof(DWORD); + lReturn = RegQueryValueEx( + hKey, // Handle to an open key + TimeoutName, // Subkey name derivation + NULL, // Reserved field + &dwKeyType, // Returns the field type + (LPBYTE) &dwWaitTimeout, // Returns the field's value + &dwKeySize ); // Number of bytes transferred + + /* If either the key was not available or it was not a DWORD value + then we ensure only the high priority debug logging is output + but we try and update the field to a zero filled DWORD value */ + + if (lReturn != ERROR_SUCCESS || dwKeyType != REG_DWORD) { + + dwWaitTimeout = INFINITE; + lReturn = RegSetValueEx( + hKey, // Handle of an open key + TimeoutName, // Address of subkey name + (DWORD) 0, // Reserved field + REG_DWORD, // Type of the key field + (PBYTE) &dwWaitTimeout, // Value for the field + sizeof(DWORD)); // Size of the field buffer + + if (lReturn != ERROR_SUCCESS) { + DbgLog((LOG_ERROR,1,TEXT("Could not create subkey %s"),pKeyNames[lKeyPos])); + dwWaitTimeout = INFINITE; + } + } +} + +void WINAPI DbgOutString(LPCTSTR psz) +{ + if (m_hOutput != INVALID_HANDLE_VALUE) { + UINT cb = lstrlen(psz); + DWORD dw; +#ifdef UNICODE + CHAR szDest[2048]; + WideCharToMultiByte(CP_ACP, 0, psz, -1, szDest, NUMELMS(szDest), 0, 0); + WriteFile (m_hOutput, szDest, cb, &dw, NULL); +#else + WriteFile (m_hOutput, psz, cb, &dw, NULL); +#endif + } else { + OutputDebugString (psz); + } +} + + + + +HRESULT DbgUniqueProcessName(LPCTSTR inName, LPTSTR outName) +{ + HRESULT hr = S_OK; + const TCHAR *pIn = inName; + int dotPos = -1; + + //scan the input and record the last '.' position + while (*pIn && (pIn - inName) < MAX_PATH) + { + if ( TEXT('.') == *pIn ) + dotPos = (int)(pIn-inName); + ++pIn; + } + + if (*pIn) //input should be zero-terminated within MAX_PATH + return E_INVALIDARG; + + DWORD dwProcessId = GetCurrentProcessId(); + + if (dotPos < 0) + { + //no extension in the input, appending process id to the input + hr = StringCchPrintf(outName, MAX_PATH, TEXT("%s_%d"), inName, dwProcessId); + } + else + { + TCHAR pathAndBasename[MAX_PATH] = {0}; + + //there's an extension - zero-terminate the path and basename first by copying + hr = StringCchCopyN(pathAndBasename, MAX_PATH, inName, (size_t)dotPos); + + //re-combine path, basename and extension with processId appended to a basename + if (SUCCEEDED(hr)) + hr = StringCchPrintf(outName, MAX_PATH, TEXT("%s_%d%s"), pathAndBasename, dwProcessId, inName + dotPos); + } + + return hr; +} + + +/* Called by DbgInitGlobalSettings to setup alternate logging destinations + */ + +void WINAPI DbgInitLogTo ( + HKEY hKey) +{ + LONG lReturn; + DWORD dwKeyType; + DWORD dwKeySize; + TCHAR szFile[MAX_PATH] = {0}; + static const TCHAR cszKey[] = TEXT("LogToFile"); + + dwKeySize = MAX_PATH; + lReturn = RegQueryValueEx( + hKey, // Handle to an open key + cszKey, // Subkey name derivation + NULL, // Reserved field + &dwKeyType, // Returns the field type + (LPBYTE) szFile, // Returns the field's value + &dwKeySize); // Number of bytes transferred + + // create an empty key if it does not already exist + // + if (lReturn != ERROR_SUCCESS || dwKeyType != REG_SZ) + { + dwKeySize = sizeof(TCHAR); + lReturn = RegSetValueEx( + hKey, // Handle of an open key + cszKey, // Address of subkey name + (DWORD) 0, // Reserved field + REG_SZ, // Type of the key field + (PBYTE)szFile, // Value for the field + dwKeySize); // Size of the field buffer + } + + // if an output-to was specified. try to open it. + // + if (m_hOutput != INVALID_HANDLE_VALUE) { + EXECUTE_ASSERT(CloseHandle (m_hOutput)); + m_hOutput = INVALID_HANDLE_VALUE; + } + if (szFile[0] != 0) + { + if (!lstrcmpi(szFile, TEXT("Console"))) { + m_hOutput = GetStdHandle (STD_OUTPUT_HANDLE); + if (m_hOutput == INVALID_HANDLE_VALUE) { + AllocConsole (); + m_hOutput = GetStdHandle (STD_OUTPUT_HANDLE); + } + SetConsoleTitle (TEXT("ActiveX Debug Output")); + } else if (szFile[0] && + lstrcmpi(szFile, TEXT("Debug")) && + lstrcmpi(szFile, TEXT("Debugger")) && + lstrcmpi(szFile, TEXT("Deb"))) + { + m_hOutput = CreateFile(szFile, GENERIC_WRITE, + FILE_SHARE_READ, + NULL, OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (INVALID_HANDLE_VALUE == m_hOutput && + GetLastError() == ERROR_SHARING_VIOLATION) + { + TCHAR uniqueName[MAX_PATH] = {0}; + if (SUCCEEDED(DbgUniqueProcessName(szFile, uniqueName))) + { + m_hOutput = CreateFile(uniqueName, GENERIC_WRITE, + FILE_SHARE_READ, + NULL, OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + } + } + + if (INVALID_HANDLE_VALUE != m_hOutput) + { + static const TCHAR cszBar[] = TEXT("\r\n\r\n=====DbgInitialize()=====\r\n\r\n"); + SetFilePointer (m_hOutput, 0, NULL, FILE_END); + DbgOutString (cszBar); + } + } + } +} + + + +/* This is called by DbgInitLogLevels to read the global debug settings for + each logging category for this module from the registry. Normally each + module has it's own values set for it's different debug categories but + setting the global SOFTWARE\Debug\Global applies them to ALL modules */ + +void WINAPI DbgInitGlobalSettings(bool fTakeMax) +{ + LONG lReturn; // Create key return value + TCHAR szInfo[iDEBUGINFO]; // Constructs key names + HKEY hGlobalKey; // Global override key + + /* Construct the global base key name */ + (void)StringCchPrintf(szInfo,NUMELMS(szInfo),TEXT("%s\\%s"),pBaseKey,pGlobalKey); + + /* Create or open the key for this module */ + lReturn = RegCreateKeyEx(HKEY_LOCAL_MACHINE, // Handle of an open key + szInfo, // Address of subkey name + (DWORD) 0, // Reserved value + NULL, // Address of class name + (DWORD) 0, // Special options flags + GENERIC_READ | GENERIC_WRITE, // Desired security access + NULL, // Key security descriptor + &hGlobalKey, // Opened handle buffer + NULL); // What really happened + + if (lReturn != ERROR_SUCCESS) { + lReturn = RegCreateKeyEx(HKEY_LOCAL_MACHINE, // Handle of an open key + szInfo, // Address of subkey name + (DWORD) 0, // Reserved value + NULL, // Address of class name + (DWORD) 0, // Special options flags + GENERIC_READ, // Desired security access + NULL, // Key security descriptor + &hGlobalKey, // Opened handle buffer + NULL); // What really happened + if (lReturn != ERROR_SUCCESS) { + DbgLog((LOG_ERROR,1,TEXT("Could not access GLOBAL module key"))); + } + return; + } + + DbgInitKeyLevels(hGlobalKey, fTakeMax); + RegCloseKey(hGlobalKey); +} + + +/* This sets the debugging log levels for the different categories. We start + by opening (or creating if not already available) the SOFTWARE\Debug key + that all these settings live under. We then look at the global values + set under SOFTWARE\Debug\Global which apply on top of the individual + module settings. We then load the individual module registry settings */ + +void WINAPI DbgInitModuleSettings(bool fTakeMax) +{ + LONG lReturn; // Create key return value + TCHAR szInfo[iDEBUGINFO]; // Constructs key names + HKEY hModuleKey; // Module key handle + + /* Construct the base key name */ + (void)StringCchPrintf(szInfo,NUMELMS(szInfo),TEXT("%s\\%s"),pBaseKey,m_ModuleName); + + /* Create or open the key for this module */ + lReturn = RegCreateKeyEx(HKEY_LOCAL_MACHINE, // Handle of an open key + szInfo, // Address of subkey name + (DWORD) 0, // Reserved value + NULL, // Address of class name + (DWORD) 0, // Special options flags + GENERIC_READ | GENERIC_WRITE, // Desired security access + NULL, // Key security descriptor + &hModuleKey, // Opened handle buffer + NULL); // What really happened + + if (lReturn != ERROR_SUCCESS) { + lReturn = RegCreateKeyEx(HKEY_LOCAL_MACHINE, // Handle of an open key + szInfo, // Address of subkey name + (DWORD) 0, // Reserved value + NULL, // Address of class name + (DWORD) 0, // Special options flags + GENERIC_READ, // Desired security access + NULL, // Key security descriptor + &hModuleKey, // Opened handle buffer + NULL); // What really happened + if (lReturn != ERROR_SUCCESS) { + DbgLog((LOG_ERROR,1,TEXT("Could not access module key"))); + } + return; + } + + DbgInitLogTo(hModuleKey); + DbgInitKeyLevels(hModuleKey, fTakeMax); + RegCloseKey(hModuleKey); +} + + +/* Initialise the module file name */ + +void WINAPI DbgInitModuleName() +{ + TCHAR FullName[iDEBUGINFO]; // Load the full path and module name + LPTSTR pName; // Searches from the end for a backslash + + GetModuleFileName(m_hInst,FullName,iDEBUGINFO); + pName = _tcsrchr(FullName,'\\'); + if (pName == NULL) { + pName = FullName; + } else { + pName++; + } + (void)StringCchCopy(m_ModuleName,NUMELMS(m_ModuleName), pName); +} + +struct MsgBoxMsg +{ + HWND hwnd; + LPCTSTR szTitle; + LPCTSTR szMessage; + DWORD dwFlags; + INT iResult; +}; + +// +// create a thread to call MessageBox(). calling MessageBox() on +// random threads at bad times can confuse the host (eg IE). +// +DWORD WINAPI MsgBoxThread( + __inout LPVOID lpParameter // thread data + ) +{ + MsgBoxMsg *pmsg = (MsgBoxMsg *)lpParameter; + pmsg->iResult = MessageBox( + pmsg->hwnd, + pmsg->szTitle, + pmsg->szMessage, + pmsg->dwFlags); + + return 0; +} + +INT MessageBoxOtherThread( + HWND hwnd, + LPCTSTR szTitle, + LPCTSTR szMessage, + DWORD dwFlags) +{ + if(g_fDbgInDllEntryPoint) + { + // can't wait on another thread because we have the loader + // lock held in the dll entry point. + // This can crash sometimes so just skip it + // return MessageBox(hwnd, szTitle, szMessage, dwFlags); + return IDCANCEL; + } + else + { + MsgBoxMsg msg = {hwnd, szTitle, szMessage, dwFlags, 0}; + DWORD dwid; + HANDLE hThread = CreateThread( + 0, // security + 0, // stack size + MsgBoxThread, + (void *)&msg, // arg + 0, // flags + &dwid); + if(hThread) + { + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + return msg.iResult; + } + + // break into debugger on failure. + return IDCANCEL; + } +} + +/* Displays a message box if the condition evaluated to FALSE */ + +void WINAPI DbgAssert(LPCTSTR pCondition,LPCTSTR pFileName,INT iLine) +{ + if(g_fUseKASSERT) + { + DbgKernelAssert(pCondition, pFileName, iLine); + } + else + { + + TCHAR szInfo[iDEBUGINFO]; + + (void)StringCchPrintf(szInfo, NUMELMS(szInfo),TEXT("%s \nAt line %d of %s\nContinue? (Cancel to debug)"), + pCondition, iLine, pFileName); + + INT MsgId = MessageBoxOtherThread(NULL,szInfo,TEXT("ASSERT Failed"), + MB_SYSTEMMODAL | + MB_ICONHAND | + MB_YESNOCANCEL | + MB_SETFOREGROUND); + switch (MsgId) + { + case IDNO: /* Kill the application */ + + FatalAppExit(FALSE, TEXT("Application terminated")); + break; + + case IDCANCEL: /* Break into the debugger */ + + DebugBreak(); + break; + + case IDYES: /* Ignore assertion continue execution */ + break; + } + } +} + +/* Displays a message box at a break point */ + +void WINAPI DbgBreakPoint(LPCTSTR pCondition,LPCTSTR pFileName,INT iLine) +{ + if(g_fUseKASSERT) + { + DbgKernelAssert(pCondition, pFileName, iLine); + } + else + { + TCHAR szInfo[iDEBUGINFO]; + + (void)StringCchPrintf(szInfo, NUMELMS(szInfo),TEXT("%s \nAt line %d of %s\nContinue? (Cancel to debug)"), + pCondition, iLine, pFileName); + + INT MsgId = MessageBoxOtherThread(NULL,szInfo,TEXT("Hard coded break point"), + MB_SYSTEMMODAL | + MB_ICONHAND | + MB_YESNOCANCEL | + MB_SETFOREGROUND); + switch (MsgId) + { + case IDNO: /* Kill the application */ + + FatalAppExit(FALSE, TEXT("Application terminated")); + break; + + case IDCANCEL: /* Break into the debugger */ + + DebugBreak(); + break; + + case IDYES: /* Ignore break point continue execution */ + break; + } + } +} + +void WINAPI DbgBreakPoint(LPCTSTR pFileName,INT iLine,__format_string LPCTSTR szFormatString,...) +{ + // A debug break point message can have at most 2000 characters if + // ANSI or UNICODE characters are being used. A debug break point message + // can have between 1000 and 2000 double byte characters in it. If a + // particular message needs more characters, then the value of this constant + // should be increased. + const DWORD MAX_BREAK_POINT_MESSAGE_SIZE = 2000; + + TCHAR szBreakPointMessage[MAX_BREAK_POINT_MESSAGE_SIZE]; + + va_list va; + va_start( va, szFormatString ); + + HRESULT hr = StringCchVPrintf( szBreakPointMessage, NUMELMS(szBreakPointMessage), szFormatString, va ); + + va_end(va); + + if( FAILED(hr) ) { + DbgBreak( "ERROR in DbgBreakPoint(). The variable length debug message could not be displayed because StringCchVPrintf() failed." ); + return; + } + + ::DbgBreakPoint( szBreakPointMessage, pFileName, iLine ); +} + + +/* When we initialised the library we stored in the m_Levels array the current + debug output level for this module for each of the five categories. When + some debug logging is sent to us it can be sent with a combination of the + categories (if it is applicable to many for example) in which case we map + the type's categories into their current debug levels and see if any of + them can be accepted. The function looks at each bit position in turn from + the input type field and then compares it's debug level with the modules. + + A level of 0 means that output is always sent to the debugger. This is + due to producing output if the input level is <= m_Levels. +*/ + + +BOOL WINAPI DbgCheckModuleLevel(DWORD Type,DWORD Level) +{ + if(g_fAutoRefreshLevels) + { + // re-read the registry every second. We cannot use RegNotify() to + // notice registry changes because it's not available on win9x. + static DWORD g_dwLastRefresh = 0; + DWORD dwTime = timeGetTime(); + if(dwTime - g_dwLastRefresh > 1000) { + g_dwLastRefresh = dwTime; + + // there's a race condition: multiple threads could update the + // values. plus read and write not synchronized. no harm + // though. + DbgInitModuleSettings(false); + } + } + + + DWORD Mask = 0x01; + + // If no valid bits are set return FALSE + if ((Type & ((1<<iMAXLEVELS)-1))) { + + // speed up unconditional output. + if (0==Level) + return(TRUE); + + for (LONG lKeyPos = 0;lKeyPos < iMAXLEVELS;lKeyPos++) { + if (Type & Mask) { + if (Level <= (m_Levels[lKeyPos] & ~LOG_FORCIBLY_SET)) { + return TRUE; + } + } + Mask <<= 1; + } + } + return FALSE; +} + + +/* Set debug levels to a given value */ + +void WINAPI DbgSetModuleLevel(DWORD Type, DWORD Level) +{ + DWORD Mask = 0x01; + + for (LONG lKeyPos = 0;lKeyPos < iMAXLEVELS;lKeyPos++) { + if (Type & Mask) { + m_Levels[lKeyPos] = Level | LOG_FORCIBLY_SET; + } + Mask <<= 1; + } +} + +/* whether to check registry values periodically. this isn't turned + automatically because of the potential performance hit. */ +void WINAPI DbgSetAutoRefreshLevels(bool fAuto) +{ + g_fAutoRefreshLevels = fAuto; +} + +#ifdef UNICODE +// +// warning -- this function is implemented twice for ansi applications +// linking to the unicode library +// +void WINAPI DbgLogInfo(DWORD Type,DWORD Level,__format_string LPCSTR pFormat,...) +{ + /* Check the current level for this type combination */ + + BOOL bAccept = DbgCheckModuleLevel(Type,Level); + if (bAccept == FALSE) { + return; + } + + TCHAR szInfo[2000]; + + /* Format the variable length parameter list */ + + va_list va; + va_start(va, pFormat); + + (void)StringCchPrintf(szInfo, NUMELMS(szInfo), + TEXT("%s(tid %x) %8d : "), + m_ModuleName, + GetCurrentThreadId(), timeGetTime() - dwTimeOffset); + + CHAR szInfoA[2000]; + WideCharToMultiByte(CP_ACP, 0, szInfo, -1, szInfoA, NUMELMS(szInfoA), 0, 0); + + (void)StringCchVPrintfA(szInfoA + lstrlenA(szInfoA), NUMELMS(szInfoA) - lstrlenA(szInfoA), pFormat, va); + (void)StringCchCatA(szInfoA, NUMELMS(szInfoA), "\r\n"); + + WCHAR wszOutString[2000]; + MultiByteToWideChar(CP_ACP, 0, szInfoA, -1, wszOutString, NUMELMS(wszOutString)); + DbgOutString(wszOutString); + + va_end(va); +} + +void WINAPI DbgAssert(LPCSTR pCondition,LPCSTR pFileName,INT iLine) +{ + if(g_fUseKASSERT) + { + DbgKernelAssert(pCondition, pFileName, iLine); + } + else + { + + TCHAR szInfo[iDEBUGINFO]; + + (void)StringCchPrintf(szInfo, NUMELMS(szInfo), TEXT("%hs \nAt line %d of %hs\nContinue? (Cancel to debug)"), + pCondition, iLine, pFileName); + + INT MsgId = MessageBoxOtherThread(NULL,szInfo,TEXT("ASSERT Failed"), + MB_SYSTEMMODAL | + MB_ICONHAND | + MB_YESNOCANCEL | + MB_SETFOREGROUND); + switch (MsgId) + { + case IDNO: /* Kill the application */ + + FatalAppExit(FALSE, TEXT("Application terminated")); + break; + + case IDCANCEL: /* Break into the debugger */ + + DebugBreak(); + break; + + case IDYES: /* Ignore assertion continue execution */ + break; + } + } +} + +/* Displays a message box at a break point */ + +void WINAPI DbgBreakPoint(LPCSTR pCondition,LPCSTR pFileName,INT iLine) +{ + if(g_fUseKASSERT) + { + DbgKernelAssert(pCondition, pFileName, iLine); + } + else + { + TCHAR szInfo[iDEBUGINFO]; + + (void)StringCchPrintf(szInfo, NUMELMS(szInfo),TEXT("%hs \nAt line %d of %hs\nContinue? (Cancel to debug)"), + pCondition, iLine, pFileName); + + INT MsgId = MessageBoxOtherThread(NULL,szInfo,TEXT("Hard coded break point"), + MB_SYSTEMMODAL | + MB_ICONHAND | + MB_YESNOCANCEL | + MB_SETFOREGROUND); + switch (MsgId) + { + case IDNO: /* Kill the application */ + + FatalAppExit(FALSE, TEXT("Application terminated")); + break; + + case IDCANCEL: /* Break into the debugger */ + + DebugBreak(); + break; + + case IDYES: /* Ignore break point continue execution */ + break; + } + } +} + +void WINAPI DbgKernelAssert(LPCSTR pCondition,LPCSTR pFileName,INT iLine) +{ + DbgLog((LOG_ERROR,0,TEXT("Assertion FAILED (%hs) at line %d in file %hs"), + pCondition, iLine, pFileName)); + DebugBreak(); +} + +#endif + +/* Print a formatted string to the debugger prefixed with this module's name + Because the COMBASE classes are linked statically every module loaded will + have their own copy of this code. It therefore helps if the module name is + included on the output so that the offending code can be easily found */ + +// +// warning -- this function is implemented twice for ansi applications +// linking to the unicode library +// +void WINAPI DbgLogInfo(DWORD Type,DWORD Level,LPCTSTR pFormat,...) +{ + + /* Check the current level for this type combination */ + + BOOL bAccept = DbgCheckModuleLevel(Type,Level); + if (bAccept == FALSE) { + return; + } + + TCHAR szInfo[2000]; + + /* Format the variable length parameter list */ + + va_list va; + va_start(va, pFormat); + + (void)StringCchPrintf(szInfo, NUMELMS(szInfo), + TEXT("%s(tid %x) %8d : "), + m_ModuleName, + GetCurrentThreadId(), timeGetTime() - dwTimeOffset); + + (void)StringCchVPrintf(szInfo + lstrlen(szInfo), NUMELMS(szInfo) - lstrlen(szInfo), pFormat, va); + (void)StringCchCat(szInfo, NUMELMS(szInfo), TEXT("\r\n")); + DbgOutString(szInfo); + + va_end(va); +} + + +/* If we are executing as a pure kernel filter we cannot display message + boxes to the user, this provides an alternative which puts the error + condition on the debugger output with a suitable eye catching message */ + +void WINAPI DbgKernelAssert(LPCTSTR pCondition,LPCTSTR pFileName,INT iLine) +{ + DbgLog((LOG_ERROR,0,TEXT("Assertion FAILED (%s) at line %d in file %s"), + pCondition, iLine, pFileName)); + DebugBreak(); +} + + + +/* Each time we create an object derived from CBaseObject the constructor will + call us to register the creation of the new object. We are passed a string + description which we store away. We return a cookie that the constructor + uses to identify the object when it is destroyed later on. We update the + total number of active objects in the DLL mainly for debugging purposes */ + +DWORD WINAPI DbgRegisterObjectCreation(LPCSTR szObjectName, + LPCWSTR wszObjectName) +{ + /* If this fires you have a mixed DEBUG/RETAIL build */ + + ASSERT(!!szObjectName ^ !!wszObjectName); + + /* Create a place holder for this object description */ + + ObjectDesc *pObject = new ObjectDesc; + ASSERT(pObject); + + /* It is valid to pass a NULL object name */ + if (pObject == NULL) { + return FALSE; + } + + /* Check we have been initialised - we may not be initialised when we are + being pulled in from an executable which has globally defined objects + as they are created by the C++ run time before WinMain is called */ + + if (m_bInit == FALSE) { + DbgInitialise(GetModuleHandle(NULL)); + } + + /* Grab the list critical section */ + EnterCriticalSection(&m_CSDebug); + + /* If no name then default to UNKNOWN */ + if (!szObjectName && !wszObjectName) { + szObjectName = pUnknownName; + } + + /* Put the new description at the _head of the list */ + + pObject->m_szName = szObjectName; + pObject->m_wszName = wszObjectName; + pObject->m_dwCookie = ++m_dwNextCookie; + pObject->m_pNext = pListHead; + + pListHead = pObject; + m_dwObjectCount++; + + DWORD ObjectCookie = pObject->m_dwCookie; + ASSERT(ObjectCookie); + + if(wszObjectName) { + DbgLog((LOG_MEMORY,2,TEXT("Object created %d (%ls) %d Active"), + pObject->m_dwCookie, wszObjectName, m_dwObjectCount)); + } else { + DbgLog((LOG_MEMORY,2,TEXT("Object created %d (%hs) %d Active"), + pObject->m_dwCookie, szObjectName, m_dwObjectCount)); + } + + LeaveCriticalSection(&m_CSDebug); + return ObjectCookie; +} + + +/* This is called by the CBaseObject destructor when an object is about to be + destroyed, we are passed the cookie we returned during construction that + identifies this object. We scan the object list for a matching cookie and + remove the object if successful. We also update the active object count */ + +BOOL WINAPI DbgRegisterObjectDestruction(DWORD dwCookie) +{ + /* Grab the list critical section */ + EnterCriticalSection(&m_CSDebug); + + ObjectDesc *pObject = pListHead; + ObjectDesc *pPrevious = NULL; + + /* Scan the object list looking for a cookie match */ + + while (pObject) { + if (pObject->m_dwCookie == dwCookie) { + break; + } + pPrevious = pObject; + pObject = pObject->m_pNext; + } + + if (pObject == NULL) { + DbgBreak("Apparently destroying a bogus object"); + LeaveCriticalSection(&m_CSDebug); + return FALSE; + } + + /* Is the object at the _head of the list */ + + if (pPrevious == NULL) { + pListHead = pObject->m_pNext; + } else { + pPrevious->m_pNext = pObject->m_pNext; + } + + /* Delete the object and update the housekeeping information */ + + m_dwObjectCount--; + + if(pObject->m_wszName) { + DbgLog((LOG_MEMORY,2,TEXT("Object destroyed %d (%ls) %d Active"), + pObject->m_dwCookie, pObject->m_wszName, m_dwObjectCount)); + } else { + DbgLog((LOG_MEMORY,2,TEXT("Object destroyed %d (%hs) %d Active"), + pObject->m_dwCookie, pObject->m_szName, m_dwObjectCount)); + } + + delete pObject; + LeaveCriticalSection(&m_CSDebug); + return TRUE; +} + + +/* This runs through the active object list displaying their details */ + +void WINAPI DbgDumpObjectRegister() +{ + TCHAR szInfo[iDEBUGINFO]; + + /* Grab the list critical section */ + + EnterCriticalSection(&m_CSDebug); + ObjectDesc *pObject = pListHead; + + /* Scan the object list displaying the name and cookie */ + + DbgLog((LOG_MEMORY,2,TEXT(""))); + DbgLog((LOG_MEMORY,2,TEXT(" ID Object Description"))); + DbgLog((LOG_MEMORY,2,TEXT(""))); + + while (pObject) { + if(pObject->m_wszName) { + (void)StringCchPrintf(szInfo,NUMELMS(szInfo),TEXT("%5d (%p) %30ls"),pObject->m_dwCookie, &pObject, pObject->m_wszName); + } else { + (void)StringCchPrintf(szInfo,NUMELMS(szInfo),TEXT("%5d (%p) %30hs"),pObject->m_dwCookie, &pObject, pObject->m_szName); + } + DbgLog((LOG_MEMORY,2,szInfo)); + pObject = pObject->m_pNext; + } + + (void)StringCchPrintf(szInfo,NUMELMS(szInfo),TEXT("Total object count %5d"),m_dwObjectCount); + DbgLog((LOG_MEMORY,2,TEXT(""))); + DbgLog((LOG_MEMORY,1,szInfo)); + LeaveCriticalSection(&m_CSDebug); +} + +/* Debug infinite wait stuff */ +DWORD WINAPI DbgWaitForSingleObject(HANDLE h) +{ + DWORD dwWaitResult; + do { + dwWaitResult = WaitForSingleObject(h, dwWaitTimeout); + ASSERT(dwWaitResult == WAIT_OBJECT_0); + } while (dwWaitResult == WAIT_TIMEOUT); + return dwWaitResult; +} +DWORD WINAPI DbgWaitForMultipleObjects(DWORD nCount, + __in_ecount(nCount) CONST HANDLE *lpHandles, + BOOL bWaitAll) +{ + DWORD dwWaitResult; + do { + dwWaitResult = WaitForMultipleObjects(nCount, + lpHandles, + bWaitAll, + dwWaitTimeout); + ASSERT((DWORD)(dwWaitResult - WAIT_OBJECT_0) < MAXIMUM_WAIT_OBJECTS); + } while (dwWaitResult == WAIT_TIMEOUT); + return dwWaitResult; +} + +void WINAPI DbgSetWaitTimeout(DWORD dwTimeout) +{ + dwWaitTimeout = dwTimeout; +} + +#endif /* DEBUG */ + +#ifdef _OBJBASE_H_ + + /* Stuff for printing out our GUID names */ + + GUID_STRING_ENTRY g_GuidNames[] = { + #define OUR_GUID_ENTRY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + { #name, { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } }, + #include <uuids.h> + }; + + CGuidNameList GuidNames; + int g_cGuidNames = sizeof(g_GuidNames) / sizeof(g_GuidNames[0]); + + char *CGuidNameList::operator [] (const GUID &guid) + { + for (int i = 0; i < g_cGuidNames; i++) { + if (g_GuidNames[i].guid == guid) { + return g_GuidNames[i].szName; + } + } + if (guid == GUID_NULL) { + return "GUID_NULL"; + } + + // !!! add something to print FOURCC guids? + + // shouldn't this print the hex CLSID? + return "Unknown GUID Name"; + } + +#endif /* _OBJBASE_H_ */ + +/* CDisp class - display our data types */ + +// clashes with REFERENCE_TIME +CDisp::CDisp(LONGLONG ll, int Format) +{ + // note: this could be combined with CDisp(LONGLONG) by + // introducing a default format of CDISP_REFTIME + LARGE_INTEGER li; + li.QuadPart = ll; + switch (Format) { + case CDISP_DEC: + { + TCHAR temp[20]; + int pos=20; + temp[--pos] = 0; + int digit; + // always output at least one digit + do { + // Get the rightmost digit - we only need the low word + digit = li.LowPart % 10; + li.QuadPart /= 10; + temp[--pos] = (TCHAR) digit+L'0'; + } while (li.QuadPart); + (void)StringCchCopy(m_String, NUMELMS(m_String), temp+pos); + break; + } + case CDISP_HEX: + default: + (void)StringCchPrintf(m_String, NUMELMS(m_String), TEXT("0x%X%8.8X"), li.HighPart, li.LowPart); + } +}; + +CDisp::CDisp(REFCLSID clsid) +{ +#ifdef UNICODE + (void)StringFromGUID2(clsid, m_String, NUMELMS(m_String)); +#else + WCHAR wszTemp[50]; + (void)StringFromGUID2(clsid, wszTemp, NUMELMS(wszTemp)); + (void)StringCchPrintf(m_String, NUMELMS(m_String), TEXT("%S"), wszTemp); +#endif +}; + +#ifdef __STREAMS__ +/* Display stuff */ +CDisp::CDisp(CRefTime llTime) +{ + LONGLONG llDiv; + if (llTime < 0) { + llTime = -llTime; + (void)StringCchCopy(m_String, NUMELMS(m_String), TEXT("-")); + } + llDiv = (LONGLONG)24 * 3600 * 10000000; + if (llTime >= llDiv) { + (void)StringCchPrintf(m_String + lstrlen(m_String), NUMELMS(m_String) - lstrlen(m_String), TEXT("%d days "), (LONG)(llTime / llDiv)); + llTime = llTime % llDiv; + } + llDiv = (LONGLONG)3600 * 10000000; + if (llTime >= llDiv) { + (void)StringCchPrintf(m_String + lstrlen(m_String), NUMELMS(m_String) - lstrlen(m_String), TEXT("%d hrs "), (LONG)(llTime / llDiv)); + llTime = llTime % llDiv; + } + llDiv = (LONGLONG)60 * 10000000; + if (llTime >= llDiv) { + (void)StringCchPrintf(m_String + lstrlen(m_String), NUMELMS(m_String) - lstrlen(m_String), TEXT("%d mins "), (LONG)(llTime / llDiv)); + llTime = llTime % llDiv; + } + (void)StringCchPrintf(m_String + lstrlen(m_String), NUMELMS(m_String) - lstrlen(m_String), TEXT("%d.%3.3d sec"), + (LONG)llTime / 10000000, + (LONG)((llTime % 10000000) / 10000)); +}; + +#endif // __STREAMS__ + + +/* Display pin */ +CDisp::CDisp(IPin *pPin) +{ + PIN_INFO pi; + TCHAR str[MAX_PIN_NAME]; + CLSID clsid; + + if (pPin) { + pPin->QueryPinInfo(&pi); + pi.pFilter->GetClassID(&clsid); + QueryPinInfoReleaseFilter(pi); + #ifndef UNICODE + WideCharToMultiByte(GetACP(), 0, pi.achName, lstrlenW(pi.achName) + 1, + str, MAX_PIN_NAME, NULL, NULL); + #else + (void)StringCchCopy(str, NUMELMS(str), pi.achName); + #endif + } else { + (void)StringCchCopy(str, NUMELMS(str), TEXT("NULL IPin")); + } + + m_pString = (PTCHAR) new TCHAR[lstrlen(str)+64]; + if (!m_pString) { + return; + } + + (void)StringCchPrintf(m_pString, lstrlen(str) + 64, TEXT("%hs(%s)"), GuidNames[clsid], str); +} + +/* Display filter or pin */ +CDisp::CDisp(IUnknown *pUnk) +{ + IBaseFilter *pf; + HRESULT hr = pUnk->QueryInterface(IID_IBaseFilter, (void **)&pf); + if(SUCCEEDED(hr)) + { + FILTER_INFO fi; + hr = pf->QueryFilterInfo(&fi); + if(SUCCEEDED(hr)) + { + QueryFilterInfoReleaseGraph(fi); + + size_t len = lstrlenW(fi.achName) + 1; + + m_pString = new TCHAR[len]; + if(m_pString) + { +#ifdef UNICODE + (void)StringCchCopy(m_pString, len, fi.achName); +#else + (void)StringCchPrintf(m_pString, len, "%S", fi.achName); +#endif + } + } + + pf->Release(); + + return; + } + + IPin *pp; + hr = pUnk->QueryInterface(IID_IPin, (void **)&pp); + if(SUCCEEDED(hr)) + { + CDisp::CDisp(pp); + pp->Release(); + return; + } +} + + +CDisp::~CDisp() +{ +} + +CDispBasic::~CDispBasic() +{ + if (m_pString != m_String) { + delete [] m_pString; + } +} + +CDisp::CDisp(double d) +{ + (void)StringCchPrintf(m_String, NUMELMS(m_String), TEXT("%d.%03d"), (int) d, (int) ((d - (int) d) * 1000)); +} + + +/* If built for debug this will display the media type details. We convert the + major and subtypes into strings and also ask the base classes for a string + description of the subtype, so MEDIASUBTYPE_RGB565 becomes RGB 565 16 bit + We also display the fields in the BITMAPINFOHEADER structure, this should + succeed as we do not accept input types unless the format is big enough */ + +#ifdef DEBUG +void WINAPI DisplayType(LPCTSTR label, const AM_MEDIA_TYPE *pmtIn) +{ + + /* Dump the GUID types and a short description */ + + DbgLog((LOG_TRACE,5,TEXT(""))); + DbgLog((LOG_TRACE,2,TEXT("%s M type %hs S type %hs"), label, + GuidNames[pmtIn->majortype], + GuidNames[pmtIn->subtype])); + DbgLog((LOG_TRACE,5,TEXT("Subtype description %s"),GetSubtypeName(&pmtIn->subtype))); + + /* Dump the generic media types */ + + if (pmtIn->bTemporalCompression) { + DbgLog((LOG_TRACE,5,TEXT("Temporally compressed"))); + } else { + DbgLog((LOG_TRACE,5,TEXT("Not temporally compressed"))); + } + + if (pmtIn->bFixedSizeSamples) { + DbgLog((LOG_TRACE,5,TEXT("Sample size %d"),pmtIn->lSampleSize)); + } else { + DbgLog((LOG_TRACE,5,TEXT("Variable size samples"))); + } + + if (pmtIn->formattype == FORMAT_VideoInfo) { + + VIDEOINFOHEADER *pVideoInfo = (VIDEOINFOHEADER *)pmtIn->pbFormat; + + DisplayRECT(TEXT("Source rectangle"),pVideoInfo->rcSource); + DisplayRECT(TEXT("Target rectangle"),pVideoInfo->rcTarget); + DisplayBITMAPINFO(HEADER(pmtIn->pbFormat)); + + } if (pmtIn->formattype == FORMAT_VideoInfo2) { + + VIDEOINFOHEADER2 *pVideoInfo2 = (VIDEOINFOHEADER2 *)pmtIn->pbFormat; + + DisplayRECT(TEXT("Source rectangle"),pVideoInfo2->rcSource); + DisplayRECT(TEXT("Target rectangle"),pVideoInfo2->rcTarget); + DbgLog((LOG_TRACE, 5, TEXT("Aspect Ratio: %d:%d"), + pVideoInfo2->dwPictAspectRatioX, + pVideoInfo2->dwPictAspectRatioY)); + DisplayBITMAPINFO(&pVideoInfo2->bmiHeader); + + } else if (pmtIn->majortype == MEDIATYPE_Audio) { + DbgLog((LOG_TRACE,2,TEXT(" Format type %hs"), + GuidNames[pmtIn->formattype])); + DbgLog((LOG_TRACE,2,TEXT(" Subtype %hs"), + GuidNames[pmtIn->subtype])); + + if ((pmtIn->subtype != MEDIASUBTYPE_MPEG1Packet) + && (pmtIn->cbFormat >= sizeof(PCMWAVEFORMAT))) + { + /* Dump the contents of the WAVEFORMATEX type-specific format structure */ + + WAVEFORMATEX *pwfx = (WAVEFORMATEX *) pmtIn->pbFormat; + DbgLog((LOG_TRACE,2,TEXT("wFormatTag %u"), pwfx->wFormatTag)); + DbgLog((LOG_TRACE,2,TEXT("nChannels %u"), pwfx->nChannels)); + DbgLog((LOG_TRACE,2,TEXT("nSamplesPerSec %lu"), pwfx->nSamplesPerSec)); + DbgLog((LOG_TRACE,2,TEXT("nAvgBytesPerSec %lu"), pwfx->nAvgBytesPerSec)); + DbgLog((LOG_TRACE,2,TEXT("nBlockAlign %u"), pwfx->nBlockAlign)); + DbgLog((LOG_TRACE,2,TEXT("wBitsPerSample %u"), pwfx->wBitsPerSample)); + + /* PCM uses a WAVEFORMAT and does not have the extra size field */ + + if (pmtIn->cbFormat >= sizeof(WAVEFORMATEX)) { + DbgLog((LOG_TRACE,2,TEXT("cbSize %u"), pwfx->cbSize)); + } + } else { + } + + } else { + DbgLog((LOG_TRACE,2,TEXT(" Format type %hs"), + GuidNames[pmtIn->formattype])); + } +} + + +void DisplayBITMAPINFO(const BITMAPINFOHEADER* pbmi) +{ + DbgLog((LOG_TRACE,5,TEXT("Size of BITMAPINFO structure %d"),pbmi->biSize)); + if (pbmi->biCompression < 256) { + DbgLog((LOG_TRACE,2,TEXT("%dx%dx%d bit (%d)"), + pbmi->biWidth, pbmi->biHeight, + pbmi->biBitCount, pbmi->biCompression)); + } else { + DbgLog((LOG_TRACE,2,TEXT("%dx%dx%d bit '%4.4hs'"), + pbmi->biWidth, pbmi->biHeight, + pbmi->biBitCount, &pbmi->biCompression)); + } + + DbgLog((LOG_TRACE,2,TEXT("Image size %d"),pbmi->biSizeImage)); + DbgLog((LOG_TRACE,5,TEXT("Planes %d"),pbmi->biPlanes)); + DbgLog((LOG_TRACE,5,TEXT("X Pels per metre %d"),pbmi->biXPelsPerMeter)); + DbgLog((LOG_TRACE,5,TEXT("Y Pels per metre %d"),pbmi->biYPelsPerMeter)); + DbgLog((LOG_TRACE,5,TEXT("Colours used %d"),pbmi->biClrUsed)); +} + + +void DisplayRECT(LPCTSTR szLabel, const RECT& rc) +{ + DbgLog((LOG_TRACE,5,TEXT("%s (Left %d Top %d Right %d Bottom %d)"), + szLabel, + rc.left, + rc.top, + rc.right, + rc.bottom)); +} + + +void WINAPI DumpGraph(IFilterGraph *pGraph, DWORD dwLevel) +{ + if( !pGraph ) + { + return; + } + + IEnumFilters *pFilters; + + DbgLog((LOG_TRACE,dwLevel,TEXT("DumpGraph [%x]"), pGraph)); + + if (FAILED(pGraph->EnumFilters(&pFilters))) { + DbgLog((LOG_TRACE,dwLevel,TEXT("EnumFilters failed!"))); + } + + IBaseFilter *pFilter; + ULONG n; + while (pFilters->Next(1, &pFilter, &n) == S_OK) { + FILTER_INFO info; + + if (FAILED(pFilter->QueryFilterInfo(&info))) { + DbgLog((LOG_TRACE,dwLevel,TEXT(" Filter [%p] -- failed QueryFilterInfo"), pFilter)); + } else { + QueryFilterInfoReleaseGraph(info); + + // !!! should QueryVendorInfo here! + + DbgLog((LOG_TRACE,dwLevel,TEXT(" Filter [%p] '%ls'"), pFilter, info.achName)); + + IEnumPins *pins; + + if (FAILED(pFilter->EnumPins(&pins))) { + DbgLog((LOG_TRACE,dwLevel,TEXT("EnumPins failed!"))); + } else { + + IPin *pPin; + while (pins->Next(1, &pPin, &n) == S_OK) { + PIN_INFO pinInfo; + + if (FAILED(pPin->QueryPinInfo(&pinInfo))) { + DbgLog((LOG_TRACE,dwLevel,TEXT(" Pin [%x] -- failed QueryPinInfo"), pPin)); + } else { + QueryPinInfoReleaseFilter(pinInfo); + + IPin *pPinConnected = NULL; + + HRESULT hr = pPin->ConnectedTo(&pPinConnected); + + if (pPinConnected) { + DbgLog((LOG_TRACE,dwLevel,TEXT(" Pin [%p] '%ls' [%sput]") + TEXT(" Connected to pin [%p]"), + pPin, pinInfo.achName, + pinInfo.dir == PINDIR_INPUT ? TEXT("In") : TEXT("Out"), + pPinConnected)); + + pPinConnected->Release(); + + // perhaps we should really dump the type both ways as a sanity + // check? + if (pinInfo.dir == PINDIR_OUTPUT) { + AM_MEDIA_TYPE mt; + + hr = pPin->ConnectionMediaType(&mt); + + if (SUCCEEDED(hr)) { + DisplayType(TEXT("Connection type"), &mt); + + FreeMediaType(mt); + } + } + } else { + DbgLog((LOG_TRACE,dwLevel, + TEXT(" Pin [%x] '%ls' [%sput]"), + pPin, pinInfo.achName, + pinInfo.dir == PINDIR_INPUT ? TEXT("In") : TEXT("Out"))); + + } + } + + pPin->Release(); + + } + + pins->Release(); + } + + } + + pFilter->Release(); + } + + pFilters->Release(); + +} + +#endif + diff --git a/Src/Plugins/Input/in_dshow/base/wxdebug.h b/Src/Plugins/Input/in_dshow/base/wxdebug.h new file mode 100644 index 00000000..62efffbe --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/wxdebug.h @@ -0,0 +1,359 @@ +//------------------------------------------------------------------------------ +// File: WXDebug.h +// +// Desc: DirectShow base classes - provides debugging facilities. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __WXDEBUG__ +#define __WXDEBUG__ + +// This library provides fairly straight forward debugging functionality, this +// is split into two main sections. The first is assertion handling, there are +// three types of assertions provided here. The most commonly used one is the +// ASSERT(condition) macro which will pop up a message box including the file +// and line number if the condition evaluates to FALSE. Then there is the +// EXECUTE_ASSERT macro which is the same as ASSERT except the condition will +// still be executed in NON debug builds. The final type of assertion is the +// KASSERT macro which is more suitable for pure (perhaps kernel) filters as +// the condition is printed onto the debugger rather than in a message box. +// +// The other part of the debug module facilties is general purpose logging. +// This is accessed by calling DbgLog(). The function takes a type and level +// field which define the type of informational string you are presenting and +// it's relative importance. The type field can be a combination (one or more) +// of LOG_TIMING, LOG_TRACE, LOG_MEMORY, LOG_LOCKING and LOG_ERROR. The level +// is a DWORD value where zero defines highest important. Use of zero as the +// debug logging level is to be encouraged ONLY for major errors or events as +// they will ALWAYS be displayed on the debugger. Other debug output has it's +// level matched against the current debug output level stored in the registry +// for this module and if less than the current setting it will be displayed. +// +// Each module or executable has it's own debug output level for each of the +// five types. These are read in when the DbgInitialise function is called +// for DLLs linking to STRMBASE.LIB this is done automatically when the DLL +// is loaded, executables must call it explicitely with the module instance +// handle given to them through the WINMAIN entry point. An executable must +// also call DbgTerminate when they have finished to clean up the resources +// the debug library uses, once again this is done automatically for DLLs + +// These are the five different categories of logging information + +enum { LOG_TIMING = 0x01, // Timing and performance measurements + LOG_TRACE = 0x02, // General step point call tracing + LOG_MEMORY = 0x04, // Memory and object allocation/destruction + LOG_LOCKING = 0x08, // Locking/unlocking of critical sections + LOG_ERROR = 0x10, // Debug error notification + LOG_CUSTOM1 = 0x20, + LOG_CUSTOM2 = 0x40, + LOG_CUSTOM3 = 0x80, + LOG_CUSTOM4 = 0x100, + LOG_CUSTOM5 = 0x200, +}; + +#define LOG_FORCIBLY_SET 0x80000000 + +enum { CDISP_HEX = 0x01, + CDISP_DEC = 0x02}; + +// For each object created derived from CBaseObject (in debug builds) we +// create a descriptor that holds it's name (statically allocated memory) +// and a cookie we assign it. We keep a list of all the active objects +// we have registered so that we can dump a list of remaining objects + +typedef struct tag_ObjectDesc { + LPCSTR m_szName; + LPCWSTR m_wszName; + DWORD m_dwCookie; + tag_ObjectDesc *m_pNext; +} ObjectDesc; + +#define DLLIMPORT __declspec(dllimport) +#define DLLEXPORT __declspec(dllexport) + +#ifdef DEBUG + + #define NAME(x) TEXT(x) + + // These are used internally by the debug library (PRIVATE) + + void WINAPI DbgInitKeyLevels(HKEY hKey, bool fTakeMax); + void WINAPI DbgInitGlobalSettings(bool fTakeMax); + void WINAPI DbgInitModuleSettings(bool fTakeMax); + void WINAPI DbgInitModuleName(); + DWORD WINAPI DbgRegisterObjectCreation( + LPCSTR szObjectName, LPCWSTR wszObjectName); + + BOOL WINAPI DbgRegisterObjectDestruction(DWORD dwCookie); + + // These are the PUBLIC entry points + + BOOL WINAPI DbgCheckModuleLevel(DWORD Type,DWORD Level); + void WINAPI DbgSetModuleLevel(DWORD Type,DWORD Level); + void WINAPI DbgSetAutoRefreshLevels(bool fAuto); + + // Initialise the library with the module handle + + void WINAPI DbgInitialise(HINSTANCE hInst); + void WINAPI DbgTerminate(); + + void WINAPI DbgDumpObjectRegister(); + + // Display error and logging to the user + + void WINAPI DbgAssert(LPCTSTR pCondition,LPCTSTR pFileName,INT iLine); + void WINAPI DbgBreakPoint(LPCTSTR pCondition,LPCTSTR pFileName,INT iLine); + void WINAPI DbgBreakPoint(LPCTSTR pFileName,INT iLine,__format_string LPCTSTR szFormatString,...); + + void WINAPI DbgKernelAssert(LPCTSTR pCondition,LPCTSTR pFileName,INT iLine); + void WINAPI DbgLogInfo(DWORD Type,DWORD Level,__format_string LPCTSTR pFormat,...); +#ifdef UNICODE + void WINAPI DbgLogInfo(DWORD Type,DWORD Level,__format_string LPCSTR pFormat,...); + void WINAPI DbgAssert(LPCSTR pCondition,LPCSTR pFileName,INT iLine); + void WINAPI DbgBreakPoint(LPCSTR pCondition,LPCSTR pFileName,INT iLine); + void WINAPI DbgKernelAssert(LPCSTR pCondition,LPCSTR pFileName,INT iLine); +#endif + void WINAPI DbgOutString(LPCTSTR psz); + + // Debug infinite wait stuff + DWORD WINAPI DbgWaitForSingleObject(HANDLE h); + DWORD WINAPI DbgWaitForMultipleObjects(DWORD nCount, + __in_ecount(nCount) CONST HANDLE *lpHandles, + BOOL bWaitAll); + void WINAPI DbgSetWaitTimeout(DWORD dwTimeout); + +#ifdef __strmif_h__ + // Display a media type: Terse at level 2, verbose at level 5 + void WINAPI DisplayType(LPCTSTR label, const AM_MEDIA_TYPE *pmtIn); + + // Dump lots of information about a filter graph + void WINAPI DumpGraph(IFilterGraph *pGraph, DWORD dwLevel); +#endif + + #define KASSERT(_x_) if (!(_x_)) \ + DbgKernelAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__) + + // Break on the debugger without putting up a message box + // message goes to debugger instead + + #define KDbgBreak(_x_) \ + DbgKernelAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__) + + // We chose a common name for our ASSERT macro, MFC also uses this name + // So long as the implementation evaluates the condition and handles it + // then we will be ok. Rather than override the behaviour expected we + // will leave whatever first defines ASSERT as the handler (i.e. MFC) + #ifndef ASSERT + #define ASSERT(_x_) if (!(_x_)) \ + DbgAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__) + #endif + + #define DbgAssertAligned( _ptr_, _alignment_ ) ASSERT( ((DWORD_PTR) (_ptr_)) % (_alignment_) == 0) + + // Put up a message box informing the user of a halt + // condition in the program + + #define DbgBreak(_x_) \ + DbgBreakPoint(TEXT(#_x_),TEXT(__FILE__),__LINE__) + + #define EXECUTE_ASSERT(_x_) ASSERT(_x_) + #define DbgLog(_x_) DbgLogInfo _x_ + // MFC style trace macros + + #define NOTE(_x_) DbgLog((LOG_TRACE,5,TEXT(_x_))) + #define NOTE1(_x_,a) DbgLog((LOG_TRACE,5,TEXT(_x_),a)) + #define NOTE2(_x_,a,b) DbgLog((LOG_TRACE,5,TEXT(_x_),a,b)) + #define NOTE3(_x_,a,b,c) DbgLog((LOG_TRACE,5,TEXT(_x_),a,b,c)) + #define NOTE4(_x_,a,b,c,d) DbgLog((LOG_TRACE,5,TEXT(_x_),a,b,c,d)) + #define NOTE5(_x_,a,b,c,d,e) DbgLog((LOG_TRACE,5,TEXT(_x_),a,b,c,d,e)) + +#else + + // Retail builds make public debug functions inert - WARNING the source + // files do not define or build any of the entry points in debug builds + // (public entry points compile to nothing) so if you go trying to call + // any of the private entry points in your source they won't compile + + #define NAME(_x_) ((LPTSTR) NULL) + + #define DbgInitialise(hInst) + #define DbgTerminate() + #define DbgLog(_x_) 0 + #define DbgOutString(psz) + #define DbgAssertAligned( _ptr_, _alignment_ ) 0 + + #define DbgRegisterObjectCreation(pObjectName) + #define DbgRegisterObjectDestruction(dwCookie) + #define DbgDumpObjectRegister() + + #define DbgCheckModuleLevel(Type,Level) + #define DbgSetModuleLevel(Type,Level) + #define DbgSetAutoRefreshLevels(fAuto) + + #define DbgWaitForSingleObject(h) WaitForSingleObject(h, INFINITE) + #define DbgWaitForMultipleObjects(nCount, lpHandles, bWaitAll) \ + WaitForMultipleObjects(nCount, lpHandles, bWaitAll, INFINITE) + #define DbgSetWaitTimeout(dwTimeout) + + #define KDbgBreak(_x_) + #define DbgBreak(_x_) + + #define KASSERT(_x_) ((void)0) + #ifndef ASSERT + #define ASSERT(_x_) ((void)0) + #endif + #define EXECUTE_ASSERT(_x_) ((void)(_x_)) + + // MFC style trace macros + + #define NOTE(_x_) ((void)0) + #define NOTE1(_x_,a) ((void)0) + #define NOTE2(_x_,a,b) ((void)0) + #define NOTE3(_x_,a,b,c) ((void)0) + #define NOTE4(_x_,a,b,c,d) ((void)0) + #define NOTE5(_x_,a,b,c,d,e) ((void)0) + + #define DisplayType(label, pmtIn) ((void)0) + #define DumpGraph(pGraph, label) ((void)0) +#endif + + +// Checks a pointer which should be non NULL - can be used as follows. + +#define CheckPointer(p,ret) {if((p)==NULL) return (ret);} + +// HRESULT Foo(VOID *pBar) +// { +// CheckPointer(pBar,E_INVALIDARG) +// } +// +// Or if the function returns a boolean +// +// BOOL Foo(VOID *pBar) +// { +// CheckPointer(pBar,FALSE) +// } + +#define ValidateReadPtr(p,cb) 0 +#define ValidateWritePtr(p,cb) 0 +#define ValidateReadWritePtr(p,cb) 0 +#define ValidateStringPtr(p) 0 +#define ValidateStringPtrA(p) 0 +#define ValidateStringPtrW(p) 0 + + +#ifdef _OBJBASE_H_ + + // Outputting GUID names. If you want to include the name + // associated with a GUID (eg CLSID_...) then + // + // GuidNames[yourGUID] + // + // Returns the name defined in uuids.h as a string + + typedef struct { + CHAR *szName; + GUID guid; + } GUID_STRING_ENTRY; + + class CGuidNameList { + public: + CHAR *operator [] (const GUID& guid); + }; + + extern CGuidNameList GuidNames; + +#endif + +#ifndef REMIND + // REMIND macro - generates warning as reminder to complete coding + // (eg) usage: + // + // #pragma message (REMIND("Add automation support")) + + + #define QUOTE(x) #x + #define QQUOTE(y) QUOTE(y) + #define REMIND(str) __FILE__ "(" QQUOTE(__LINE__) ") : " str +#endif + +// Method to display objects in a useful format +// +// eg If you want to display a LONGLONG ll in a debug string do (eg) +// +// DbgLog((LOG_TRACE, n, TEXT("Value is %s"), (LPCTSTR)CDisp(ll, CDISP_HEX))); + + +class CDispBasic +{ +public: + CDispBasic() { m_pString = m_String; }; + ~CDispBasic(); +protected: + PTCHAR m_pString; // normally points to m_String... unless too much data + TCHAR m_String[50]; +}; +class CDisp : public CDispBasic +{ +public: + CDisp(LONGLONG ll, int Format = CDISP_HEX); // Display a LONGLONG in CDISP_HEX or CDISP_DEC form + CDisp(REFCLSID clsid); // Display a GUID + CDisp(double d); // Display a floating point number +#ifdef __strmif_h__ +#ifdef __STREAMS__ + CDisp(CRefTime t); // Display a Reference Time +#endif + CDisp(IPin *pPin); // Display a pin as {filter clsid}(pin name) + CDisp(IUnknown *pUnk); // Display a filter or pin +#endif // __strmif_h__ + ~CDisp(); + + // Implement cast to (LPCTSTR) as parameter to logger + operator LPCTSTR() + { + return (LPCTSTR)m_pString; + }; +}; + + +#if defined(DEBUG) +class CAutoTrace +{ +private: + LPCTSTR _szBlkName; + const int _level; + static const TCHAR _szEntering[]; + static const TCHAR _szLeaving[]; +public: + CAutoTrace(LPCTSTR szBlkName, const int level = 15) + : _szBlkName(szBlkName), _level(level) + {DbgLog((LOG_TRACE, _level, _szEntering, _szBlkName));} + + ~CAutoTrace() + {DbgLog((LOG_TRACE, _level, _szLeaving, _szBlkName));} +}; + +#if defined (__FUNCTION__) + +#define AMTRACEFN() CAutoTrace __trace(TEXT(__FUNCTION__)) +#define AMTRACE(_x_) CAutoTrace __trace(TEXT(__FUNCTION__)) + +#else + +#define AMTRACE(_x_) CAutoTrace __trace _x_ +#define AMTRACEFN() + +#endif + +#else + +#define AMTRACE(_x_) +#define AMTRACEFN() + +#endif + +#endif // __WXDEBUG__ + + diff --git a/Src/Plugins/Input/in_dshow/base/wxlist.cpp b/Src/Plugins/Input/in_dshow/base/wxlist.cpp new file mode 100644 index 00000000..75085fe2 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/wxlist.cpp @@ -0,0 +1,891 @@ +//------------------------------------------------------------------------------ +// File: WXList.cpp +// +// Desc: DirectShow base classes - implements a non-MFC based generic list +// template class. +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +/* A generic list of pointers to objects. + Objectives: avoid using MFC libraries in ndm kernel mode and + provide a really useful list type. + + The class is thread safe in that separate threads may add and + delete items in the list concurrently although the application + must ensure that constructor and destructor access is suitably + synchronised. + + The list name must not conflict with MFC classes as an + application may use both + + The nodes form a doubly linked, NULL terminated chain with an anchor + block (the list object per se) holding pointers to the first and last + nodes and a count of the nodes. + There is a node cache to reduce the allocation and freeing overhead. + It optionally (determined at construction time) has an Event which is + set whenever the list becomes non-empty and reset whenever it becomes + empty. + It optionally (determined at construction time) has a Critical Section + which is entered during the important part of each operation. (About + all you can do outside it is some parameter checking). + + The node cache is a repository of nodes that are NOT in the list to speed + up storage allocation. Each list has its own cache to reduce locking and + serialising. The list accesses are serialised anyway for a given list - a + common cache would mean that we would have to separately serialise access + of all lists within the cache. Because the cache only stores nodes that are + not in the list, releasing the cache does not release any list nodes. This + means that list nodes can be copied or rechained from one list to another + without danger of creating a dangling reference if the original cache goes + away. + + Questionable design decisions: + 1. Retaining the warts for compatibility + 2. Keeping an element count -i.e. counting whenever we do anything + instead of only when we want the count. + 3. Making the chain pointers NULL terminated. If the list object + itself looks just like a node and the list is kept as a ring then + it reduces the number of special cases. All inserts look the same. +*/ + + +#include <streams.h> + +/* set cursor to the position of each element of list in turn */ +#define INTERNALTRAVERSELIST(list, cursor) \ +for ( cursor = (list).GetHeadPositionI() \ + ; cursor!=NULL \ + ; cursor = (list).Next(cursor) \ + ) + + +/* set cursor to the position of each element of list in turn + in reverse order +*/ +#define INTERNALREVERSETRAVERSELIST(list, cursor) \ +for ( cursor = (list).GetTailPositionI() \ + ; cursor!=NULL \ + ; cursor = (list).Prev(cursor) \ + ) + +/* Constructor calls a separate initialisation function that + creates a node cache, optionally creates a lock object + and optionally creates a signaling object. + + By default we create a locking object, a DEFAULTCACHE sized + cache but no event object so the list cannot be used in calls + to WaitForSingleObject +*/ +CBaseList::CBaseList(__in_opt LPCTSTR pName, // Descriptive list name + INT iItems) : // Node cache size +#ifdef DEBUG + CBaseObject(pName), +#endif + m_pFirst(NULL), + m_pLast(NULL), + m_Count(0), + m_Cache(iItems) +{ +} // constructor + +CBaseList::CBaseList(__in_opt LPCTSTR pName) : // Descriptive list name +#ifdef DEBUG + CBaseObject(pName), +#endif + m_pFirst(NULL), + m_pLast(NULL), + m_Count(0), + m_Cache(DEFAULTCACHE) +{ +} // constructor + +#ifdef UNICODE +CBaseList::CBaseList(__in_opt LPCSTR pName, // Descriptive list name + INT iItems) : // Node cache size +#ifdef DEBUG + CBaseObject(pName), +#endif + m_pFirst(NULL), + m_pLast(NULL), + m_Count(0), + m_Cache(iItems) +{ +} // constructor + +CBaseList::CBaseList(__in_opt LPCSTR pName) : // Descriptive list name +#ifdef DEBUG + CBaseObject(pName), +#endif + m_pFirst(NULL), + m_pLast(NULL), + m_Count(0), + m_Cache(DEFAULTCACHE) +{ +} // constructor + +#endif + +/* The destructor enumerates all the node objects in the list and + in the cache deleting each in turn. We do not do any processing + on the objects that the list holds (i.e. points to) so if they + represent interfaces for example the creator of the list should + ensure that each of them is released before deleting us +*/ +CBaseList::~CBaseList() +{ + /* Delete all our list nodes */ + + RemoveAll(); + +} // destructor + +/* Remove all the nodes from the list but don't do anything + with the objects that each node looks after (this is the + responsibility of the creator). + Aa a last act we reset the signalling event + (if available) to indicate to clients that the list + does not have any entries in it. +*/ +void CBaseList::RemoveAll() +{ + /* Free up all the CNode objects NOTE we don't bother putting the + deleted nodes into the cache as this method is only really called + in serious times of change such as when we are being deleted at + which point the cache will be deleted anway */ + + CNode *pn = m_pFirst; + while (pn) { + CNode *op = pn; + pn = pn->Next(); + delete op; + } + + /* Reset the object count and the list pointers */ + + m_Count = 0; + m_pFirst = m_pLast = NULL; + +} // RemoveAll + + + +/* Return a position enumerator for the entire list. + A position enumerator is a pointer to a node object cast to a + transparent type so all we do is return the _head/_tail node + pointer in the list. + WARNING because the position is a pointer to a node there is + an implicit assumption for users a the list class that after + deleting an object from the list that any other position + enumerators that you have may be invalid (since the node + may be gone). +*/ +__out_opt POSITION CBaseList::GetHeadPositionI() const +{ + return (POSITION) m_pFirst; +} // GetHeadPosition + + + +__out_opt POSITION CBaseList::GetTailPositionI() const +{ + return (POSITION) m_pLast; +} // GetTailPosition + + + +/* Get the number of objects in the list, + Get the lock before accessing the count. + Locking may not be entirely necessary but it has the side effect + of making sure that all operations are complete before we get it. + So for example if a list is being added to this list then that + will have completed in full before we continue rather than seeing + an intermediate albeit valid state +*/ +int CBaseList::GetCountI() const +{ + return m_Count; +} // GetCount + + + +/* Return the object at rp, update rp to the next object from + the list or NULL if you have moved over the last object. + You may still call this function once we return NULL but + we will continue to return a NULL position value +*/ +__out void *CBaseList::GetNextI(__inout POSITION& rp) const +{ + /* have we reached the end of the list */ + + if (rp == NULL) { + return NULL; + } + + /* Lock the object before continuing */ + + void *pObject; + + /* Copy the original position then step on */ + + CNode *pn = (CNode *) rp; + ASSERT(pn != NULL); + rp = (POSITION) pn->Next(); + + /* Get the object at the original position from the list */ + + pObject = pn->GetData(); + // ASSERT(pObject != NULL); // NULL pointers in the list are allowed. + return pObject; +} //GetNext + + + +/* Return the object at p. + Asking for the object at NULL ASSERTs then returns NULL + The object is NOT locked. The list is not being changed + in any way. If another thread is busy deleting the object + then locking would only result in a change from one bad + behaviour to another. +*/ +__out_opt void *CBaseList::GetI(__in_opt POSITION p) const +{ + if (p == NULL) { + return NULL; + } + + CNode * pn = (CNode *) p; + void *pObject = pn->GetData(); + // ASSERT(pObject != NULL); // NULL pointers in the list are allowed. + return pObject; +} //Get + +__out void *CBaseList::GetValidI(__in POSITION p) const +{ + CNode * pn = (CNode *) p; + void *pObject = pn->GetData(); + // ASSERT(pObject != NULL); // NULL pointers in the list are allowed. + return pObject; +} //Get + + +/* Return the first position in the list which holds the given pointer. + Return NULL if it's not found. +*/ +__out_opt POSITION CBaseList::FindI( __in void * pObj) const +{ + POSITION pn; + INTERNALTRAVERSELIST(*this, pn){ + if (GetI(pn)==pObj) { + return pn; + } + } + return NULL; +} // Find + + + +/* Remove the first node in the list (deletes the pointer to its object + from the list, does not free the object itself). + Return the pointer to its object or NULL if empty +*/ +__out_opt void *CBaseList::RemoveHeadI() +{ + /* All we do is get the _head position and ask for that to be deleted. + We could special case this since some of the code path checking + in Remove() is redundant as we know there is no previous + node for example but it seems to gain little over the + added complexity + */ + + return RemoveI((POSITION)m_pFirst); +} // RemoveHead + + + +/* Remove the last node in the list (deletes the pointer to its object + from the list, does not free the object itself). + Return the pointer to its object or NULL if empty +*/ +__out_opt void *CBaseList::RemoveTailI() +{ + /* All we do is get the _tail position and ask for that to be deleted. + We could special case this since some of the code path checking + in Remove() is redundant as we know there is no previous + node for example but it seems to gain little over the + added complexity + */ + + return RemoveI((POSITION)m_pLast); +} // RemoveTail + + + +/* Remove the pointer to the object in this position from the list. + Deal with all the chain pointers + Return a pointer to the object removed from the list. + The node object that is freed as a result + of this operation is added to the node cache where + it can be used again. + Remove(NULL) is a harmless no-op - but probably is a wart. +*/ +__out_opt void *CBaseList::RemoveI(__in_opt POSITION pos) +{ + /* Lock the critical section before continuing */ + + // ASSERT (pos!=NULL); // Removing NULL is to be harmless! + if (pos==NULL) return NULL; + + + CNode *pCurrent = (CNode *) pos; + ASSERT(pCurrent != NULL); + + /* Update the previous node */ + + CNode *pNode = pCurrent->Prev(); + if (pNode == NULL) { + m_pFirst = pCurrent->Next(); + } else { + pNode->SetNext(pCurrent->Next()); + } + + /* Update the following node */ + + pNode = pCurrent->Next(); + if (pNode == NULL) { + m_pLast = pCurrent->Prev(); + } else { + pNode->SetPrev(pCurrent->Prev()); + } + + /* Get the object this node was looking after */ + + void *pObject = pCurrent->GetData(); + + // ASSERT(pObject != NULL); // NULL pointers in the list are allowed. + + /* Try and add the node object to the cache - + a NULL return code from the cache means we ran out of room. + The cache size is fixed by a constructor argument when the + list is created and defaults to DEFAULTCACHE. + This means that the cache will have room for this many + node objects. So if you have a list of media samples + and you know there will never be more than five active at + any given time of them for example then override the default + constructor + */ + + m_Cache.AddToCache(pCurrent); + + /* If the list is empty then reset the list event */ + + --m_Count; + ASSERT(m_Count >= 0); + return pObject; +} // Remove + + + +/* Add this object to the _tail end of our list + Return the new _tail position. +*/ + +__out_opt POSITION CBaseList::AddTailI(__in void *pObject) +{ + /* Lock the critical section before continuing */ + + CNode *pNode; + // ASSERT(pObject); // NULL pointers in the list are allowed. + + /* If there is a node objects in the cache then use + that otherwise we will have to create a new one */ + + pNode = (CNode *) m_Cache.RemoveFromCache(); + if (pNode == NULL) { + pNode = new CNode; + } + + /* Check we have a valid object */ + + if (pNode == NULL) { + return NULL; + } + + /* Initialise all the CNode object + just in case it came from the cache + */ + + pNode->SetData(pObject); + pNode->SetNext(NULL); + pNode->SetPrev(m_pLast); + + if (m_pLast == NULL) { + m_pFirst = pNode; + } else { + m_pLast->SetNext(pNode); + } + + /* Set the new last node pointer and also increment the number + of list entries, the critical section is unlocked when we + exit the function + */ + + m_pLast = pNode; + ++m_Count; + + return (POSITION) pNode; +} // AddTail(object) + + + +/* Add this object to the _head end of our list + Return the new _head position. +*/ +__out_opt POSITION CBaseList::AddHeadI(__in void *pObject) +{ + CNode *pNode; + // ASSERT(pObject); // NULL pointers in the list are allowed. + + /* If there is a node objects in the cache then use + that otherwise we will have to create a new one */ + + pNode = (CNode *) m_Cache.RemoveFromCache(); + if (pNode == NULL) { + pNode = new CNode; + } + + /* Check we have a valid object */ + + if (pNode == NULL) { + return NULL; + } + + /* Initialise all the CNode object + just in case it came from the cache + */ + + pNode->SetData(pObject); + + /* chain it in (set four pointers) */ + pNode->SetPrev(NULL); + pNode->SetNext(m_pFirst); + + if (m_pFirst == NULL) { + m_pLast = pNode; + } else { + m_pFirst->SetPrev(pNode); + } + m_pFirst = pNode; + + ++m_Count; + + return (POSITION) pNode; +} // AddHead(object) + + + +/* Add all the elements in *pList to the _tail of this list. + Return TRUE if it all worked, FALSE if it didn't. + If it fails some elements may have been added. +*/ +BOOL CBaseList::AddTail(__in CBaseList *pList) +{ + /* lock the object before starting then enumerate + each entry in the source list and add them one by one to + our list (while still holding the object lock) + Lock the other list too. + */ + POSITION pos = pList->GetHeadPositionI(); + + while (pos) { + if (NULL == AddTailI(pList->GetNextI(pos))) { + return FALSE; + } + } + return TRUE; +} // AddTail(list) + + + +/* Add all the elements in *pList to the _head of this list. + Return TRUE if it all worked, FALSE if it didn't. + If it fails some elements may have been added. +*/ +BOOL CBaseList::AddHead(__in CBaseList *pList) +{ + /* lock the object before starting then enumerate + each entry in the source list and add them one by one to + our list (while still holding the object lock) + Lock the other list too. + + To avoid reversing the list, traverse it backwards. + */ + + POSITION pos; + + INTERNALREVERSETRAVERSELIST(*pList, pos) { + if (NULL== AddHeadI(pList->GetValidI(pos))){ + return FALSE; + } + } + return TRUE; +} // AddHead(list) + + + +/* Add the object after position p + p is still valid after the operation. + AddAfter(NULL,x) adds x to the start - same as AddHead + Return the position of the new object, NULL if it failed +*/ +__out_opt POSITION CBaseList::AddAfterI(__in_opt POSITION pos, __in void * pObj) +{ + if (pos==NULL) + return AddHeadI(pObj); + + /* As someone else might be furkling with the list - + Lock the critical section before continuing + */ + CNode *pAfter = (CNode *) pos; + ASSERT(pAfter != NULL); + if (pAfter==m_pLast) + return AddTailI(pObj); + + /* set pnode to point to a new node, preferably from the cache */ + + CNode *pNode = (CNode *) m_Cache.RemoveFromCache(); + if (pNode == NULL) { + pNode = new CNode; + } + + /* Check we have a valid object */ + + if (pNode == NULL) { + return NULL; + } + + /* Initialise all the CNode object + just in case it came from the cache + */ + + pNode->SetData(pObj); + + /* It is to be added to the middle of the list - there is a before + and after node. Chain it after pAfter, before pBefore. + */ + CNode * pBefore = pAfter->Next(); + ASSERT(pBefore != NULL); + + /* chain it in (set four pointers) */ + pNode->SetPrev(pAfter); + pNode->SetNext(pBefore); + pBefore->SetPrev(pNode); + pAfter->SetNext(pNode); + + ++m_Count; + + return (POSITION) pNode; + +} // AddAfter(object) + + + +BOOL CBaseList::AddAfter(__in_opt POSITION p, __in CBaseList *pList) +{ + POSITION pos; + INTERNALTRAVERSELIST(*pList, pos) { + /* p follows along the elements being added */ + p = AddAfterI(p, pList->GetValidI(pos)); + if (p==NULL) return FALSE; + } + return TRUE; +} // AddAfter(list) + + + +/* Mirror images: + Add the element or list after position p. + p is still valid after the operation. + AddBefore(NULL,x) adds x to the end - same as AddTail +*/ +__out_opt POSITION CBaseList::AddBeforeI(__in_opt POSITION pos, __in void * pObj) +{ + if (pos==NULL) + return AddTailI(pObj); + + /* set pnode to point to a new node, preferably from the cache */ + + CNode *pBefore = (CNode *) pos; + ASSERT(pBefore != NULL); + if (pBefore==m_pFirst) + return AddHeadI(pObj); + + CNode * pNode = (CNode *) m_Cache.RemoveFromCache(); + if (pNode == NULL) { + pNode = new CNode; + } + + /* Check we have a valid object */ + + if (pNode == NULL) { + return NULL; + } + + /* Initialise all the CNode object + just in case it came from the cache + */ + + pNode->SetData(pObj); + + /* It is to be added to the middle of the list - there is a before + and after node. Chain it after pAfter, before pBefore. + */ + + CNode * pAfter = pBefore->Prev(); + ASSERT(pAfter != NULL); + + /* chain it in (set four pointers) */ + pNode->SetPrev(pAfter); + pNode->SetNext(pBefore); + pBefore->SetPrev(pNode); + pAfter->SetNext(pNode); + + ++m_Count; + + return (POSITION) pNode; + +} // Addbefore(object) + + + +BOOL CBaseList::AddBefore(__in_opt POSITION p, __in CBaseList *pList) +{ + POSITION pos; + INTERNALREVERSETRAVERSELIST(*pList, pos) { + /* p follows along the elements being added */ + p = AddBeforeI(p, pList->GetValidI(pos)); + if (p==NULL) return FALSE; + } + return TRUE; +} // AddBefore(list) + + + +/* Split *this after position p in *this + Retain as *this the _tail portion of the original *this + Add the _head portion to the _tail end of *pList + Return TRUE if it all worked, FALSE if it didn't. + + e.g. + foo->MoveToTail(foo->GetHeadPosition(), bar); + moves one element from the _head of foo to the _tail of bar + foo->MoveToTail(NULL, bar); + is a no-op + foo->MoveToTail(foo->GetTailPosition, bar); + concatenates foo onto the end of bar and empties foo. + + A better, except excessively long name might be + MoveElementsFromHeadThroughPositionToOtherTail +*/ +BOOL CBaseList::MoveToTail + (__in_opt POSITION pos, __in CBaseList *pList) +{ + /* Algorithm: + Note that the elements (including their order) in the concatenation + of *pList to the _head of *this is invariant. + 1. Count elements to be moved + 2. Join *pList onto the _head of this to make one long chain + 3. Set first/Last pointers in *this and *pList + 4. Break the chain at the new place + 5. Adjust counts + 6. Set/Reset any events + */ + + if (pos==NULL) return TRUE; // no-op. Eliminates special cases later. + + + /* Make cMove the number of nodes to move */ + CNode * p = (CNode *)pos; + int cMove = 0; // number of nodes to move + while(p!=NULL) { + p = p->Prev(); + ++cMove; + } + + + /* Join the two chains together */ + if (pList->m_pLast!=NULL) + pList->m_pLast->SetNext(m_pFirst); + if (m_pFirst!=NULL) + m_pFirst->SetPrev(pList->m_pLast); + + + /* set first and last pointers */ + p = (CNode *)pos; + + if (pList->m_pFirst==NULL) + pList->m_pFirst = m_pFirst; + m_pFirst = p->Next(); + if (m_pFirst==NULL) + m_pLast = NULL; + pList->m_pLast = p; + + + /* Break the chain after p to create the new pieces */ + if (m_pFirst!=NULL) + m_pFirst->SetPrev(NULL); + p->SetNext(NULL); + + + /* Adjust the counts */ + m_Count -= cMove; + pList->m_Count += cMove; + + return TRUE; + +} // MoveToTail + + + +/* Mirror image of MoveToTail: + Split *this before position p in *this. + Retain in *this the _head portion of the original *this + Add the _tail portion to the start (i.e. _head) of *pList + Return TRUE if it all worked, FALSE if it didn't. + + e.g. + foo->MoveToHead(foo->GetTailPosition(), bar); + moves one element from the _tail of foo to the _head of bar + foo->MoveToHead(NULL, bar); + is a no-op + foo->MoveToHead(foo->GetHeadPosition, bar); + concatenates foo onto the start of bar and empties foo. +*/ +BOOL CBaseList::MoveToHead + (__in_opt POSITION pos, __in CBaseList *pList) +{ + + /* See the comments on the algorithm in MoveToTail */ + + if (pos==NULL) return TRUE; // no-op. Eliminates special cases later. + + /* Make cMove the number of nodes to move */ + CNode * p = (CNode *)pos; + int cMove = 0; // number of nodes to move + while(p!=NULL) { + p = p->Next(); + ++cMove; + } + + + /* Join the two chains together */ + if (pList->m_pFirst!=NULL) + pList->m_pFirst->SetPrev(m_pLast); + if (m_pLast!=NULL) + m_pLast->SetNext(pList->m_pFirst); + + + /* set first and last pointers */ + p = (CNode *)pos; + + + if (pList->m_pLast==NULL) + pList->m_pLast = m_pLast; + + m_pLast = p->Prev(); + if (m_pLast==NULL) + m_pFirst = NULL; + pList->m_pFirst = p; + + + /* Break the chain after p to create the new pieces */ + if (m_pLast!=NULL) + m_pLast->SetNext(NULL); + p->SetPrev(NULL); + + + /* Adjust the counts */ + m_Count -= cMove; + pList->m_Count += cMove; + + return TRUE; + +} // MoveToHead + + + +/* Reverse the order of the [pointers to] objects in *this +*/ +void CBaseList::Reverse() +{ + /* algorithm: + The obvious booby trap is that you flip pointers around and lose + addressability to the node that you are going to process next. + The easy way to avoid this is do do one chain at a time. + + Run along the forward chain, + For each node, set the reverse pointer to the one ahead of us. + The reverse chain is now a copy of the old forward chain, including + the NULL termination. + + Run along the reverse chain (i.e. old forward chain again) + For each node set the forward pointer of the node ahead to point back + to the one we're standing on. + The first node needs special treatment, + it's new forward pointer is NULL. + Finally set the First/Last pointers + + */ + CNode * p; + + // Yes we COULD use a traverse, but it would look funny! + p = m_pFirst; + while (p!=NULL) { + CNode * q; + q = p->Next(); + p->SetNext(p->Prev()); + p->SetPrev(q); + p = q; + } + + p = m_pFirst; + m_pFirst = m_pLast; + m_pLast = p; + + +#if 0 // old version + + if (m_pFirst==NULL) return; // empty list + if (m_pFirst->Next()==NULL) return; // single node list + + + /* run along forward chain */ + for ( p = m_pFirst + ; p!=NULL + ; p = p->Next() + ){ + p->SetPrev(p->Next()); + } + + + /* special case first element */ + m_pFirst->SetNext(NULL); // fix the old first element + + + /* run along new reverse chain i.e. old forward chain again */ + for ( p = m_pFirst // start at the old first element + ; p->Prev()!=NULL // while there's a node still to be set + ; p = p->Prev() // work in the same direction as before + ){ + p->Prev()->SetNext(p); + } + + + /* fix forward and reverse pointers + - the triple XOR swap would work but all the casts look hideous */ + p = m_pFirst; + m_pFirst = m_pLast; + m_pLast = p; +#endif + +} // Reverse diff --git a/Src/Plugins/Input/in_dshow/base/wxlist.h b/Src/Plugins/Input/in_dshow/base/wxlist.h new file mode 100644 index 00000000..dbd951c3 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/wxlist.h @@ -0,0 +1,553 @@ +//------------------------------------------------------------------------------ +// File: WXList.h +// +// Desc: DirectShow base classes - defines a non-MFC generic template list +// class. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +/* A generic list of pointers to objects. + No storage management or copying is done on the objects pointed to. + Objectives: avoid using MFC libraries in ndm kernel mode and + provide a really useful list type. + + The class is thread safe in that separate threads may add and + delete items in the list concurrently although the application + must ensure that constructor and destructor access is suitably + synchronised. An application can cause deadlock with operations + which use two lists by simultaneously calling + list1->Operation(list2) and list2->Operation(list1). So don't! + + The names must not conflict with MFC classes as an application + may use both. + */ + +#ifndef __WXLIST__ +#define __WXLIST__ + + /* A POSITION represents (in some fashion that's opaque) a cursor + on the list that can be set to identify any element. NULL is + a valid value and several operations regard NULL as the position + "one step off the end of the list". (In an n element list there + are n+1 places to insert and NULL is that "n+1-th" value). + The POSITION of an element in the list is only invalidated if + that element is deleted. Move operations may mean that what + was a valid POSITION in one list is now a valid POSITION in + a different list. + + Some operations which at first sight are illegal are allowed as + harmless no-ops. For instance RemoveHead is legal on an empty + list and it returns NULL. This allows an atomic way to test if + there is an element there, and if so, get it. The two operations + AddTail and RemoveHead thus implement a MONITOR (See Hoare's paper). + + Single element operations return POSITIONs, non-NULL means it worked. + whole list operations return a BOOL. TRUE means it all worked. + + This definition is the same as the POSITION type for MFCs, so we must + avoid defining it twice. + */ +#ifndef __AFX_H__ +struct __POSITION { int unused; }; +typedef __POSITION* POSITION; +#endif + +const int DEFAULTCACHE = 10; /* Default node object cache size */ + +/* A class representing one node in a list. + Each node knows a pointer to it's adjacent nodes and also a pointer + to the object that it looks after. + All of these pointers can be retrieved or set through member functions. +*/ +class CBaseList +#ifdef DEBUG + : public CBaseObject +#endif +{ + /* Making these classes inherit from CBaseObject does nothing + functionally but it allows us to check there are no memory + leaks in debug builds. + */ + +public: + +#ifdef DEBUG + class CNode : public CBaseObject { +#else + class CNode { +#endif + + CNode *m_pPrev; /* Previous node in the list */ + CNode *m_pNext; /* Next node in the list */ + void *m_pObject; /* Pointer to the object */ + + public: + + /* Constructor - initialise the object's pointers */ + CNode() +#ifdef DEBUG + : CBaseObject(NAME("List node")) +#endif + { + }; + + + /* Return the previous node before this one */ + __out CNode *Prev() const { return m_pPrev; }; + + + /* Return the next node after this one */ + __out CNode *Next() const { return m_pNext; }; + + + /* Set the previous node before this one */ + void SetPrev(__in_opt CNode *p) { m_pPrev = p; }; + + + /* Set the next node after this one */ + void SetNext(__in_opt CNode *p) { m_pNext = p; }; + + + /* Get the pointer to the object for this node */ + __out void *GetData() const { return m_pObject; }; + + + /* Set the pointer to the object for this node */ + void SetData(__in void *p) { m_pObject = p; }; + }; + + class CNodeCache + { + public: + CNodeCache(INT iCacheSize) : m_iCacheSize(iCacheSize), + m_pHead(NULL), + m_iUsed(0) + {}; + ~CNodeCache() { + CNode *pNode = m_pHead; + while (pNode) { + CNode *pCurrent = pNode; + pNode = pNode->Next(); + delete pCurrent; + } + }; + void AddToCache(__inout CNode *pNode) + { + if (m_iUsed < m_iCacheSize) { + pNode->SetNext(m_pHead); + m_pHead = pNode; + m_iUsed++; + } else { + delete pNode; + } + }; + CNode *RemoveFromCache() + { + CNode *pNode = m_pHead; + if (pNode != NULL) { + m_pHead = pNode->Next(); + m_iUsed--; + ASSERT(m_iUsed >= 0); + } else { + ASSERT(m_iUsed == 0); + } + return pNode; + }; + private: + INT m_iCacheSize; + INT m_iUsed; + CNode *m_pHead; + }; + +protected: + + CNode* m_pFirst; /* Pointer to first node in the list */ + CNode* m_pLast; /* Pointer to the last node in the list */ + LONG m_Count; /* Number of nodes currently in the list */ + +private: + + CNodeCache m_Cache; /* Cache of unused node pointers */ + +private: + + /* These override the default copy constructor and assignment + operator for all list classes. They are in the private class + declaration section so that anybody trying to pass a list + object by value will generate a compile time error of + "cannot access the private member function". If these were + not here then the compiler will create default constructors + and assignment operators which when executed first take a + copy of all member variables and then during destruction + delete them all. This must not be done for any heap + allocated data. + */ + CBaseList(const CBaseList &refList); + CBaseList &operator=(const CBaseList &refList); + +public: + + CBaseList(__in_opt LPCTSTR pName, + INT iItems); + + CBaseList(__in_opt LPCTSTR pName); +#ifdef UNICODE + CBaseList(__in_opt LPCSTR pName, + INT iItems); + + CBaseList(__in_opt LPCSTR pName); +#endif + ~CBaseList(); + + /* Remove all the nodes from *this i.e. make the list empty */ + void RemoveAll(); + + + /* Return a cursor which identifies the first element of *this */ + __out_opt POSITION GetHeadPositionI() const; + + + /* Return a cursor which identifies the last element of *this */ + __out_opt POSITION GetTailPositionI() const; + + + /* Return the number of objects in *this */ + int GetCountI() const; + +protected: + /* Return the pointer to the object at rp, + Update rp to the next node in *this + but make it NULL if it was at the end of *this. + This is a wart retained for backwards compatibility. + GetPrev is not implemented. + Use Next, Prev and Get separately. + */ + __out void *GetNextI(__inout POSITION& rp) const; + + + /* Return a pointer to the object at p + Asking for the object at NULL will return NULL harmlessly. + */ + __out_opt void *GetI(__in_opt POSITION p) const; + __out void *GetValidI(__in POSITION p) const; + +public: + /* return the next / prev position in *this + return NULL when going past the end/start. + Next(NULL) is same as GetHeadPosition() + Prev(NULL) is same as GetTailPosition() + An n element list therefore behaves like a n+1 element + cycle with NULL at the start/end. + + !!WARNING!! - This handling of NULL is DIFFERENT from GetNext. + + Some reasons are: + 1. For a list of n items there are n+1 positions to insert + These are conveniently encoded as the n POSITIONs and NULL. + 2. If you are keeping a list sorted (fairly common) and you + search forward for an element to insert before and don't + find it you finish up with NULL as the element before which + to insert. You then want that NULL to be a valid POSITION + so that you can insert before it and you want that insertion + point to mean the (n+1)-th one that doesn't have a POSITION. + (symmetrically if you are working backwards through the list). + 3. It simplifies the algebra which the methods generate. + e.g. AddBefore(p,x) is identical to AddAfter(Prev(p),x) + in ALL cases. All the other arguments probably are reflections + of the algebraic point. + */ + __out_opt POSITION Next(__in_opt POSITION pos) const + { + if (pos == NULL) { + return (POSITION) m_pFirst; + } + CNode *pn = (CNode *) pos; + return (POSITION) pn->Next(); + } //Next + + // See Next + __out_opt POSITION Prev(__in_opt POSITION pos) const + { + if (pos == NULL) { + return (POSITION) m_pLast; + } + CNode *pn = (CNode *) pos; + return (POSITION) pn->Prev(); + } //Prev + + + /* Return the first position in *this which holds the given + pointer. Return NULL if the pointer was not not found. + */ +protected: + __out_opt POSITION FindI( __in void * pObj) const; + + // ??? Should there be (or even should there be only) + // ??? POSITION FindNextAfter(void * pObj, POSITION p) + // ??? And of course FindPrevBefore too. + // ??? List.Find(&Obj) then becomes List.FindNextAfter(&Obj, NULL) + + + /* Remove the first node in *this (deletes the pointer to its + object from the list, does not free the object itself). + Return the pointer to its object. + If *this was already empty it will harmlessly return NULL. + */ + __out_opt void *RemoveHeadI(); + + + /* Remove the last node in *this (deletes the pointer to its + object from the list, does not free the object itself). + Return the pointer to its object. + If *this was already empty it will harmlessly return NULL. + */ + __out_opt void *RemoveTailI(); + + + /* Remove the node identified by p from the list (deletes the pointer + to its object from the list, does not free the object itself). + Asking to Remove the object at NULL will harmlessly return NULL. + Return the pointer to the object removed. + */ + __out_opt void *RemoveI(__in_opt POSITION p); + + /* Add single object *pObj to become a new last element of the list. + Return the new _tail position, NULL if it fails. + If you are adding a COM objects, you might want AddRef it first. + Other existing POSITIONs in *this are still valid + */ + __out_opt POSITION AddTailI(__in void * pObj); +public: + + + /* Add all the elements in *pList to the _tail of *this. + This duplicates all the nodes in *pList (i.e. duplicates + all its pointers to objects). It does not duplicate the objects. + If you are adding a list of pointers to a COM object into the list + it's a good idea to AddRef them all it when you AddTail it. + Return TRUE if it all worked, FALSE if it didn't. + If it fails some elements may have been added. + Existing POSITIONs in *this are still valid + + If you actually want to MOVE the elements, use MoveToTail instead. + */ + BOOL AddTail(__in CBaseList *pList); + + + /* Mirror images of AddHead: */ + + /* Add single object to become a new first element of the list. + Return the new _head position, NULL if it fails. + Existing POSITIONs in *this are still valid + */ +protected: + __out_opt POSITION AddHeadI(__in void * pObj); +public: + + /* Add all the elements in *pList to the _head of *this. + Same warnings apply as for AddTail. + Return TRUE if it all worked, FALSE if it didn't. + If it fails some of the objects may have been added. + + If you actually want to MOVE the elements, use MoveToHead instead. + */ + BOOL AddHead(__in CBaseList *pList); + + + /* Add the object *pObj to *this after position p in *this. + AddAfter(NULL,x) adds x to the start - equivalent to AddHead + Return the position of the object added, NULL if it failed. + Existing POSITIONs in *this are undisturbed, including p. + */ +protected: + __out_opt POSITION AddAfterI(__in_opt POSITION p, __in void * pObj); +public: + + /* Add the list *pList to *this after position p in *this + AddAfter(NULL,x) adds x to the start - equivalent to AddHead + Return TRUE if it all worked, FALSE if it didn't. + If it fails, some of the objects may be added + Existing POSITIONs in *this are undisturbed, including p. + */ + BOOL AddAfter(__in_opt POSITION p, __in CBaseList *pList); + + + /* Mirror images: + Add the object *pObj to this-List after position p in *this. + AddBefore(NULL,x) adds x to the end - equivalent to AddTail + Return the position of the new object, NULL if it fails + Existing POSITIONs in *this are undisturbed, including p. + */ + protected: + __out_opt POSITION AddBeforeI(__in_opt POSITION p, __in void * pObj); + public: + + /* Add the list *pList to *this before position p in *this + AddAfter(NULL,x) adds x to the start - equivalent to AddHead + Return TRUE if it all worked, FALSE if it didn't. + If it fails, some of the objects may be added + Existing POSITIONs in *this are undisturbed, including p. + */ + BOOL AddBefore(__in_opt POSITION p, __in CBaseList *pList); + + + /* Note that AddAfter(p,x) is equivalent to AddBefore(Next(p),x) + even in cases where p is NULL or Next(p) is NULL. + Similarly for mirror images etc. + This may make it easier to argue about programs. + */ + + + + /* The following operations do not copy any elements. + They move existing blocks of elements around by switching pointers. + They are fairly efficient for long lists as for short lists. + (Alas, the Count slows things down). + + They split the list into two parts. + One part remains as the original list, the other part + is appended to the second list. There are eight possible + variations: + Split the list {after/before} a given element + keep the {_head/_tail} portion in the original list + append the rest to the {_head/_tail} of the new list. + + Since After is strictly equivalent to Before Next + we are not in serious need of the Before/After variants. + That leaves only four. + + If you are processing a list left to right and dumping + the bits that you have processed into another list as + you go, the Tail/Tail variant gives the most natural result. + If you are processing in reverse order, Head/Head is best. + + By using NULL positions and empty lists judiciously either + of the other two can be built up in two operations. + + The definition of NULL (see Next/Prev etc) means that + degenerate cases include + "move all elements to new list" + "Split a list into two lists" + "Concatenate two lists" + (and quite a few no-ops) + + !!WARNING!! The type checking won't buy you much if you get list + positions muddled up - e.g. use a POSITION that's in a different + list and see what a mess you get! + */ + + /* Split *this after position p in *this + Retain as *this the _tail portion of the original *this + Add the _head portion to the _tail end of *pList + Return TRUE if it all worked, FALSE if it didn't. + + e.g. + foo->MoveToTail(foo->GetHeadPosition(), bar); + moves one element from the _head of foo to the _tail of bar + foo->MoveToTail(NULL, bar); + is a no-op, returns NULL + foo->MoveToTail(foo->GetTailPosition, bar); + concatenates foo onto the end of bar and empties foo. + + A better, except excessively long name might be + MoveElementsFromHeadThroughPositionToOtherTail + */ + BOOL MoveToTail(__in_opt POSITION pos, __in CBaseList *pList); + + + /* Mirror image: + Split *this before position p in *this. + Retain in *this the _head portion of the original *this + Add the _tail portion to the start (i.e. _head) of *pList + + e.g. + foo->MoveToHead(foo->GetTailPosition(), bar); + moves one element from the _tail of foo to the _head of bar + foo->MoveToHead(NULL, bar); + is a no-op, returns NULL + foo->MoveToHead(foo->GetHeadPosition, bar); + concatenates foo onto the start of bar and empties foo. + */ + BOOL MoveToHead(__in_opt POSITION pos, __in CBaseList *pList); + + + /* Reverse the order of the [pointers to] objects in *this + */ + void Reverse(); + + + /* set cursor to the position of each element of list in turn */ + #define TRAVERSELIST(list, cursor) \ + for ( cursor = (list).GetHeadPosition() \ + ; cursor!=NULL \ + ; cursor = (list).Next(cursor) \ + ) + + + /* set cursor to the position of each element of list in turn + in reverse order + */ + #define REVERSETRAVERSELIST(list, cursor) \ + for ( cursor = (list).GetTailPosition() \ + ; cursor!=NULL \ + ; cursor = (list).Prev(cursor) \ + ) + +}; // end of class declaration + +template<class OBJECT> class CGenericList : public CBaseList +{ +public: + CGenericList(__in_opt LPCTSTR pName, + INT iItems, + BOOL bLock = TRUE, + BOOL bAlert = FALSE) : + CBaseList(pName, iItems) { + UNREFERENCED_PARAMETER(bAlert); + UNREFERENCED_PARAMETER(bLock); + }; + CGenericList(__in_opt LPCTSTR pName) : + CBaseList(pName) { + }; + + __out_opt POSITION GetHeadPosition() const { return (POSITION)m_pFirst; } + __out_opt POSITION GetTailPosition() const { return (POSITION)m_pLast; } + int GetCount() const { return m_Count; } + + __out OBJECT *GetNext(__inout POSITION& rp) const { return (OBJECT *) GetNextI(rp); } + + __out_opt OBJECT *Get(__in_opt POSITION p) const { return (OBJECT *) GetI(p); } + __out OBJECT *GetValid(__in POSITION p) const { return (OBJECT *) GetValidI(p); } + __out_opt OBJECT *GetHead() const { return Get(GetHeadPosition()); } + + __out_opt OBJECT *RemoveHead() { return (OBJECT *) RemoveHeadI(); } + + __out_opt OBJECT *RemoveTail() { return (OBJECT *) RemoveTailI(); } + + __out_opt OBJECT *Remove(__in_opt POSITION p) { return (OBJECT *) RemoveI(p); } + __out_opt POSITION AddBefore(__in_opt POSITION p, __in OBJECT * pObj) { return AddBeforeI(p, pObj); } + __out_opt POSITION AddAfter(__in_opt POSITION p, __in OBJECT * pObj) { return AddAfterI(p, pObj); } + __out_opt POSITION AddHead(__in OBJECT * pObj) { return AddHeadI(pObj); } + __out_opt POSITION AddTail(__in OBJECT * pObj) { return AddTailI(pObj); } + BOOL AddTail(__in CGenericList<OBJECT> *pList) + { return CBaseList::AddTail((CBaseList *) pList); } + BOOL AddHead(__in CGenericList<OBJECT> *pList) + { return CBaseList::AddHead((CBaseList *) pList); } + BOOL AddAfter(__in_opt POSITION p, __in CGenericList<OBJECT> *pList) + { return CBaseList::AddAfter(p, (CBaseList *) pList); }; + BOOL AddBefore(__in_opt POSITION p, __in CGenericList<OBJECT> *pList) + { return CBaseList::AddBefore(p, (CBaseList *) pList); }; + __out_opt POSITION Find( __in OBJECT * pObj) const { return FindI(pObj); } +}; // end of class declaration + + + +/* These define the standard list types */ + +typedef CGenericList<CBaseObject> CBaseObjectList; +typedef CGenericList<IUnknown> CBaseInterfaceList; + +#endif /* __WXLIST__ */ + diff --git a/Src/Plugins/Input/in_dshow/base/wxutil.cpp b/Src/Plugins/Input/in_dshow/base/wxutil.cpp new file mode 100644 index 00000000..7d48b9b5 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/wxutil.cpp @@ -0,0 +1,769 @@ +//------------------------------------------------------------------------------ +// File: WXUtil.cpp +// +// Desc: DirectShow base classes - implements helper classes for building +// multimedia filters. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#include <streams.h> +#define STRSAFE_NO_DEPRECATE +#include <strsafe.h> + + +// --- CAMEvent ----------------------- +CAMEvent::CAMEvent(BOOL fManualReset, __inout_opt HRESULT *phr) +{ + m_hEvent = CreateEvent(NULL, fManualReset, FALSE, NULL); + if (NULL == m_hEvent) { + if (NULL != phr && SUCCEEDED(*phr)) { + *phr = E_OUTOFMEMORY; + } + } +} + +CAMEvent::CAMEvent(__inout_opt HRESULT *phr) +{ + m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (NULL == m_hEvent) { + if (NULL != phr && SUCCEEDED(*phr)) { + *phr = E_OUTOFMEMORY; + } + } +} + +CAMEvent::~CAMEvent() +{ + if (m_hEvent) { + EXECUTE_ASSERT(CloseHandle(m_hEvent)); + } +} + + +// --- CAMMsgEvent ----------------------- +// One routine. The rest is handled in CAMEvent + +CAMMsgEvent::CAMMsgEvent(__inout_opt HRESULT *phr) : CAMEvent(FALSE, phr) +{ +} + +BOOL CAMMsgEvent::WaitMsg(DWORD dwTimeout) +{ + // wait for the event to be signalled, or for the + // timeout (in MS) to expire. allow SENT messages + // to be processed while we wait + DWORD dwWait; + DWORD dwStartTime; + + // set the waiting period. + DWORD dwWaitTime = dwTimeout; + + // the timeout will eventually run down as we iterate + // processing messages. grab the start time so that + // we can calculate elapsed times. + if (dwWaitTime != INFINITE) { + dwStartTime = timeGetTime(); + } + + do { + dwWait = MsgWaitForMultipleObjects(1,&m_hEvent,FALSE, dwWaitTime, QS_SENDMESSAGE); + if (dwWait == WAIT_OBJECT_0 + 1) { + MSG Message; + PeekMessage(&Message,NULL,0,0,PM_NOREMOVE); + + // If we have an explicit length of time to wait calculate + // the next wake up point - which might be now. + // If dwTimeout is INFINITE, it stays INFINITE + if (dwWaitTime != INFINITE) { + + DWORD dwElapsed = timeGetTime()-dwStartTime; + + dwWaitTime = + (dwElapsed >= dwTimeout) + ? 0 // wake up with WAIT_TIMEOUT + : dwTimeout-dwElapsed; + } + } + } while (dwWait == WAIT_OBJECT_0 + 1); + + // return TRUE if we woke on the event handle, + // FALSE if we timed out. + return (dwWait == WAIT_OBJECT_0); +} + +// --- CAMThread ---------------------- + + +CAMThread::CAMThread(__inout_opt HRESULT *phr) + : m_EventSend(TRUE, phr), // must be manual-reset for CheckRequest() + m_EventComplete(FALSE, phr) +{ + m_hThread = NULL; +} + +CAMThread::~CAMThread() { + Close(); +} + + +// when the thread starts, it calls this function. We unwrap the 'this' +//pointer and call ThreadProc. +DWORD WINAPI +CAMThread::InitialThreadProc(__inout LPVOID pv) +{ + HRESULT hrCoInit = CAMThread::CoInitializeHelper(); + if(FAILED(hrCoInit)) { + DbgLog((LOG_ERROR, 1, TEXT("CoInitializeEx failed."))); + } + + CAMThread * pThread = (CAMThread *) pv; + + HRESULT hr = pThread->ThreadProc(); + + if(SUCCEEDED(hrCoInit)) { + CoUninitialize(); + } + + return hr; +} + +BOOL +CAMThread::Create() +{ + DWORD threadid; + + CAutoLock lock(&m_AccessLock); + + if (ThreadExists()) { + return FALSE; + } + + m_hThread = CreateThread( + NULL, + 0, + CAMThread::InitialThreadProc, + this, + 0, + &threadid); + + if (!m_hThread) { + return FALSE; + } + + return TRUE; +} + +DWORD +CAMThread::CallWorker(DWORD dwParam) +{ + // lock access to the worker thread for scope of this object + CAutoLock lock(&m_AccessLock); + + if (!ThreadExists()) { + return (DWORD) E_FAIL; + } + + // set the parameter + m_dwParam = dwParam; + + // signal the worker thread + m_EventSend.Set(); + + // wait for the completion to be signalled + m_EventComplete.Wait(); + + // done - this is the thread's return value + return m_dwReturnVal; +} + +// Wait for a request from the client +DWORD +CAMThread::GetRequest() +{ + m_EventSend.Wait(); + return m_dwParam; +} + +// is there a request? +BOOL +CAMThread::CheckRequest(__out_opt DWORD * pParam) +{ + if (!m_EventSend.Check()) { + return FALSE; + } else { + if (pParam) { + *pParam = m_dwParam; + } + return TRUE; + } +} + +// reply to the request +void +CAMThread::Reply(DWORD dw) +{ + m_dwReturnVal = dw; + + // The request is now complete so CheckRequest should fail from + // now on + // + // This event should be reset BEFORE we signal the client or + // the client may Set it before we reset it and we'll then + // reset it (!) + + m_EventSend.Reset(); + + // Tell the client we're finished + + m_EventComplete.Set(); +} + +HRESULT CAMThread::CoInitializeHelper() +{ + // call CoInitializeEx and tell OLE not to create a window (this + // thread probably won't dispatch messages and will hang on + // broadcast msgs o/w). + // + // If CoInitEx is not available, threads that don't call CoCreate + // aren't affected. Threads that do will have to handle the + // failure. Perhaps we should fall back to CoInitialize and risk + // hanging? + // + + // older versions of ole32.dll don't have CoInitializeEx + + HRESULT hr = E_FAIL; + HINSTANCE hOle = GetModuleHandle(TEXT("ole32.dll")); + if(hOle) + { + typedef HRESULT (STDAPICALLTYPE *PCoInitializeEx)( + LPVOID pvReserved, DWORD dwCoInit); + PCoInitializeEx pCoInitializeEx = + (PCoInitializeEx)(GetProcAddress(hOle, "CoInitializeEx")); + if(pCoInitializeEx) + { + hr = (*pCoInitializeEx)(0, COINIT_DISABLE_OLE1DDE ); + } + } + else + { + // caller must load ole32.dll + DbgBreak("couldn't locate ole32.dll"); + } + + return hr; +} + + +// destructor for CMsgThread - cleans up any messages left in the +// queue when the thread exited +CMsgThread::~CMsgThread() +{ + if (m_hThread != NULL) { + WaitForSingleObject(m_hThread, INFINITE); + EXECUTE_ASSERT(CloseHandle(m_hThread)); + } + + POSITION pos = m_ThreadQueue.GetHeadPosition(); + while (pos) { + CMsg * pMsg = m_ThreadQueue.GetNext(pos); + delete pMsg; + } + m_ThreadQueue.RemoveAll(); + + if (m_hSem != NULL) { + EXECUTE_ASSERT(CloseHandle(m_hSem)); + } +} + +BOOL +CMsgThread::CreateThread( + ) +{ + m_hSem = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL); + if (m_hSem == NULL) { + return FALSE; + } + + m_hThread = ::CreateThread(NULL, 0, DefaultThreadProc, + (LPVOID)this, 0, &m_ThreadId); + return m_hThread != NULL; +} + + +// This is the threads message pump. Here we get and dispatch messages to +// clients thread proc until the client refuses to process a message. +// The client returns a non-zero value to stop the message pump, this +// value becomes the threads exit code. + +DWORD WINAPI +CMsgThread::DefaultThreadProc( + __inout LPVOID lpParam + ) +{ + CMsgThread *lpThis = (CMsgThread *)lpParam; + CMsg msg; + LRESULT lResult; + + // !!! + CoInitialize(NULL); + + // allow a derived class to handle thread startup + lpThis->OnThreadInit(); + + do { + lpThis->GetThreadMsg(&msg); + lResult = lpThis->ThreadMessageProc(msg.uMsg,msg.dwFlags, + msg.lpParam, msg.pEvent); + } while (lResult == 0L); + + // !!! + CoUninitialize(); + + return (DWORD)lResult; +} + + +// Block until the next message is placed on the list m_ThreadQueue. +// copies the message to the message pointed to by *pmsg +void +CMsgThread::GetThreadMsg(__out CMsg *msg) +{ + CMsg * pmsg = NULL; + + // keep trying until a message appears + while (TRUE) { + { + CAutoLock lck(&m_Lock); + pmsg = m_ThreadQueue.RemoveHead(); + if (pmsg == NULL) { + m_lWaiting++; + } else { + break; + } + } + // the semaphore will be signalled when it is non-empty + WaitForSingleObject(m_hSem, INFINITE); + } + // copy fields to caller's CMsg + *msg = *pmsg; + + // this CMsg was allocated by the 'new' in PutThreadMsg + delete pmsg; + +} + +// Helper function - convert int to WSTR +void WINAPI IntToWstr(int i, __out_ecount(12) LPWSTR wstr) +{ +#ifdef UNICODE + if (FAILED(StringCchPrintf(wstr, 12, L"%d", i))) { + wstr[0] = 0; + } +#else + TCHAR temp[12]; + if (FAILED(StringCchPrintf(temp, NUMELMS(temp), "%d", i))) { + wstr[0] = 0; + } else { + MultiByteToWideChar(CP_ACP, 0, temp, -1, wstr, 12); + } +#endif +} // IntToWstr + + +#define MEMORY_ALIGNMENT 4 +#define MEMORY_ALIGNMENT_LOG2 2 +#define MEMORY_ALIGNMENT_MASK MEMORY_ALIGNMENT - 1 + +void * __stdcall memmoveInternal(void * dst, const void * src, size_t count) +{ + void * ret = dst; + +#ifdef _X86_ + if (dst <= src || (char *)dst >= ((char *)src + count)) { + + /* + * Non-Overlapping Buffers + * copy from lower addresses to higher addresses + */ + _asm { + mov esi,src + mov edi,dst + mov ecx,count + cld + mov edx,ecx + and edx,MEMORY_ALIGNMENT_MASK + shr ecx,MEMORY_ALIGNMENT_LOG2 + rep movsd + or ecx,edx + jz memmove_done + rep movsb +memmove_done: + } + } + else { + + /* + * Overlapping Buffers + * copy from higher addresses to lower addresses + */ + _asm { + mov esi,src + mov edi,dst + mov ecx,count + std + add esi,ecx + add edi,ecx + dec esi + dec edi + rep movsb + cld + } + } +#else + MoveMemory(dst, src, count); +#endif + + return ret; +} + +HRESULT AMSafeMemMoveOffset( + __in_bcount(dst_size) void * dst, + __in size_t dst_size, + __in DWORD cb_dst_offset, + __in_bcount(src_size) const void * src, + __in size_t src_size, + __in DWORD cb_src_offset, + __in size_t count) +{ + // prevent read overruns + if( count + cb_src_offset < count || // prevent integer overflow + count + cb_src_offset > src_size) // prevent read overrun + { + return E_INVALIDARG; + } + + // prevent write overruns + if( count + cb_dst_offset < count || // prevent integer overflow + count + cb_dst_offset > dst_size) // prevent write overrun + { + return E_INVALIDARG; + } + + memmoveInternal( (BYTE *)dst+cb_dst_offset, (BYTE *)src+cb_src_offset, count); + return S_OK; +} + + +#ifdef DEBUG +/******************************Public*Routine******************************\ +* Debug CCritSec helpers +* +* We provide debug versions of the Constructor, destructor, Lock and Unlock +* routines. The debug code tracks who owns each critical section by +* maintaining a depth count. +* +* History: +* +\**************************************************************************/ + +CCritSec::CCritSec() +{ + InitializeCriticalSection(&m_CritSec); + m_currentOwner = m_lockCount = 0; + m_fTrace = FALSE; +} + +CCritSec::~CCritSec() +{ + DeleteCriticalSection(&m_CritSec); +} + +void CCritSec::Lock() +{ + UINT tracelevel=3; + DWORD us = GetCurrentThreadId(); + DWORD currentOwner = m_currentOwner; + if (currentOwner && (currentOwner != us)) { + // already owned, but not by us + if (m_fTrace) { + DbgLog((LOG_LOCKING, 2, TEXT("Thread %d about to wait for lock %x owned by %d"), + GetCurrentThreadId(), &m_CritSec, currentOwner)); + tracelevel=2; + // if we saw the message about waiting for the critical + // section we ensure we see the message when we get the + // critical section + } + } + EnterCriticalSection(&m_CritSec); + if (0 == m_lockCount++) { + // we now own it for the first time. Set owner information + m_currentOwner = us; + + if (m_fTrace) { + DbgLog((LOG_LOCKING, tracelevel, TEXT("Thread %d now owns lock %x"), m_currentOwner, &m_CritSec)); + } + } +} + +void CCritSec::Unlock() { + if (0 == --m_lockCount) { + // about to be unowned + if (m_fTrace) { + DbgLog((LOG_LOCKING, 3, TEXT("Thread %d releasing lock %x"), m_currentOwner, &m_CritSec)); + } + + m_currentOwner = 0; + } + LeaveCriticalSection(&m_CritSec); +} + +void WINAPI DbgLockTrace(CCritSec * pcCrit, BOOL fTrace) +{ + pcCrit->m_fTrace = fTrace; +} + +BOOL WINAPI CritCheckIn(CCritSec * pcCrit) +{ + return (GetCurrentThreadId() == pcCrit->m_currentOwner); +} + +BOOL WINAPI CritCheckIn(const CCritSec * pcCrit) +{ + return (GetCurrentThreadId() == pcCrit->m_currentOwner); +} + +BOOL WINAPI CritCheckOut(CCritSec * pcCrit) +{ + return (GetCurrentThreadId() != pcCrit->m_currentOwner); +} + +BOOL WINAPI CritCheckOut(const CCritSec * pcCrit) +{ + return (GetCurrentThreadId() != pcCrit->m_currentOwner); +} +#endif + + +STDAPI WriteBSTR(__deref_out BSTR *pstrDest, LPCWSTR szSrc) +{ + *pstrDest = SysAllocString( szSrc ); + if( !(*pstrDest) ) return E_OUTOFMEMORY; + return NOERROR; +} + + +STDAPI FreeBSTR(__deref_in BSTR* pstr) +{ + if( (PVOID)*pstr == NULL ) return S_FALSE; + SysFreeString( *pstr ); + return NOERROR; +} + + +// Return a wide string - allocating memory for it +// Returns: +// S_OK - no error +// E_POINTER - ppszReturn == NULL +// E_OUTOFMEMORY - can't allocate memory for returned string +STDAPI AMGetWideString(LPCWSTR psz, __deref_out LPWSTR *ppszReturn) +{ + CheckPointer(ppszReturn, E_POINTER); + ValidateReadWritePtr(ppszReturn, sizeof(LPWSTR)); + *ppszReturn = NULL; + size_t nameLen; + HRESULT hr = StringCbLengthW(psz, 100000, &nameLen); + if (FAILED(hr)) { + return hr; + } + *ppszReturn = (LPWSTR)CoTaskMemAlloc(nameLen + sizeof(WCHAR)); + if (*ppszReturn == NULL) { + return E_OUTOFMEMORY; + } + CopyMemory(*ppszReturn, psz, nameLen + sizeof(WCHAR)); + return NOERROR; +} + +// Waits for the HANDLE hObject. While waiting messages sent +// to windows on our thread by SendMessage will be processed. +// Using this function to do waits and mutual exclusion +// avoids some deadlocks in objects with windows. +// Return codes are the same as for WaitForSingleObject +DWORD WINAPI WaitDispatchingMessages( + HANDLE hObject, + DWORD dwWait, + HWND hwnd, + UINT uMsg, + HANDLE hEvent) +{ + BOOL bPeeked = FALSE; + DWORD dwResult; + DWORD dwStart; + DWORD dwThreadPriority; + + static UINT uMsgId = 0; + + HANDLE hObjects[2] = { hObject, hEvent }; + if (dwWait != INFINITE && dwWait != 0) { + dwStart = GetTickCount(); + } + for (; ; ) { + DWORD nCount = NULL != hEvent ? 2 : 1; + + // Minimize the chance of actually dispatching any messages + // by seeing if we can lock immediately. + dwResult = WaitForMultipleObjects(nCount, hObjects, FALSE, 0); + if (dwResult < WAIT_OBJECT_0 + nCount) { + break; + } + + DWORD dwTimeOut = dwWait; + if (dwTimeOut > 10) { + dwTimeOut = 10; + } + dwResult = MsgWaitForMultipleObjects( + nCount, + hObjects, + FALSE, + dwTimeOut, + hwnd == NULL ? QS_SENDMESSAGE : + QS_SENDMESSAGE + QS_POSTMESSAGE); + if (dwResult == WAIT_OBJECT_0 + nCount || + dwResult == WAIT_TIMEOUT && dwTimeOut != dwWait) { + MSG msg; + if (hwnd != NULL) { + while (PeekMessage(&msg, hwnd, uMsg, uMsg, PM_REMOVE)) { + DispatchMessage(&msg); + } + } + // Do this anyway - the previous peek doesn't flush out the + // messages + PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE); + + if (dwWait != INFINITE && dwWait != 0) { + DWORD dwNow = GetTickCount(); + + // Working with differences handles wrap-around + DWORD dwDiff = dwNow - dwStart; + if (dwDiff > dwWait) { + dwWait = 0; + } else { + dwWait -= dwDiff; + } + dwStart = dwNow; + } + if (!bPeeked) { + // Raise our priority to prevent our message queue + // building up + dwThreadPriority = GetThreadPriority(GetCurrentThread()); + if (dwThreadPriority < THREAD_PRIORITY_HIGHEST) { + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); + } + bPeeked = TRUE; + } + } else { + break; + } + } + if (bPeeked) { + SetThreadPriority(GetCurrentThread(), dwThreadPriority); + if (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) & QS_POSTMESSAGE) { + if (uMsgId == 0) { + uMsgId = RegisterWindowMessage(TEXT("AMUnblock")); + } + if (uMsgId != 0) { + MSG msg; + // Remove old ones + while (PeekMessage(&msg, (HWND)-1, uMsgId, uMsgId, PM_REMOVE)) { + } + } + PostThreadMessage(GetCurrentThreadId(), uMsgId, 0, 0); + } + } + return dwResult; +} + +HRESULT AmGetLastErrorToHResult() +{ + DWORD dwLastError = GetLastError(); + if(dwLastError != 0) + { + return HRESULT_FROM_WIN32(dwLastError); + } + else + { + return E_FAIL; + } +} + +IUnknown* QzAtlComPtrAssign(__deref_inout_opt IUnknown** pp, __in_opt IUnknown* lp) +{ + if (lp != NULL) + lp->AddRef(); + if (*pp) + (*pp)->Release(); + *pp = lp; + return lp; +} + +/****************************************************************************** + +CompatibleTimeSetEvent + + CompatibleTimeSetEvent() sets the TIME_KILL_SYNCHRONOUS flag before calling +timeSetEvent() if the current operating system supports it. TIME_KILL_SYNCHRONOUS +is supported on Windows XP and later operating systems. + +Parameters: +- The same parameters as timeSetEvent(). See timeSetEvent()'s documentation in +the Platform SDK for more information. + +Return Value: +- The same return value as timeSetEvent(). See timeSetEvent()'s documentation in +the Platform SDK for more information. + +******************************************************************************/ +MMRESULT CompatibleTimeSetEvent( UINT uDelay, UINT uResolution, __in LPTIMECALLBACK lpTimeProc, DWORD_PTR dwUser, UINT fuEvent ) +{ + #if WINVER >= 0x0501 + { + static bool fCheckedVersion = false; + static bool fTimeKillSynchronousFlagAvailable = false; + + if( !fCheckedVersion ) { + fTimeKillSynchronousFlagAvailable = TimeKillSynchronousFlagAvailable(); + fCheckedVersion = true; + } + + if( fTimeKillSynchronousFlagAvailable ) { + fuEvent = fuEvent | TIME_KILL_SYNCHRONOUS; + } + } + #endif // WINVER >= 0x0501 + + return timeSetEvent( uDelay, uResolution, lpTimeProc, dwUser, fuEvent ); +} + +bool TimeKillSynchronousFlagAvailable( void ) +{ + OSVERSIONINFO osverinfo; + + osverinfo.dwOSVersionInfoSize = sizeof(osverinfo); + + if( GetVersionEx( &osverinfo ) ) { + + // Windows XP's major version is 5 and its' minor version is 1. + // timeSetEvent() started supporting the TIME_KILL_SYNCHRONOUS flag + // in Windows XP. + if( (osverinfo.dwMajorVersion > 5) || + ( (osverinfo.dwMajorVersion == 5) && (osverinfo.dwMinorVersion >= 1) ) ) { + return true; + } + } + + return false; +} + + diff --git a/Src/Plugins/Input/in_dshow/base/wxutil.h b/Src/Plugins/Input/in_dshow/base/wxutil.h new file mode 100644 index 00000000..3bfc2d29 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/base/wxutil.h @@ -0,0 +1,532 @@ +//------------------------------------------------------------------------------ +// File: WXUtil.h +// +// Desc: DirectShow base classes - defines helper classes and functions for +// building multimedia filters. +// +// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------ + + +#ifndef __WXUTIL__ +#define __WXUTIL__ + +// eliminate spurious "statement has no effect" warnings. +#pragma warning(disable: 4705) + +// wrapper for whatever critical section we have +class CCritSec { + + // make copy constructor and assignment operator inaccessible + + CCritSec(const CCritSec &refCritSec); + CCritSec &operator=(const CCritSec &refCritSec); + + CRITICAL_SECTION m_CritSec; + +#ifdef DEBUG +public: + DWORD m_currentOwner; + DWORD m_lockCount; + BOOL m_fTrace; // Trace this one +public: + CCritSec(); + ~CCritSec(); + void Lock(); + void Unlock(); +#else + +public: + CCritSec() { + InitializeCriticalSection(&m_CritSec); + }; + + ~CCritSec() { + DeleteCriticalSection(&m_CritSec); + }; + + void Lock() { + EnterCriticalSection(&m_CritSec); + }; + + void Unlock() { + LeaveCriticalSection(&m_CritSec); + }; +#endif +}; + +// +// To make deadlocks easier to track it is useful to insert in the +// code an assertion that says whether we own a critical section or +// not. We make the routines that do the checking globals to avoid +// having different numbers of member functions in the debug and +// retail class implementations of CCritSec. In addition we provide +// a routine that allows usage of specific critical sections to be +// traced. This is NOT on by default - there are far too many. +// + +#ifdef DEBUG + BOOL WINAPI CritCheckIn(CCritSec * pcCrit); + BOOL WINAPI CritCheckIn(const CCritSec * pcCrit); + BOOL WINAPI CritCheckOut(CCritSec * pcCrit); + BOOL WINAPI CritCheckOut(const CCritSec * pcCrit); + void WINAPI DbgLockTrace(CCritSec * pcCrit, BOOL fTrace); +#else + #define CritCheckIn(x) TRUE + #define CritCheckOut(x) TRUE + #define DbgLockTrace(pc, fT) +#endif + + +// locks a critical section, and unlocks it automatically +// when the lock goes out of scope +class CAutoLock { + + // make copy constructor and assignment operator inaccessible + + CAutoLock(const CAutoLock &refAutoLock); + CAutoLock &operator=(const CAutoLock &refAutoLock); + +protected: + CCritSec * m_pLock; + +public: + CAutoLock(CCritSec * plock) + { + m_pLock = plock; + m_pLock->Lock(); + }; + + ~CAutoLock() { + m_pLock->Unlock(); + }; +}; + + + +// wrapper for event objects +class CAMEvent +{ + + // make copy constructor and assignment operator inaccessible + + CAMEvent(const CAMEvent &refEvent); + CAMEvent &operator=(const CAMEvent &refEvent); + +protected: + HANDLE m_hEvent; +public: + CAMEvent(BOOL fManualReset = FALSE, __inout_opt HRESULT *phr = NULL); + CAMEvent(__inout_opt HRESULT *phr); + ~CAMEvent(); + + // Cast to HANDLE - we don't support this as an lvalue + operator HANDLE () const { return m_hEvent; }; + + void Set() {EXECUTE_ASSERT(SetEvent(m_hEvent));}; + BOOL Wait(DWORD dwTimeout = INFINITE) { + return (WaitForSingleObject(m_hEvent, dwTimeout) == WAIT_OBJECT_0); + }; + void Reset() { ResetEvent(m_hEvent); }; + BOOL Check() { return Wait(0); }; +}; + + +// wrapper for event objects that do message processing +// This adds ONE method to the CAMEvent object to allow sent +// messages to be processed while waiting + +class CAMMsgEvent : public CAMEvent +{ + +public: + + CAMMsgEvent(__inout_opt HRESULT *phr = NULL); + + // Allow SEND messages to be processed while waiting + BOOL WaitMsg(DWORD dwTimeout = INFINITE); +}; + +// old name supported for the time being +#define CTimeoutEvent CAMEvent + +// support for a worker thread + +#ifdef AM_NOVTABLE +// simple thread class supports creation of worker thread, synchronization +// and communication. Can be derived to simplify parameter passing +class AM_NOVTABLE CAMThread { + + // make copy constructor and assignment operator inaccessible + + CAMThread(const CAMThread &refThread); + CAMThread &operator=(const CAMThread &refThread); + + CAMEvent m_EventSend; + CAMEvent m_EventComplete; + + DWORD m_dwParam; + DWORD m_dwReturnVal; + +protected: + HANDLE m_hThread; + + // thread will run this function on startup + // must be supplied by derived class + virtual DWORD ThreadProc() = 0; + +public: + CAMThread(__inout_opt HRESULT *phr = NULL); + virtual ~CAMThread(); + + CCritSec m_AccessLock; // locks access by client threads + CCritSec m_WorkerLock; // locks access to shared objects + + // thread initially runs this. param is actually 'this'. function + // just gets this and calls ThreadProc + static DWORD WINAPI InitialThreadProc(__inout LPVOID pv); + + // start thread running - error if already running + BOOL Create(); + + // signal the thread, and block for a response + // + DWORD CallWorker(DWORD); + + // accessor thread calls this when done with thread (having told thread + // to exit) + void Close() { + + // Disable warning: Conversion from LONG to PVOID of greater size +#pragma warning(push) +#pragma warning(disable: 4312) + HANDLE hThread = (HANDLE)InterlockedExchangePointer(&m_hThread, 0); +#pragma warning(pop) + + if (hThread) { + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + } + }; + + // ThreadExists + // Return TRUE if the thread exists. FALSE otherwise + BOOL ThreadExists(void) const + { + if (m_hThread == 0) { + return FALSE; + } else { + return TRUE; + } + } + + // wait for the next request + DWORD GetRequest(); + + // is there a request? + BOOL CheckRequest(__out_opt DWORD * pParam); + + // reply to the request + void Reply(DWORD); + + // If you want to do WaitForMultipleObjects you'll need to include + // this handle in your wait list or you won't be responsive + HANDLE GetRequestHandle() const { return m_EventSend; }; + + // Find out what the request was + DWORD GetRequestParam() const { return m_dwParam; }; + + // call CoInitializeEx (COINIT_DISABLE_OLE1DDE) if + // available. S_FALSE means it's not available. + static HRESULT CoInitializeHelper(); +}; +#endif // AM_NOVTABLE + + +// CQueue +// +// Implements a simple Queue ADT. The queue contains a finite number of +// objects, access to which is controlled by a semaphore. The semaphore +// is created with an initial count (N). Each time an object is added +// a call to WaitForSingleObject is made on the semaphore's handle. When +// this function returns a slot has been reserved in the queue for the new +// object. If no slots are available the function blocks until one becomes +// available. Each time an object is removed from the queue ReleaseSemaphore +// is called on the semaphore's handle, thus freeing a slot in the queue. +// If no objects are present in the queue the function blocks until an +// object has been added. + +#define DEFAULT_QUEUESIZE 2 + +template <class T> class CQueue { +private: + HANDLE hSemPut; // Semaphore controlling queue "putting" + HANDLE hSemGet; // Semaphore controlling queue "getting" + CRITICAL_SECTION CritSect; // Thread seriallization + int nMax; // Max objects allowed in queue + int iNextPut; // Array index of next "PutMsg" + int iNextGet; // Array index of next "GetMsg" + T *QueueObjects; // Array of objects (ptr's to void) + + void Initialize(int n) { + iNextPut = iNextGet = 0; + nMax = n; + InitializeCriticalSection(&CritSect); + hSemPut = CreateSemaphore(NULL, n, n, NULL); + hSemGet = CreateSemaphore(NULL, 0, n, NULL); + QueueObjects = new T[n]; + } + + +public: + CQueue(int n) { + Initialize(n); + } + + CQueue() { + Initialize(DEFAULT_QUEUESIZE); + } + + ~CQueue() { + delete [] QueueObjects; + DeleteCriticalSection(&CritSect); + CloseHandle(hSemPut); + CloseHandle(hSemGet); + } + + T GetQueueObject() { + int iSlot; + T Object; + LONG lPrevious; + + // Wait for someone to put something on our queue, returns straight + // away is there is already an object on the queue. + // + WaitForSingleObject(hSemGet, INFINITE); + + EnterCriticalSection(&CritSect); + iSlot = iNextGet++ % nMax; + Object = QueueObjects[iSlot]; + LeaveCriticalSection(&CritSect); + + // Release anyone waiting to put an object onto our queue as there + // is now space available in the queue. + // + ReleaseSemaphore(hSemPut, 1L, &lPrevious); + return Object; + } + + void PutQueueObject(T Object) { + int iSlot; + LONG lPrevious; + + // Wait for someone to get something from our queue, returns straight + // away is there is already an empty slot on the queue. + // + WaitForSingleObject(hSemPut, INFINITE); + + EnterCriticalSection(&CritSect); + iSlot = iNextPut++ % nMax; + QueueObjects[iSlot] = Object; + LeaveCriticalSection(&CritSect); + + // Release anyone waiting to remove an object from our queue as there + // is now an object available to be removed. + // + ReleaseSemaphore(hSemGet, 1L, &lPrevious); + } +}; + +// Ensures that memory is not read past the length source buffer +// and that memory is not written past the length of the dst buffer +// dst - buffer to copy to +// dst_size - total size of destination buffer +// cb_dst_offset - offset, first byte copied to dst+cb_dst_offset +// src - buffer to copy from +// src_size - total size of source buffer +// cb_src_offset - offset, first byte copied from src+cb_src_offset +// count - number of bytes to copy +// +// Returns: +// S_OK - no error +// E_INVALIDARG - values passed would lead to overrun +HRESULT AMSafeMemMoveOffset( + __in_bcount(dst_size) void * dst, + __in size_t dst_size, + __in DWORD cb_dst_offset, + __in_bcount(src_size) const void * src, + __in size_t src_size, + __in DWORD cb_src_offset, + __in size_t count); + +extern "C" +void * __stdcall memmoveInternal(void *, const void *, size_t); + +inline void * __cdecl memchrInternal(const void *buf, int chr, size_t cnt) +{ +#ifdef _X86_ + void *pRet = NULL; + + _asm { + cld // make sure we get the direction right + mov ecx, cnt // num of bytes to scan + mov edi, buf // pointer byte stream + mov eax, chr // byte to scan for + repne scasb // look for the byte in the byte stream + jnz exit_memchr // Z flag set if byte found + dec edi // scasb always increments edi even when it + // finds the required byte + mov pRet, edi +exit_memchr: + } + return pRet; + +#else + while ( cnt && (*(unsigned char *)buf != (unsigned char)chr) ) { + buf = (unsigned char *)buf + 1; + cnt--; + } + + return(cnt ? (void *)buf : NULL); +#endif +} + +void WINAPI IntToWstr(int i, __out_ecount(12) LPWSTR wstr); + +#define WstrToInt(sz) _wtoi(sz) +#define atoiW(sz) _wtoi(sz) +#define atoiA(sz) atoi(sz) + +// These are available to help managing bitmap VIDEOINFOHEADER media structures + +extern const DWORD bits555[3]; +extern const DWORD bits565[3]; +extern const DWORD bits888[3]; + +// These help convert between VIDEOINFOHEADER and BITMAPINFO structures + +STDAPI_(const GUID) GetTrueColorType(const BITMAPINFOHEADER *pbmiHeader); +STDAPI_(const GUID) GetBitmapSubtype(const BITMAPINFOHEADER *pbmiHeader); +STDAPI_(WORD) GetBitCount(const GUID *pSubtype); + +// strmbase.lib implements this for compatibility with people who +// managed to link to this directly. we don't want to advertise it. +// +// STDAPI_(/* T */ CHAR *) GetSubtypeName(const GUID *pSubtype); + +STDAPI_(CHAR *) GetSubtypeNameA(const GUID *pSubtype); +STDAPI_(WCHAR *) GetSubtypeNameW(const GUID *pSubtype); + +#ifdef UNICODE +#define GetSubtypeName GetSubtypeNameW +#else +#define GetSubtypeName GetSubtypeNameA +#endif + +STDAPI_(LONG) GetBitmapFormatSize(const BITMAPINFOHEADER *pHeader); +STDAPI_(DWORD) GetBitmapSize(const BITMAPINFOHEADER *pHeader); + +#ifdef __AMVIDEO__ +STDAPI_(BOOL) ContainsPalette(const VIDEOINFOHEADER *pVideoInfo); +STDAPI_(const RGBQUAD *) GetBitmapPalette(const VIDEOINFOHEADER *pVideoInfo); +#endif // __AMVIDEO__ + + +// Compares two interfaces and returns TRUE if they are on the same object +BOOL WINAPI IsEqualObject(IUnknown *pFirst, IUnknown *pSecond); + +// This is for comparing pins +#define EqualPins(pPin1, pPin2) IsEqualObject(pPin1, pPin2) + + +// Arithmetic helper functions + +// Compute (a * b + rnd) / c +LONGLONG WINAPI llMulDiv(LONGLONG a, LONGLONG b, LONGLONG c, LONGLONG rnd); +LONGLONG WINAPI Int64x32Div32(LONGLONG a, LONG b, LONG c, LONG rnd); + + +// Avoids us dyna-linking to SysAllocString to copy BSTR strings +STDAPI WriteBSTR(__deref_out BSTR * pstrDest, LPCWSTR szSrc); +STDAPI FreeBSTR(__deref_in BSTR* pstr); + +// Return a wide string - allocating memory for it +// Returns: +// S_OK - no error +// E_POINTER - ppszReturn == NULL +// E_OUTOFMEMORY - can't allocate memory for returned string +STDAPI AMGetWideString(LPCWSTR pszString, __deref_out LPWSTR *ppszReturn); + +// Special wait for objects owning windows +DWORD WINAPI WaitDispatchingMessages( + HANDLE hObject, + DWORD dwWait, + HWND hwnd = NULL, + UINT uMsg = 0, + HANDLE hEvent = NULL); + +// HRESULT_FROM_WIN32 converts ERROR_SUCCESS to a success code, but in +// our use of HRESULT_FROM_WIN32, it typically means a function failed +// to call SetLastError(), and we still want a failure code. +// +#define AmHresultFromWin32(x) (MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, x)) + +// call GetLastError and return an HRESULT value that will fail the +// SUCCEEDED() macro. +HRESULT AmGetLastErrorToHResult(void); + +// duplicate of ATL's CComPtr to avoid linker conflicts. + +IUnknown* QzAtlComPtrAssign(__deref_inout_opt IUnknown** pp, __in_opt IUnknown* lp); + +template <class T> +class QzCComPtr +{ +public: + typedef T _PtrClass; + QzCComPtr() {p=NULL;} + QzCComPtr(T* lp) + { + if ((p = lp) != NULL) + p->AddRef(); + } + QzCComPtr(const QzCComPtr<T>& lp) + { + if ((p = lp.p) != NULL) + p->AddRef(); + } + ~QzCComPtr() {if (p) p->Release();} + void Release() {if (p) p->Release(); p=NULL;} + operator T*() {return (T*)p;} + T& operator*() {ASSERT(p!=NULL); return *p; } + //The assert on operator& usually indicates a bug. If this is really + //what is needed, however, take the address of the p member explicitly. + T** operator&() { ASSERT(p==NULL); return &p; } + T* operator->() { ASSERT(p!=NULL); return p; } + T* operator=(T* lp){return (T*)QzAtlComPtrAssign((IUnknown**)&p, lp);} + T* operator=(const QzCComPtr<T>& lp) + { + return (T*)QzAtlComPtrAssign((IUnknown**)&p, lp.p); + } +#if _MSC_VER>1020 + bool operator!(){return (p == NULL);} +#else + BOOL operator!(){return (p == NULL) ? TRUE : FALSE;} +#endif + T* p; +}; + +MMRESULT CompatibleTimeSetEvent( UINT uDelay, UINT uResolution, __in LPTIMECALLBACK lpTimeProc, DWORD_PTR dwUser, UINT fuEvent ); +bool TimeKillSynchronousFlagAvailable( void ); + +// Helper to replace lstrcpmi +__inline int lstrcmpiLocaleIndependentW(LPCWSTR lpsz1, LPCWSTR lpsz2) +{ + return CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, lpsz1, -1, lpsz2, -1) - CSTR_EQUAL; +} +__inline int lstrcmpiLocaleIndependentA(LPCSTR lpsz1, LPCSTR lpsz2) +{ + return CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE, lpsz1, -1, lpsz2, -1) - CSTR_EQUAL; +} + +#endif /* __WXUTIL__ */ diff --git a/Src/Plugins/Input/in_dshow/config.cpp b/Src/Plugins/Input/in_dshow/config.cpp new file mode 100644 index 00000000..451d9307 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/config.cpp @@ -0,0 +1,124 @@ +#include <windows.h> +#include "resource.h" +#include "../Agave/Language/api_language.h" +#include "main.h" +#include <strsafe.h> + +extern const char *INI_FILE; + +static char app_name[] = "in_dshow"; + +static char default_extlist[]="MPG;MPEG;M2V;AVI"; + +char config_extlist[129] = {0}; + +static int _r_i(char *name, int def) +{ + if (!_strnicmp(name,"config_",7)) name += 7; + return GetPrivateProfileIntA(app_name,name,def,INI_FILE); +} +#define RI(x) (( x ) = _r_i(#x,( x ))) +static void _w_i(char *name, int d) +{ + char str[120] = {0}; + StringCchPrintfA(str, 120, "%d",d); + if (!_strnicmp(name,"config_",7)) name += 7; + WritePrivateProfileStringA(app_name,name,str,INI_FILE); +} +#define WI(x) _w_i(#x,( x )) + +static void _r_s(char *name,char *data, int mlen) +{ + char buf[2048] = {0}; + lstrcpynA(buf,data, 2048); + if (!_strnicmp(name,"config_",7)) name += 7; + GetPrivateProfileStringA(app_name,name,buf,data,mlen,INI_FILE); +} +#define RS(x) (_r_s(#x,x,sizeof(x))) + +static void _w_s(char *name, char *data) +{ + if (!_strnicmp(name,"config_",7)) name += 7; + WritePrivateProfileStringA(app_name,name,data,INI_FILE); +} +#define WS(x) (_w_s(#x,x)) + +#define ISSEP(x) ((x) == ' ' || (x) == ';' || (x) == ',' || (x) == ':' || (x) == '.') +char *getfileextensions() +{ + static char list[512]; + char *op=list; + // char *g_fileassos="MP3;MP2;MP1\0MPEG Audio Files (*.MP3;*.MP2;*.MP1)\0"; + + char *p=config_extlist; + int s=0; + while (p && *p) + { + while (ISSEP(*p)) p++; + if (!*p) break; + if (s) *op++=';'; + s=1; + while (p && *p && !ISSEP(*p)) *op++=*p++; + } + *op++=0; + lstrcpynA(op,WASABI_API_LNGSTRING(IDS_VIDEO_FILES_OFD),512); + while (op && *op) op++; + p=config_extlist; + s=0; + while (p && *p) + { + while (ISSEP(*p)) p++; + if (!p || !*p) break; + if (s) *op++=';'; + s=1; + *op++='*'; + *op++='.'; + while (p && *p && !ISSEP(*p)) *op++=*p++; + } + *op++=')'; + *op++=0; + *op++=0; + return list; +} + +void config_read() +{ + lstrcpynA(config_extlist,default_extlist, 129); + RS(config_extlist); +} + +void config_write() +{ + WS(config_extlist); +} + +INT_PTR CALLBACK configProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch(uMsg) + { + case WM_INITDIALOG: + SetDlgItemTextA(hwndDlg,IDC_TYPES,config_extlist); + SendDlgItemMessage(hwndDlg,IDC_TYPES,EM_LIMITTEXT,128,0); + return TRUE; + case WM_COMMAND: + switch(LOWORD(wParam)) + { + case IDC_DEFAULTTYPES: + SetDlgItemTextA(hwndDlg,IDC_TYPES,default_extlist); + break; + case IDOK: + GetDlgItemTextA(hwndDlg,IDC_TYPES,config_extlist,128); + config_write(); + case IDCANCEL: + EndDialog(hwndDlg,0); + break; + } + break; + } + return FALSE; +} + +void doConfig(HINSTANCE hInstance, HWND hwndParent) +{ + WASABI_API_DIALOGBOXW(IDD_CONFIG,hwndParent,configProc); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_dshow/dshowrender.cpp b/Src/Plugins/Input/in_dshow/dshowrender.cpp new file mode 100644 index 00000000..5a2b81dc --- /dev/null +++ b/Src/Plugins/Input/in_dshow/dshowrender.cpp @@ -0,0 +1,583 @@ +// +// This file contains code that supports an alternate method playing dshow data... +// This will have dshow take control of rendering the data (audio or video). +// Used for mms streams +// +#ifdef WINAMPX + +#include <windows.h> +#include <math.h> + +#include "message.h" + +#include "../jnetlib/jnetlib.h" + + +#include "in2.h" +#include <AtlBase.h> +#include <streams.h> +#include <qedit.h> +#include <qnetwork.h> +#ifdef DEFGUID +#include <initguid.h> // declares DEFINE_GUID to declare an EXTERN_C const. +#endif + +#define IPC_GET_IVIDEOOUTPUT 500 + +// Externs + +extern HRESULT AddToRot(IUnknown *pUnkGraph, DWORD *pdwRegister); +extern void RemoveFromRot(DWORD pdwRegister); +extern bool ReportMissingCodec(char *fn); + +extern In_Module mod; // the output module (filled in near the bottom of this file) + +extern char lastfn[MAX_PATH]; // currently playing file (used for getting info on the current file) +extern char lastfn_status[256]; +extern int file_length; // file length, in bytes +extern int decode_pos_ms; // current decoding position, in milliseconds. +extern int paused; // are we paused? +extern volatile int seek_needed; // if != -1, it is the point that the decode thread should seek to, in ms. +extern int m_length; +extern int paused; // are we paused? +extern bool s_using_dsr; + +// Static Vars and Defines + +class IVideoOutput +{ +public: + virtual ~IVideoOutput() { } + virtual int open(int w, int h, int vflip, double aspectratio, unsigned int fmt)=0; + virtual void setcallback(LRESULT (*msgcallback)(void *token, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam), void *token) { } + virtual void close()=0; + virtual void draw(void *frame)=0; + virtual void drawSubtitle(/*SubsItem **/ void *item) { } + virtual void showStatusMsg(const char *text) { } + virtual int get_latency() { return 0; } + virtual void notifyBufferState(int bufferstate) { } /* 0-255*/ + + virtual int extended(int param1, int param2, int param3) { return 0; } // Dispatchable, eat this! +}; +static IVideoOutput * m_video_output; + +static CComPtr<IGraphBuilder> s_pGraphBuilder; +static CComPtr<IMediaEventEx> s_pMediaEventEx; +static CComPtr<IMediaControl> s_pMediaControl; +static CComPtr<IVideoWindow> s_pVideoWindow; +static CComPtr<IBasicAudio> s_pBasicAudio; +static CComPtr<IBasicVideo> s_pBasicVideo; +static CComPtr<IMediaSeeking> s_pMediaSeeking; + +static HWND s_dsr_notif_hwnd = NULL; +static HWND s_hVideoWnd = NULL; +static WNDPROC s_OriginalVideoWndProc = NULL; +static RECT s_parentRect; +static DWORD GraphEdit_dwRegister = 0; +static int s_buffering = 0; +static int s_bufferstat = 0; +static BOOL s_bAudioOnly; +static int s_setVolumeOnStart = -1; + + +// Forward Declarations +LRESULT CALLBACK dsr_TimerWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +LRESULT CALLBACK dsr_SubclassParentWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +void dsr_setvolume(int volume); +void dsr_handleNotifyEvents(); +void dsr_stop(); + +// Macros +#define BeginEnumFilters(pFilterGraph, pEnumFilters, pBaseFilter) \ +{CComPtr<IEnumFilters> pEnumFilters; \ + if(pFilterGraph && SUCCEEDED(pFilterGraph->EnumFilters(&pEnumFilters))) \ +{ \ + for(CComPtr<IBaseFilter> pBaseFilter; S_OK == pEnumFilters->Next(1, &pBaseFilter, 0); pBaseFilter = NULL) \ +{ \ + +#define EndEnumFilters }}} + +static void SendStatus(int status, int arg ) { + mod.fire_winampstatus( status, arg ); +} + + + + + +//------------------------------------------------------------------------------ +// Name: CheckVisibility() +// Desc: Set global values for presence of video window. +//------------------------------------------------------------------------------ +bool CheckVisibility(void) // returns false for failure +{ + HRESULT hr; + if ((!s_pVideoWindow) || (!s_pBasicVideo)) + { + s_bAudioOnly = TRUE; // Audio-only files have no video interfaces. + return TRUE; + } + s_bAudioOnly = FALSE; + + long lVisible; + hr = s_pVideoWindow->get_Visible(&lVisible); // If this is an audio-only clip, get_Visible() won't work. + if ((FAILED(hr)) && (hr == E_NOINTERFACE)) + { + s_bAudioOnly = TRUE; + return TRUE; + } + return !FAILED(hr); +} + + +static RECT fitMediaToWindow(RECT rectWindow, int mediaWidth, int mediaHeight) +{ + RECT retval; + + int windowWidth = rectWindow.right - rectWindow.left; + int windowHeight = rectWindow.bottom - rectWindow.top; + + if (mediaHeight*windowWidth > mediaWidth*windowHeight) + { + // Gap is on left&right sides + int nOutWidth = windowHeight* mediaWidth / mediaHeight; + int nGap = (windowWidth - nOutWidth)/2; + retval.top = rectWindow.top; + retval.bottom = retval.top + windowHeight; + retval.left = rectWindow.left + nGap; + retval.right = retval.left + nOutWidth; + } + else + { + // Gap is on the top/bottom sides + int nOutHeight = windowWidth* mediaHeight / mediaWidth; + int nGap = (windowHeight - nOutHeight)/2; + retval.left = rectWindow.left; + retval.right = retval.left + windowWidth; + retval.top = rectWindow.top + nGap; + retval.bottom = retval.top + nOutHeight; + } + + return retval; +} + + + + +void dsr_releaseObjects() +{ + if(s_dsr_notif_hwnd) + { + KillTimer(s_dsr_notif_hwnd,112); + DestroyWindow(s_dsr_notif_hwnd); + } + s_dsr_notif_hwnd=NULL; + + if ((s_OriginalVideoWndProc) && (s_hVideoWnd)) + { + SetWindowLong(s_hVideoWnd, GWL_WNDPROC, (LONG_PTR)s_OriginalVideoWndProc); + s_OriginalVideoWndProc = NULL; + s_hVideoWnd = NULL; + } + + s_pMediaEventEx = NULL; + s_pGraphBuilder = NULL; + s_pMediaControl = NULL; + s_pVideoWindow = NULL; + s_pBasicAudio = NULL; + s_pBasicVideo = NULL; + s_pMediaSeeking = NULL; + + s_using_dsr = false; +} + + +// Prefix used for functions here are dsd_ ( + +int dsr_play(char *fn) +{ + HRESULT hr; + + hr = s_pGraphBuilder.CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER); + if (FAILED(hr)) return 1; + +#ifdef _DEBUG + AddToRot(s_pGraphBuilder, &GraphEdit_dwRegister); +#endif + + s_pGraphBuilder->QueryInterface(IID_IMediaEvent, (void **)&s_pMediaEventEx); + if(!s_pMediaEventEx) + { + dsr_releaseObjects(); + return 1; + } + + m_video_output=(IVideoOutput *)SendMessage(mod.hMainWindow,WM_USER,0,IPC_GET_IVIDEOOUTPUT); + if(!m_video_output) + { + dsr_releaseObjects(); + return -200; // Can't play file + } + + // Create window that will receive filter notifications + static int classReg=0; + if(!classReg) + { + WNDCLASS wc={0,}; + wc.style = CS_DBLCLKS; + wc.lpfnWndProc = dsr_TimerWndProc; + wc.hInstance = mod.hDllInstance; + wc.hIcon = NULL; + wc.hCursor = NULL; + wc.lpszClassName = "in_dshowClass2"; + if (!RegisterClass(&wc)) + { + dsr_releaseObjects(); + return 1; + } + classReg=1; + } + s_dsr_notif_hwnd=CreateWindow("in_dshowClass2","dshow_notif2",NULL,0,0,1,1,NULL,NULL,mod.hDllInstance,NULL); + SetTimer(s_dsr_notif_hwnd,112,500,0); + + // Build a normal graph (start rendering) + WCHAR f[4096]; + MultiByteToWideChar(CP_ACP,0,fn,lstrlen(fn)+1,f,4096); + hr = s_pGraphBuilder->RenderFile(f,NULL); + if (FAILED(hr)) { + dsr_handleNotifyEvents(); + dsr_releaseObjects(); + if ((hr == CLASS_E_CLASSNOTAVAILABLE) || (hr == VFW_E_UNSUPPORTED_VIDEO) || (hr == VFW_E_NO_DECOMPRESSOR)) + { + if (ReportMissingCodec(fn)) // returns true if we sent a message + return -500; // Unsupported format + return -200; // Can't play file + } + return 1; + } + + // Check if it's a partial playing of the file (likely video codec missing) + if ((hr == VFW_S_PARTIAL_RENDER) || (hr == VFW_S_VIDEO_NOT_RENDERED)) + { + if (!ReportMissingCodec(fn)) // Report the missing codec if we can determine it + mod.fire_winampstatus(WINAMPX_STATUS_MISSING_AVI_CODEC, 0); // If we can't report a null codec missing + } + + if (FAILED(s_pGraphBuilder->QueryInterface(IID_IMediaControl, (void **)&s_pMediaControl)) || + FAILED(s_pGraphBuilder->QueryInterface(IID_IMediaSeeking, (void **)&s_pMediaSeeking)) || + FAILED(s_pMediaSeeking->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME))) + { + dsr_releaseObjects(); + return 1; + } + + + // Get length of file + int mylength = -1; + LONGLONG length; + if (SUCCEEDED(s_pMediaSeeking->GetDuration(&length))) + { + mylength=(int)(length/10000); + } + m_length = mylength; + + // Query for video interfaces, which may not be relevant for audio files + s_pGraphBuilder->QueryInterface(IID_IVideoWindow, (void **)&s_pVideoWindow); + s_pGraphBuilder->QueryInterface(IID_IBasicVideo, (void **)&s_pBasicVideo); + + // Query for audio interfaces, which may not be relevant for video-only files + s_pGraphBuilder->QueryInterface(IID_IBasicAudio, (void **)&s_pBasicAudio); + if (s_setVolumeOnStart != -1) + { + dsr_setvolume(s_setVolumeOnStart); + s_setVolumeOnStart = -1; + } + + // Is this an audio-only file (no video component)? + CheckVisibility(); + if (!s_bAudioOnly) + { + m_video_output->open(1, 1, 0, 1.0f, VIDEO_MAKETYPE('N','O','N','E')); // Dummy Size of 1x1 + s_hVideoWnd = (HWND)m_video_output->extended(VIDUSER_GET_VIDEOHWND, 0, 0); + InvalidateRect(s_hVideoWnd, NULL, TRUE); + + // Setup the video window + s_pVideoWindow->put_Owner((OAHWND) s_hVideoWnd); + s_pVideoWindow->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS); + + RECT grc; + GetClientRect(s_hVideoWnd, &grc); + s_parentRect = grc; + + if ((s_pBasicVideo) && (s_pVideoWindow)) + { + long videoWidth, videoHeight; + if (SUCCEEDED(s_pBasicVideo->GetVideoSize(&videoWidth, &videoHeight))) + { + RECT r = fitMediaToWindow(grc, videoWidth, videoHeight); + s_pVideoWindow->SetWindowPosition(r.left, r.top, r.right-r.left, r.bottom-r.top); + } + } + + // Intercept resize messages + s_OriginalVideoWndProc = (WNDPROC) SetWindowLong(s_hVideoWnd, GWL_WNDPROC, (LONG_PTR)dsr_SubclassParentWndProc); + } + + // Run the graph to play the media file + hr = s_pMediaControl->Run(); + if (FAILED(hr)) + { + dsr_stop(); + return 1; + } + + return 0; +} + + +// stop playing. +void dsr_stop() +{ + if (s_pMediaControl) + s_pMediaControl->Stop(); + if (s_pVideoWindow) + { + s_pVideoWindow->put_Visible(OAFALSE); + s_pVideoWindow->put_Owner(NULL); + } + + if (m_video_output) + m_video_output->close(); + mod.outMod->Close(); + mod.SAVSADeInit(); + dsr_releaseObjects(); + m_length=-1; + lastfn[0]=0; +} + + + +// Standard Pause Routines +void dsr_pause() { + paused=1; + if (s_pMediaControl) + { + s_pMediaControl->Pause(); + } +} +void dsr_unpause() { + paused=0; + if (s_pMediaControl) + { + s_pMediaControl->Run(); + } +} + + + +int dsr_getoutputtime() { + if (s_bufferstat) + return s_bufferstat; + + if (s_pMediaSeeking) + { + LONGLONG pos; + if (SUCCEEDED(s_pMediaSeeking->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME)) && + SUCCEEDED(s_pMediaSeeking->GetCurrentPosition(&pos))) + { + return (int)(pos/10000); + } + } + return 0; +} + +void dsr_setoutputtime(int time_in_ms) { + if(s_pMediaSeeking) { + DWORD dwCaps = AM_SEEKING_CanSeekAbsolute; + if (s_pMediaSeeking->CheckCapabilities(&dwCaps) == S_OK) + { + int oldpause=paused; + if(oldpause) dsr_unpause(); + LONGLONG l=((LONGLONG)time_in_ms)*10000; + s_pMediaSeeking->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME); + s_pMediaSeeking->SetPositions(&l,AM_SEEKING_AbsolutePositioning,NULL,AM_SEEKING_NoPositioning); + mod.outMod->Flush(time_in_ms); + if(oldpause) dsr_pause(); + } + } +} + +void dsr_setvolume(int volume) +{ + if (s_pBasicAudio) + { + volume = min(volume, 255); + volume = max(volume, 0); + s_pBasicAudio->put_Volume(log(volume+1)/log(256)*10000-10000); // Map (0,255) to (-10000,0) log scale (as ActiveX is in DB's) + } + else + { + s_setVolumeOnStart = volume; + } +} + + + +LRESULT CALLBACK dsr_TimerWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if(uMsg==WM_TIMER && wParam==112 && s_dsr_notif_hwnd) + { + dsr_handleNotifyEvents(); + if(s_buffering) + { + BeginEnumFilters(s_pGraphBuilder, pEF, pBF) + { + if(CComQIPtr<IAMNetworkStatus, &IID_IAMNetworkStatus> pAMNS = pBF) + { + long BufferingProgress = 0; + if(SUCCEEDED(pAMNS->get_BufferingProgress(&BufferingProgress)) && BufferingProgress > 0) + { + wsprintf(lastfn_status,"Buffering (%ld%%)",BufferingProgress); + if(m_video_output) m_video_output->extended(VIDUSER_SET_INFOSTRING,(int)&lastfn_status,0); + + int bpos=BufferingProgress; + int csa = mod.SAGetMode(); + char tempdata[75*2]={0,}; + int x; + if (csa&1) + { + for (x = 0; x < bpos*75/100; x ++) + { + tempdata[x]=x*16/75; + } + } + if (csa&2) + { + int offs=(csa&1) ? 75 : 0; + x=0; + while (x < bpos*75/100) + { + tempdata[offs + x++]=-6+x*14/75; + } + while (x < 75) + { + tempdata[offs + x++]=0; + } + } + if (csa==4) + { + tempdata[0]=tempdata[1]=(bpos*127/100); + } + if (csa) mod.SAAdd(tempdata,++s_bufferstat,(csa==3)?0x80000003:csa); + + PostMessage(mod.hMainWindow,WM_USER,0,243); + SendStatus(WINAMPX_STATUS_PREBUFFERING_PCT, 255*BufferingProgress/100); + + break; + } + } + } + EndEnumFilters + } + } + + return (DefWindowProc(hwnd, uMsg, wParam, lParam)); +} + + +void dsr_handleNotifyEvents() +{ +// char s[256]; + for (;;) { + long evCode, param1, param2; + HRESULT h = s_pMediaEventEx->GetEvent(&evCode, ¶m1, ¶m2, 0); + if (FAILED(h)) break; + switch(evCode) { + case EC_BUFFERING_DATA: + { +// sprintf(s, "Handling Event: EC_BUFFERING_DATA: %d\n", param1); +// OutputDebugString(s); + s_buffering=param1; + if(!s_buffering) + { + lastfn_status[0]=0; + s_bufferstat=0; + PostMessage(mod.hMainWindow,WM_USER,0,243); + + break; + } + } + break; + + case EC_COMPLETE: + { +// OutputDebugString("Handling Event: EC_COMPLETE\n"); + PostMessage(mod.hMainWindow,WM_WA_MPEG_EOF,0,0); + } + + default: + { +// sprintf(s, "Handling Event: 0x%x : param1=0x%x; param2 = 0x%x\n", evCode, param1, param2); +// OutputDebugString(s); + } + } + + s_pMediaEventEx->FreeEventParams(evCode, param1, param2); + } +} + + +// Subclass of the Window containing the code. (Used to check if the parent got resized) +LRESULT CALLBACK dsr_SubclassParentWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + LRESULT retval = CallWindowProc(s_OriginalVideoWndProc, hwnd, uMsg, wParam, lParam); + + if ((uMsg == WM_SIZE) && (s_hVideoWnd)) + { + RECT grc; + GetClientRect(s_hVideoWnd, &grc); + + if (!EqualRect(&grc, &s_parentRect)) + { + s_parentRect = grc; + + if ((s_pBasicVideo) && (s_pVideoWindow)) + { + long videoWidth, videoHeight; + if (SUCCEEDED(s_pBasicVideo->GetVideoSize(&videoWidth, &videoHeight))) + { + RECT r = fitMediaToWindow(grc, videoWidth, videoHeight); + s_pVideoWindow->SetWindowPosition(r.left, r.top, r.right-r.left, r.bottom-r.top); + } + } + } + } + + if ((uMsg == WM_PARENTNOTIFY) && (LOWORD(wParam)==WM_RBUTTONDOWN)) + { + SendStatus(WINAMPX_STATUS_VIDEO_RIGHT_CLICK, lParam); + } + + if ((uMsg == WM_PARENTNOTIFY) && (LOWORD(wParam)==WM_LBUTTONDOWN)) + { + static DWORD dwLastTime = 0; + int dwTimeNow = GetTickCount(); + if (((dwTimeNow - dwLastTime) < GetDoubleClickTime()) && (dwLastTime != 0)) + { + PostMessage(hwnd, WM_USER_DSR_LBUTTONDBLCLK, 0, 0); // Notify the video window that we double clicked + dwLastTime = 0; + } + else + dwLastTime = dwTimeNow; + } + + if ((uMsg == WM_USER_DSR_FULLSCREEN) && (s_pVideoWindow)) + { + s_pVideoWindow->HideCursor(wParam ? OATRUE : OAFALSE); + } + + return retval; +} + +#endif // WINAMPX diff --git a/Src/Plugins/Input/in_dshow/header_asf.cpp b/Src/Plugins/Input/in_dshow/header_asf.cpp new file mode 100644 index 00000000..c78e3f3b --- /dev/null +++ b/Src/Plugins/Input/in_dshow/header_asf.cpp @@ -0,0 +1,202 @@ +#include <windows.h> +#include "header_asf.h" +#include <mmsystem.h> +#include <stdio.h> +#include <bfc/platform/types.h> +#pragma pack(1) + +/////////////////////// +// MS GUID definition +/////////////////////// +#ifndef GUID_DEFINED +#define GUID_DEFINED +// Size of GUID is 16 bytes! +typedef struct { + uint32_t Data1; // 4 bytes + uint16_t Data2; // 2 bytes + uint16_t Data3; // 2 bytes + uint8_t Data4[8]; // 8 bytes +} GUID_t; +#endif + +/////////////////////// +// ASF Object Header +/////////////////////// +typedef struct { + uint8_t guid[16]; + uint64_t size; +} ASF_obj_header_t; + +//////////////// +// ASF Header +//////////////// +typedef struct { + ASF_obj_header_t objh; + uint32_t cno; // number of subchunks + uint8_t v1; // unknown (0x01) + uint8_t v2; // unknown (0x02) +} ASF_header_t; + +///////////////////// +// ASF File Header +///////////////////// +typedef struct { + uint8_t client[16]; // Client GUID + uint64_t file_size; + uint64_t creat_time; //File creation time FILETIME 8 + uint64_t packets; //Number of packets UINT64 8 + uint64_t end_timestamp; //Timestamp of the end position UINT64 8 + uint64_t duration; //Duration of the playback UINT64 8 + uint32_t start_timestamp; //Timestamp of the start position UINT32 4 + uint32_t preroll; //Time to bufferize before playing UINT32 4 + uint32_t flags; //Unknown, maybe flags ( usually contains 2 ) UINT32 4 + uint32_t packetsize; //Size of packet, in bytes UINT32 4 + uint32_t packetsize2; //Size of packet ( confirm ) UINT32 4 + uint32_t frame_size; //Size of uncompressed video frame UINT32 4 +} ASF_file_header_t; + +/////////////////////// +// ASF Stream Header +/////////////////////// +typedef struct { + uint8_t type[16]; // Stream type (audio/video) GUID 16 + uint8_t concealment[16]; // Audio error concealment type GUID 16 + uint64_t unk1; // Unknown, maybe reserved ( usually contains 0 ) UINT64 8 + uint32_t type_size; //Total size of type-specific data UINT32 4 + uint32_t stream_size; //Size of stream-specific data UINT32 4 + uint16_t stream_no; //Stream number UINT16 2 + uint32_t unk2; //Unknown UINT32 4 +} ASF_stream_header_t; + +/////////////////////////// +// ASF Content Description +/////////////////////////// +typedef struct { + uint16_t title_size; + uint16_t author_size; + uint16_t copyright_size; + uint16_t comment_size; + uint16_t rating_size; +} ASF_content_description_t; + +//////////////////////// +// ASF Segment Header +//////////////////////// +typedef struct { + uint8_t streamno; + uint8_t seq; + uint32_t x; + uint8_t flag; +} ASF_segmhdr_t; + +////////////////////// +// ASF Stream Chunck +////////////////////// +typedef struct { + uint16_t type; + uint16_t size; + uint32_t sequence_number; + uint16_t unknown; + uint16_t size_confirm; +} ASF_stream_chunck_t; + +#pragma pack() + +// Definition of the differents type of ASF streaming +typedef enum { + ASF_Unknown_e, + ASF_Live_e, + ASF_Prerecorded_e, + ASF_Redirector_e, + ASF_PlainText_e +} ASF_StreamType_e; + +#define ASF_LOAD_GUID_PREFIX(guid) (*(uint32_t *)(guid)) + +#define ASF_GUID_PREFIX_audio_stream 0xF8699E40 +#define ASF_GUID_PREFIX_video_stream 0xBC19EFC0 +#define ASF_GUID_PREFIX_audio_conceal_none 0x49f1a440 +#define ASF_GUID_PREFIX_audio_conceal_interleave 0xbfc3cd50 +#define ASF_GUID_PREFIX_header 0x75B22630 +#define ASF_GUID_PREFIX_data_chunk 0x75b22636 +#define ASF_GUID_PREFIX_index_chunk 0x33000890 +#define ASF_GUID_PREFIX_stream_header 0xB7DC0791 +#define ASF_GUID_PREFIX_header_2_0 0xD6E229D1 +#define ASF_GUID_PREFIX_file_header 0x8CABDCA1 +#define ASF_GUID_PREFIX_content_desc 0x75b22633 + +HeaderAsf::HeaderAsf() +{ +} + +int HeaderAsf::getInfos(const wchar_t *filename, bool checkMetadata) +{ + ASF_header_t asfh; + DWORD bytesRead = 0; + unsigned char asfhdrguid[16]={0x30,0x26,0xB2,0x75,0x8E,0x66,0xCF,0x11,0xA6,0xD9,0x00,0xAA,0x00,0x62,0xCE,0x6C}; + + HANDLE fh=CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (fh==INVALID_HANDLE_VALUE) + return 0; + + ReadFile(fh,&asfh, sizeof(ASF_header_t), &bytesRead, NULL); + + // check for ASF guid + if(memcmp(asfhdrguid,asfh.objh.guid,16) || asfh.cno>256) + { + CloseHandle(fh); + return 0; + } + + for(int i=0;i<(int)asfh.cno;i++) { + int pos = SetFilePointer(fh, 0, 0, FILE_CURRENT); + + ASF_obj_header_t objh; + bytesRead = 0; + ReadFile(fh, &objh, sizeof(objh), &bytesRead, NULL); + + switch(ASF_LOAD_GUID_PREFIX(objh.guid)) { + case ASF_GUID_PREFIX_file_header: + { + ASF_file_header_t fileh; + bytesRead = 0; + ReadFile(fh, &fileh, sizeof(fileh), &bytesRead, NULL); + length=(int)(fileh.duration/10000); + } + break; + case ASF_GUID_PREFIX_stream_header: + { + ASF_stream_header_t streamh; + bytesRead = 0; + ReadFile(fh, &streamh, sizeof(streamh), &bytesRead, NULL); + switch(ASF_LOAD_GUID_PREFIX(streamh.type)) + { + case ASF_GUID_PREFIX_audio_stream: + { + WAVEFORMATEX wfe = {0}; + bytesRead = 0; + ReadFile(fh, &wfe, sizeof(wfe), &bytesRead, NULL); + audio_bps=wfe.wBitsPerSample; + audio_srate=wfe.nSamplesPerSec; + audio_nch=wfe.nChannels; + has_audio=true; + } + break; + case ASF_GUID_PREFIX_video_stream: + { + bytesRead = 0; + ReadFile(fh, &video_w, sizeof(video_w), &bytesRead, NULL); + bytesRead = 0; + ReadFile(fh, &video_h, sizeof(video_h), &bytesRead, NULL); + has_video=true; + } + break; + } + } + break; + } + SetFilePointer(fh, pos+(int)objh.size, NULL, FILE_BEGIN); + } + CloseHandle(fh); + return 1; +} diff --git a/Src/Plugins/Input/in_dshow/header_asf.h b/Src/Plugins/Input/in_dshow/header_asf.h new file mode 100644 index 00000000..ce85c1f4 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/header_asf.h @@ -0,0 +1,14 @@ +#ifndef _HEADER_ASF_H +#define _HEADER_ASF_H + +#include "Header.h" + +class HeaderAsf : public Header +{ +public: + HeaderAsf(); + + int getInfos(const wchar_t *filename, bool checkMetadata=false); +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_dshow/header_avi.cpp b/Src/Plugins/Input/in_dshow/header_avi.cpp new file mode 100644 index 00000000..3fea2756 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/header_avi.cpp @@ -0,0 +1,283 @@ +#include <windows.h> +#include "header_avi.h" +#include <mmsystem.h> +#include <bfc/platform/types.h> + +#pragma pack(1) + + +typedef struct +{ + DWORD dwMicroSecPerFrame; // frame display rate (or 0L) + DWORD dwMaxBytesPerSec; // max. transfer rate + DWORD dwPaddingGranularity; // pad to multiples of this + // size; normally 2K. + DWORD dwFlags; // the ever-present flags + DWORD dwTotalFrames; // # frames in file + DWORD dwInitialFrames; + DWORD dwStreams; + DWORD dwSuggestedBufferSize; + + DWORD dwWidth; + DWORD dwHeight; + + DWORD dwReserved[4]; +} +MainAVIHeader; + +typedef struct +{ + FOURCC fccType; + FOURCC fccHandler; + DWORD dwFlags; /* Contains AVITF_* flags */ + WORD wPriority; + WORD wLanguage; + DWORD dwInitialFrames; + DWORD dwScale; + DWORD dwRate; /* dwRate / dwScale == samples/second */ + DWORD dwStart; + DWORD dwLength; /* In units above... */ + DWORD dwSuggestedBufferSize; + DWORD dwQuality; + DWORD dwSampleSize; + RECT rcFrame; +} +AVIStreamHeader; + +#pragma pack() + +#ifndef mmioFOURCC +#define mmioFOURCC( ch0, ch1, ch2, ch3 ) \ + ( (long)(unsigned char)(ch0) | ( (long)(unsigned char)(ch1) << 8 ) | \ + ( (long)(unsigned char)(ch2) << 16 ) | ( (long)(unsigned char)(ch3) << 24 ) ) +#endif /* mmioFOURCC */ + +HeaderAvi::HeaderAvi(bool bAllowHttpConnection /* = false */) + : bAllowHttp(bAllowHttpConnection), fh(INVALID_HANDLE_VALUE) +{} + +HeaderAvi::~HeaderAvi() +{} + +int HeaderAvi::getInfos(const wchar_t *filename, bool checkMetadata) +{ + { + fh = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (fh == INVALID_HANDLE_VALUE) return 0; + } + + int riffid = read_dword(); + if (riffid != mmioFOURCC('R', 'I', 'F', 'F')) + { + myfclose(); + return 0; + } + + unsigned __int32 filesize = read_dword(); + int aviid = read_dword(); + if (aviid != mmioFOURCC('A', 'V', 'I', ' ')) + { + myfclose(); + return 0; + } + + int last_fccType = 0; + while (1) + { + int id = read_dword(); + if (!id) break; + + if (id == mmioFOURCC('L', 'I', 'S', 'T')) + { + unsigned __int32 len = read_dword() - 4; + id = read_dword(); + + switch (id) + { + case mmioFOURCC('m', 'o', 'v', 'i'): + { + // MOVI header + //if (!checkMetadata) // might have metadata at the end of the file, so we gotta keep going + //{ + myfclose(); + return 1; + //} + len = (len + 1) & (~1); + myfseek(len, FILE_CURRENT); + } + break; + case mmioFOURCC('I', 'N', 'F', 'O'): + { + if (!checkMetadata) + { + if (len) + myfseek(len, FILE_CURRENT); + break; + } + + while (len) + { + int infoId = read_dword(); + unsigned __int32 infoSize = read_dword(); + unsigned __int32 chunksize = (infoSize + 1) & (~1); + len -= (chunksize + 8); + switch (infoId) + { + // TODO: IYER for year, but is it string or number? + // TODO: ITRK for track, but is it string or number? + case mmioFOURCC('I', 'C', 'O', 'M'): + { + composer = (wchar_t*)calloc(infoSize + 1, sizeof(wchar_t)); + composer[infoSize] = 0; + myfread(composer, infoSize, 1); + chunksize -= infoSize; + } + break; + case mmioFOURCC('I', 'P', 'U', 'B'): + { + publisher = (wchar_t*)calloc(infoSize + 1, sizeof(wchar_t)); + publisher[infoSize] = 0; + myfread(publisher, infoSize, 1); + chunksize -= infoSize; + } + break; + case mmioFOURCC('I', 'A', 'L', 'B'): + { + album = (wchar_t*)calloc(infoSize + 1, sizeof(wchar_t)); + album[infoSize] = 0; + myfread(album, infoSize, 1); + chunksize -= infoSize; + } + break; + case mmioFOURCC('I', 'G', 'N', 'R'): + { + genre = (wchar_t*)calloc(infoSize + 1, sizeof(wchar_t)); + genre[infoSize] = 0; + myfread(genre, infoSize, 1); + chunksize -= infoSize; + } + break; + case mmioFOURCC('I', 'C', 'M', 'T'): + { + comment = (wchar_t*)calloc(infoSize + 1, sizeof(wchar_t)); + comment[infoSize] = 0; + myfread(comment, infoSize, 1); + chunksize -= infoSize; + } + break; + case mmioFOURCC('I', 'A', 'R', 'T'): + { + artist = (wchar_t*)calloc(infoSize + 1, sizeof(wchar_t)); + artist[infoSize] = 0; + myfread(artist, infoSize, 1); + chunksize -= infoSize; + } + break; + case mmioFOURCC('I', 'N', 'A', 'M'): + { + title = (wchar_t*)calloc(infoSize + 1, sizeof(wchar_t)); + title[infoSize] = 0; + myfread(title, infoSize, 1); + chunksize -= infoSize; + } + break; + } + if (chunksize > 0) myfseek(chunksize, FILE_CURRENT); + + } + } + break; + } + continue; + } + + unsigned __int32 size2 = read_dword(); + size_t chunksize = (size2 + 1) & (~1); + switch (id) + { + case mmioFOURCC('a', 'v', 'i', 'h'): + { + MainAVIHeader ah; + size_t l = min(size2, sizeof(ah)); + myfread(&ah, l, 1); + chunksize -= l; + video_h = ah.dwHeight; video_w = ah.dwWidth; + if (video_h && video_w) has_video = true; + } + break; + case mmioFOURCC('s', 't', 'r', 'h'): + { + AVIStreamHeader ash; + size_t l = min(size2, sizeof(ash)); + myfread(&ash, l, 1); + chunksize -= l; + last_fccType = ash.fccType; + if (last_fccType == mmioFOURCC('v', 'i', 'd', 's')) + { + float frametime = (float)ash.dwScale / ((float)ash.dwRate / 1000); + length = (int)((float)ash.dwLength * frametime); + } + } + break; + case mmioFOURCC('s', 't', 'r', 'f'): + { + if (last_fccType == mmioFOURCC('v', 'i', 'd', 's')) + { + } + else if (last_fccType == mmioFOURCC('a', 'u', 'd', 's')) + { + WAVEFORMATEX wfe; + size_t l = min(chunksize, sizeof(wfe)); + myfread(&wfe, sizeof(wfe), 1); + audio_bps = wfe.wBitsPerSample; + audio_srate = wfe.nSamplesPerSec; + audio_nch = wfe.nChannels; + if (!audio_bps) audio_bps = 16; + has_audio = true; + chunksize -= l; + } + } + break; + } + if (chunksize > 0) myfseek((long)chunksize, FILE_CURRENT); + } + + myfclose(); + return 1; +} + +size_t HeaderAvi::myfread(void *buffer, size_t size, size_t count) +{ + DWORD bytesRead = 0; + ReadFile(fh, buffer, (DWORD)(size*count), &bytesRead, NULL); + return bytesRead; +} + +int HeaderAvi::myfclose() +{ + return CloseHandle(fh); +} + +// Note; Only supports SEEK_CUR for http (which is what is used) +int HeaderAvi::myfseek(long offset, DWORD origin) +{ + return SetFilePointer(fh, offset, 0, origin); + +} +/* TODO: +__int64 myFileSeek (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; +} +*/
\ No newline at end of file diff --git a/Src/Plugins/Input/in_dshow/header_avi.h b/Src/Plugins/Input/in_dshow/header_avi.h new file mode 100644 index 00000000..d96fe2c3 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/header_avi.h @@ -0,0 +1,25 @@ +#ifndef _HEADER_AVI_H +#define _HEADER_AVI_H + +#include <windows.h> + +#include "Header.h" + +class HeaderAvi : public Header +{ +public: + HeaderAvi(bool bAllowHttpConnection = false); // bAllowHttp will allow an http connection to read header info +~HeaderAvi(); + int getInfos(const wchar_t *filename, bool checkMetadata=false); + int read_dword() { int v=0; myfread(&v,sizeof(v),1); return v; } + +private: + HANDLE fh; + + bool bAllowHttp; + size_t myfread( void *buffer, size_t size, size_t count); + int myfclose(); + int myfseek(long offset, DWORD origin); +}; + +#endif diff --git a/Src/Plugins/Input/in_dshow/header_mpg.cpp b/Src/Plugins/Input/in_dshow/header_mpg.cpp new file mode 100644 index 00000000..874ead79 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/header_mpg.cpp @@ -0,0 +1,234 @@ +#include <windows.h> +#include "header_mpg.h" +#include <bfc/platform/types.h> + +static int frameratecode2framerate[16] = { + 0, + // Official mpeg1/2 framerates: + 24000*10000/1001, 24*10000,25*10000, 30000*10000/1001, 30*10000,50*10000,60000*10000/1001, 60*10000, + // libmpeg3's "Unofficial economy rates": + 1*10000,5*10000,10*10000,12*10000,15*10000,0,0 +}; + +static const int gaSampleRate[3][4] = +{ + {44100, 48000, 32000, 0}, // MPEG-1 + {22050, 24000, 16000, 0}, // MPEG-2 + {11025, 12000, 8000, 0}, // MPEG-2.5 +}; + +static const int gaBitrate[2][3][15] = +{ + { + // MPEG-1 + { 0, 32, 64, 96,128,160,192,224,256,288,320,352,384,416,448}, // Layer 1 + { 0, 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384}, // Layer 2 + { 0, 32, 40, 48, 56, 64, 80, 96,112,128,160,192,224,256,320}, // Layer 3 + }, + + { + // MPEG-2, MPEG-2.5 + { 0, 32, 48, 56, 64, 80, 96,112,128,144,160,176,192,224,256}, // Layer 1 + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160}, // Layer 2 + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160}, // Layer 3 + }, +}; + +#define MPEG_SYNCWORD 0x7ff +#define MPEG1 0 +#define MPEG2 1 +#define MPEG25 2 +#define MODE_MONO 3 + +HeaderMpg::HeaderMpg() : fh(INVALID_HANDLE_VALUE) +{ + memset(buf, 0, sizeof(buf)); + pbuf = end = 0; + m_BitrateNdx = m_SampleRateNdx = m_Layer = m_Id = m_Mode = + m_Idex = video_bitrate = audio_bitrate = 0; +} + +int HeaderMpg::mp3headerFromInt(unsigned long dwHdrBits) +{ + // header fields + int m_Syncword; + /*int m_CrcCheck; + int m_Padding; + int m_Private; + int m_ModeExt; + int m_Copyright; + int m_Original; + int m_Emphasis;*/ + + // calculated data + int m_HeaderValid; + + // read header fields + m_Syncword = (dwHdrBits >> 21) & 0x000007ff; + m_Idex = (dwHdrBits >> 20) & 0x00000001; + m_Id = (dwHdrBits >> 19) & 0x00000001; + m_Layer = 4 -((dwHdrBits >> 17) & 0x00000003); + //m_CrcCheck = !((dwHdrBits >> 16) & 0x00000001); + m_BitrateNdx = (dwHdrBits >> 12) & 0x0000000f; + m_SampleRateNdx = (dwHdrBits >> 10) & 0x00000003; + /*m_Padding = (dwHdrBits >> 9) & 0x00000001; + m_Private = (dwHdrBits >> 8) & 0x00000001;*/ + m_Mode = (dwHdrBits >> 6) & 0x00000003; + /*m_ModeExt = (dwHdrBits >> 4) & 0x00000003; + m_Copyright = (dwHdrBits >> 3) & 0x00000001; + m_Original = (dwHdrBits >> 2) & 0x00000001; + m_Emphasis = (dwHdrBits ) & 0x00000003;*/ + + // check if header is valid +/* if ( + (m_Syncword != MPEG_SYNCWORD) || + +#ifndef SYNC_ALL_LAYERS + (m_Layer != 3 ) || +#else + (m_Layer == 4 ) || +#endif + + (m_BitrateNdx == 15 ) || + (m_BitrateNdx == 0 ) || + (m_SampleRateNdx == 3 ) || + (m_Idex == 0 && m_Layer != 3 ) || + (m_Idex == 0 && m_Id == 1 && m_Layer == 3) + )*/ + + if ( + (m_Syncword != MPEG_SYNCWORD) || + (m_Layer == 4 ) || + (m_BitrateNdx == 15 ) || + (m_BitrateNdx == 0 ) || + (m_SampleRateNdx == 3 ) || + (m_Idex == 0 && m_Layer != 3 ) || + (m_Idex == 0 && m_Id == 1 ) || + (m_Idex == 0 && m_BitrateNdx>8 ) + ) + { + m_HeaderValid = 0; +// ResetMembers(); + } + else + { + m_HeaderValid = 1; +// SetMembers(); + } + + return m_HeaderValid; +} + +static __int64 FileSize64(HANDLE file) +{ + LARGE_INTEGER position; + position.QuadPart=0; + position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart); + + if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) + return INVALID_FILE_SIZE; + else + return position.QuadPart; +} + +int HeaderMpg::getInfos(const wchar_t *filename, bool metadata) +{ + fh = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if(fh==INVALID_HANDLE_VALUE) return 0; + int ret=decodeHeader(); + if(ret) { + int l=MulDiv(video_bitrate+audio_bitrate,2048,2018); + l/=8; + int64_t flen = FileSize64(fh); + if(l) + length=(int)((flen*1000ull) / (int64_t)l); + } +CloseHandle(fh); + return ret; +} + +int HeaderMpg::decodeHeader() +{ + DWORD bytesRead = 0; + memset(buf,0,sizeof(buf)); + end=buf+sizeof(buf); + ReadFile(fh, &buf, sizeof(buf), &bytesRead, NULL); + + pbuf=buf; + while(1) { + int code; + code=sync_packet(); + if(!code) return 1; + switch(code) { + case 0x1b3: { + if(has_video) break; + + pbuf+=4; + if ((pbuf[6] & 0x20) != 0x20){ + //printf("missing marker bit!\n"); + //return 1; /* missing marker_bit */ + break; + } + + int height = (pbuf[0] << 16) | (pbuf[1] << 8) | pbuf[2]; + + video_w = (height >> 12); + video_h = (height & 0xfff); + + int width = ((height >> 12) + 15) & ~15; + height = ((height & 0xfff) + 15) & ~15; + + if ((width > 768) || (height > 576)){ + //printf("size restrictions for MP@ML or MPEG1 exceeded! (%dx%d)\n",width,height); + // return 1; /* size restrictions for MP@ML or MPEG1 */ + break; + } + + has_video=true; + + int bitrate=(pbuf[4]<<10)|(pbuf[5]<<2)|(pbuf[6]>>6); + if(bitrate==262143) { + //variable bitrate + //has_video=false; + break; + } + bitrate=bitrate*2/5; + video_bitrate=bitrate*1000; + break; + } + case 0x1be: { + // padding stream + pbuf+=4; + int s=pbuf[0]<<8|pbuf[1]; + pbuf+=2+s; + break; + } + } + if(code>=0x1c0 && code<=0x1df) { + + pbuf+=4; + int len=pbuf[0]<<8|pbuf[1]; + pbuf+=2; + pbuf+=5; + if (len > (end-pbuf)) + return 0; + // decode mpeg audio header + while(--len) { + if(mp3headerFromInt((pbuf[0]<<24)|(pbuf[1]<<16)|(pbuf[2]<<8)|pbuf[3])) { + has_audio=true; + audio_bps=16; + int m_MpegVersion = m_Id==1 ? MPEG1 : (m_Idex==1?MPEG2 : MPEG25); + audio_nch = m_Mode == MODE_MONO ? 1:2; + audio_srate = gaSampleRate[m_MpegVersion][m_SampleRateNdx]; + audio_bitrate = gaBitrate[m_MpegVersion==MPEG1?0:1][m_Layer-1][m_BitrateNdx] * 1000; + break; + } + pbuf++; + } + } + if(has_audio && has_video) return 1; + pbuf++; + } + + return 1; +} diff --git a/Src/Plugins/Input/in_dshow/header_mpg.h b/Src/Plugins/Input/in_dshow/header_mpg.h new file mode 100644 index 00000000..7122eade --- /dev/null +++ b/Src/Plugins/Input/in_dshow/header_mpg.h @@ -0,0 +1,43 @@ +#ifndef _HEADER_MPG_H +#define _HEADER_MPG_H + +#include <stdio.h> +#include "Header.h" + +class HeaderMpg : public Header +{ +public: + HeaderMpg(); + + int getInfos(const wchar_t *filename, bool checkMetadata=false); + + int video_bitrate,audio_bitrate; + +private: + HANDLE fh; + + int decodeHeader(); + + unsigned char buf[8192*8]; + unsigned char *pbuf; + unsigned char *end; + int sync_packet() { + // sync packet: + while(1){ + if(pbuf>=end) return 0; + if(pbuf[0]==0 && pbuf[1]==0 && pbuf[2]==1) break; // synced + pbuf++; + } + return 0x100|pbuf[3]; + } + + int mp3headerFromInt(unsigned long dwHdrBits); + int m_BitrateNdx; + int m_SampleRateNdx; + int m_Layer; + int m_Id; + int m_Mode; + int m_Idex; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_dshow/header_wav.cpp b/Src/Plugins/Input/in_dshow/header_wav.cpp new file mode 100644 index 00000000..04fbc846 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/header_wav.cpp @@ -0,0 +1,220 @@ +#include "header_wav.h" +#include <windows.h> +#include "header_avi.h" +#include <mmsystem.h> +#include <bfc/platform/types.h> +#pragma pack(1) + +struct WAVEfmt +{ + unsigned __int16 encoding; + unsigned __int16 numChannels; + unsigned __int32 sampleRate; + unsigned __int32 avgBytesPerSec; + unsigned __int16 blockAlign; + unsigned __int16 bitsPerSample; +}; +#pragma pack() + +#ifndef mmioFOURCC +#define mmioFOURCC( ch0, ch1, ch2, ch3 ) \ + ( (long)(unsigned char)(ch0) | ( (long)(unsigned char)(ch1) << 8 ) | \ + ( (long)(unsigned char)(ch2) << 16 ) | ( (long)(unsigned char)(ch3) << 24 ) ) +#endif /* mmioFOURCC */ + +HeaderWav::HeaderWav(bool bAllowHttpConnection /* = false */) + : bAllowHttp(bAllowHttpConnection), fh(INVALID_HANDLE_VALUE) +{} + + + +int HeaderWav::getInfos(const wchar_t *filename, bool checkMetadata) +{ + unsigned __int32 bytesPerSecond=0; + { + fh = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (fh == INVALID_HANDLE_VALUE) return 0; + } + + int riffid = read_dword(); + if (riffid != mmioFOURCC('R', 'I', 'F', 'F')) + { + myfclose(); + return 0; + } + + unsigned __int32 filesize = read_dword(); + int aviid = read_dword(); + if (aviid != mmioFOURCC('W', 'A', 'V', 'E')) + { + myfclose(); + return 0; + } + + while (1) + { + int id = read_dword(); + if (!id) break; + + if (id == mmioFOURCC('L', 'I', 'S', 'T')) + { + unsigned __int32 len = read_dword() - 4; + id = read_dword(); + + switch (id) + { + case mmioFOURCC('I', 'N', 'F', 'O'): + { + if (!checkMetadata) + { + if (len) + myfseek(len, FILE_CURRENT); + break; + } + + while (len) + { + int infoId = read_dword(); + unsigned __int32 infoSize = read_dword(); + unsigned __int32 chunksize = (infoSize + 1) & (~1); + len -= (chunksize + 8); + switch (infoId) + { + // TODO: IYER for year, but is it string or number? + // TODO: ITRK for track, but is it string or number? + case mmioFOURCC('I', 'C', 'O', 'M'): + { + composer = (wchar_t*)calloc(infoSize + 1, sizeof(wchar_t)); + composer[infoSize] = 0; + myfread(composer, infoSize, 1); + chunksize -= infoSize; + } + break; + case mmioFOURCC('I', 'P', 'U', 'B'): + { + publisher = (wchar_t*)calloc(infoSize + 1, sizeof(wchar_t)); + publisher[infoSize] = 0; + myfread(publisher, infoSize, 1); + chunksize -= infoSize; + } + break; + case mmioFOURCC('I', 'A', 'L', 'B'): + { + album = (wchar_t*)calloc(infoSize + 1, sizeof(wchar_t)); + album[infoSize] = 0; + myfread(album, infoSize, 1); + chunksize -= infoSize; + } + break; + case mmioFOURCC('I', 'G', 'N', 'R'): + { + genre = (wchar_t*)calloc(infoSize + 1, sizeof(wchar_t)); + genre[infoSize] = 0; + myfread(genre, infoSize, 1); + chunksize -= infoSize; + } + break; + case mmioFOURCC('I', 'C', 'M', 'T'): + { + comment = (wchar_t*)calloc(infoSize + 1, sizeof(wchar_t)); + comment[infoSize] = 0; + myfread(comment, infoSize, 1); + chunksize -= infoSize; + } + break; + case mmioFOURCC('I', 'A', 'R', 'T'): + { + artist = (wchar_t*)calloc(infoSize + 1, sizeof(wchar_t)); + artist[infoSize] = 0; + myfread(artist, infoSize, 1); + chunksize -= infoSize; + } + break; + case mmioFOURCC('I', 'N', 'A', 'M'): + { + title = (wchar_t*)calloc(infoSize + 1, sizeof(wchar_t)); + title[infoSize] = 0; + myfread(title, infoSize, 1); + chunksize -= infoSize; + } + break; + } + if (chunksize > 0) myfseek(chunksize, FILE_CURRENT); + + } + } + break; + } + continue; + } + + unsigned __int32 size2 = read_dword(); + unsigned __int32 chunksize = (size2 + 1) & (~1); + switch (id) + { + case mmioFOURCC('f', 'm', 't', ' '): + { + WAVEfmt fmt; + size_t l = min(size2, sizeof(fmt)); + myfread(&fmt, l, 1); + chunksize -= (unsigned __int32)l; + has_audio=true; + audio_nch=fmt.numChannels; + audio_bps=fmt.bitsPerSample; + audio_srate=fmt.sampleRate; + bytesPerSecond=fmt.avgBytesPerSec; + } + break; + case mmioFOURCC('d','a','t','a'): + { + if (bytesPerSecond) + length = MulDiv(1000, chunksize, bytesPerSecond); + } + break; + + } + if (chunksize > 0) myfseek(chunksize, FILE_CURRENT); + } + + myfclose(); + return 1; +} + +size_t HeaderWav::myfread(void *buffer, size_t size, size_t count) +{ + DWORD bytesRead = 0; + ReadFile(fh, buffer,(DWORD)(size*count), &bytesRead, NULL); + return bytesRead; +} + + +int HeaderWav::myfclose() +{ + return CloseHandle(fh); +} + + +// Note; Only supports SEEK_CUR for http (which is what is used) +int HeaderWav::myfseek(long offset, DWORD origin) +{ + return SetFilePointer(fh, offset, 0, origin); + +} + +/* TODO: +__int64 myFileSeek (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; +} +*/
\ No newline at end of file diff --git a/Src/Plugins/Input/in_dshow/header_wav.h b/Src/Plugins/Input/in_dshow/header_wav.h new file mode 100644 index 00000000..5ff506d8 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/header_wav.h @@ -0,0 +1,24 @@ +#ifndef NULLSOFT_IN_DSHOW_HEADER_WAV_H +#define NULLSOFT_IN_DSHOW_HEADER_WAV_H + +#include <windows.h> + +#include "Header.h" + +class HeaderWav : public Header +{ +public: + HeaderWav(bool bAllowHttpConnection = false); // bAllowHttp will allow an http connection to read header info + int getInfos(const wchar_t *filename, bool checkMetadata=false); + unsigned __int32 read_dword() { unsigned __int32 v=0; myfread(&v,sizeof(v),1); return v; } + +private: + HANDLE fh; + + bool bAllowHttp; + size_t myfread( void *buffer, size_t size, size_t count); + int myfclose(); + int myfseek(long offset, DWORD origin); +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_dshow/in_dshow.dsp b/Src/Plugins/Input/in_dshow/in_dshow.dsp new file mode 100644 index 00000000..9d233c8d --- /dev/null +++ b/Src/Plugins/Input/in_dshow/in_dshow.dsp @@ -0,0 +1,201 @@ +# Microsoft Developer Studio Project File - Name="in_dshow" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=in_dshow - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "in_dshow.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "in_dshow.mak" CFG="in_dshow - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "in_dshow - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "in_dshow - Win32 Debug Winamp" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "in_dshow - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "in_dshow_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../dshow/dshow" /I "../dshow/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "in_dshow_EXPORTS" /D "USE_ASM" /D "NO_LAYER12" /D "DLL_DECODER_SUPPORT" /D "BUILTIN_MP3_SUPPORT" /D "BUILTIN_VP3_SUPPORT" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 ../dshow/strmbase.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib wsock32.lib ../dshow/strmiids.lib /nologo /dll /machine:I386 /out:"$(ProgramFiles)\winamp\plugins\in_dshow.dll" /opt:nowin98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "in_dshow - Win32 Debug Winamp" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "in_dshow___Win32_Debug_Winamp" +# PROP BASE Intermediate_Dir "in_dshow___Win32_Debug_Winamp" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "in_dshow___Win32_Debug_Winamp" +# PROP Intermediate_Dir "in_dshow___Win32_Debug_Winamp" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../dshow/dshow" /I "../dshow/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "in_dshow_EXPORTS" /D "USE_ASM" /D "NO_LAYER12" /D "DLL_DECODER_SUPPORT" /D "BUILTIN_MP3_SUPPORT" /D "BUILTIN_VP3_SUPPORT" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../dshow/dshow" /I "../dshow/include" /I "../WinAmpX" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "in_dshow_EXPORTS" /D "USE_ASM" /D "NO_LAYER12" /D "DLL_DECODER_SUPPORT" /D "BUILTIN_MP3_SUPPORT" /D "BUILTIN_VP3_SUPPORT" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 ../dshow/strmbasd.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib wsock32.lib ../dshow/strmiids.lib /nologo /dll /debug /machine:I386 /out:"c:\progra~1\winamp\plugins\in_dshow.dll" /pdbtype:sept +# ADD LINK32 ../dshow/strmbasd.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib wsock32.lib ../dshow/strmiids.lib /nologo /dll /debug /machine:I386 /out:"C:/Program Files/Winamp/Plugins/in_dshow.dll" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "in_dshow - Win32 Release" +# Name "in_dshow - Win32 Debug Winamp" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\audioswitch.cpp +# End Source File +# Begin Source File + +SOURCE=.\config.cpp +# End Source File +# Begin Source File + +SOURCE=.\dshowrender.cpp +# End Source File +# Begin Source File + +SOURCE=.\header_asf.cpp +# End Source File +# Begin Source File + +SOURCE=.\header_avi.cpp +# End Source File +# Begin Source File + +SOURCE=.\header_mpg.cpp +# End Source File +# Begin Source File + +SOURCE=.\info.cpp +# End Source File +# Begin Source File + +SOURCE=.\Main.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\grabber.h +# End Source File +# Begin Source File + +SOURCE=.\header_asf.h +# End Source File +# Begin Source File + +SOURCE=.\header_avi.h +# End Source File +# Begin Source File + +SOURCE=.\header_mpg.h +# End Source File +# Begin Source File + +SOURCE=.\IN2.H +# End Source File +# Begin Source File + +SOURCE=.\OUT.H +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=.\in_dshow.rc +# End Source File +# End Group +# Begin Group "jnetlib" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\jnetlib\asyncdns.cpp +# End Source File +# Begin Source File + +SOURCE=..\jnetlib\asyncdns.h +# End Source File +# Begin Source File + +SOURCE=..\jnetlib\connection.cpp +# End Source File +# Begin Source File + +SOURCE=..\jnetlib\connection.h +# End Source File +# Begin Source File + +SOURCE=..\jnetlib\httpget.cpp +# End Source File +# Begin Source File + +SOURCE=..\jnetlib\httpget.h +# End Source File +# Begin Source File + +SOURCE=..\jnetlib\util.cpp +# End Source File +# Begin Source File + +SOURCE=..\jnetlib\util.h +# End Source File +# End Group +# End Target +# End Project diff --git a/Src/Plugins/Input/in_dshow/in_dshow.dsw b/Src/Plugins/Input/in_dshow/in_dshow.dsw new file mode 100644 index 00000000..6731c5d7 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/in_dshow.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "in_dshow"=.\in_dshow.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/Src/Plugins/Input/in_dshow/in_dshow.rc b/Src/Plugins/Input/in_dshow/in_dshow.rc new file mode 100644 index 00000000..1ee89df7 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/in_dshow.rc @@ -0,0 +1,197 @@ +// 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 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_CONFIG DIALOGEX 0, 0, 179, 89 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Nullsoft DirectShow Decoder Configuration" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "File Association",IDC_STATIC,4,5,171,62 + LTEXT "Extension list (semicolon delimited):",IDC_STATIC,10,17,114,8 + EDITTEXT IDC_TYPES,10,29,159,14,ES_AUTOHSCROLL + PUSHBUTTON "Set to default",IDC_DEFAULTTYPES,10,48,54,13 + LTEXT "Default: ""MPG;MPEG;M2V;AVI""",IDC_STATIC,68,50,99,8 + DEFPUSHBUTTON "OK",IDOK,71,71,50,13 + PUSHBUTTON "Cancel",IDCANCEL,125,71,50,13 +END + +IDD_FILEINFO DIALOGEX 0, 0, 263, 180 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "DirectShow File Info" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_FILENAME,4,5,255,13,ES_AUTOHSCROLL | ES_READONLY + GROUPBOX "Info",IDC_STATIC,4,22,255,50 + LTEXT "",IDC_INFO,11,32,242,36 + GROUPBOX "Currently Used Filters (double-click to set properties)",IDC_STATIC,4,75,255,81 + LISTBOX IDC_FILTERLIST,11,87,242,53,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + LTEXT "",IDC_FPS,11,143,242,8 + DEFPUSHBUTTON "OK",IDOK,209,162,50,13 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_CONFIG, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 175 + TOPMARGIN, 5 + BOTTOMMARGIN, 84 + END + + IDD_FILEINFO, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 259 + TOPMARGIN, 5 + BOTTOMMARGIN, 175 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_DSHOW_PLUGIN "Nullsoft DirectShow Decoder v%s" + 65535 "{20395FD0-AC67-446d-B8D0-D88BFD3174FC}" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_DSHOW_PLUGIN_OLD "Nullsoft DirectShow Decoder" + IDS_VIDEO_FILES_OFD "Video Files (" + IDS_VIDEO_SIZE "Video size: %dx%d\n" + IDS_VIDEO "Video: %dx%d " + IDS_VIDEO_KBPS "Video: %dx%d (%dkbps) " + IDS_VIDEO_SIZE_KBPS "Video size: %dx%d (%dkbps)\n" + IDS_AUDIO "Audio: %dhz, %dbit, %dch\n" + IDS_AUDIO_KBPS "Audio: %dhz, %dbit, %dch (%dkbps)\n" + IDS_AUDIO_S "Audio: %dkhz %dbit %s " + IDS_STRING11 "Audio: %dkhz %dbit %s" + IDS_AUDIO_CH "Audio: %dhz, %dbit, %dch\n" + IDS_STEREO "Stereo" + IDS_MONO "Mono" + IDS_LENGTH "Length: %d:%02d:%02d\nBitrate: %dkbps" +END + +STRINGTABLE +BEGIN + IDS_DURATION "(%d:%02d:%02d) %dkbps" + IDS_NO_AVAILABLE_INFO "No available info" + IDS_AVERAGE_FPS "Average FPS: %.02f" + IDS_BUFFERING "Buffering (%d%%)" + IDS_FPS "fps" + IDS_FAMILY_STRING_MPEG "MPEG Video Format" + IDS_FAMILY_STRING_MPEG2 "MPEG-2 Video Format" + IDS_FAMILY_STRING_AVI "AVI Video Format" + IDS_ABOUT_TEXT "%s\n© 2003-2023 Winamp SA\n\nBuild date: %hs" + IDS_TRACK_X "Track %d" + IDS_TRACK_1 "Track 1" + IDS_FAMILY_STRING_MOV "QuickTime Movie" + IDS_FAMILY_STRING_FLV "Flash Video" + IDS_FAMILY_STRING_OGV "Ogg Video File" + IDS_FAMILY_STRING_OGA "Ogg Audio File" + IDS_FAMILY_STRING_OGM "Ogg Media File" +END + +STRINGTABLE +BEGIN + IDS_FAMILY_STRING_RM "Real Media Video File" + IDS_FAMILY_STRING_VOB "DVD Video File" + IDS_FAMILY_STRING_AC3 "AC3 Audio File" + IDS_FAMILY_STRING_MKV "Matroska Video" + IDS_FAMILY_STRING_MP4 "MPEG-4 File Format" + IDS_FAMILY_STRING_M4V "MPEG-4 Video File Format" + IDS_FAMILY_STRING_3GPP "3GPP File Format" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// French (France) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_FRA) +#ifdef _WIN32 +LANGUAGE LANG_FRENCH, SUBLANG_FRENCH +#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 + +#endif // French (France) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#include "version.rc2" + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/Src/Plugins/Input/in_dshow/in_dshow.sln b/Src/Plugins/Input/in_dshow/in_dshow.sln new file mode 100644 index 00000000..9417bc2b --- /dev/null +++ b/Src/Plugins/Input/in_dshow/in_dshow.sln @@ -0,0 +1,44 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29424.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_dshow", "in_dshow.vcxproj", "{6BC7AB97-1F4A-4C37-AA42-9972BE904922}" + ProjectSection(ProjectDependencies) = postProject + {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsutil", "..\nsutil\nsutil.vcxproj", "{DABE6307-F8DD-416D-9DAC-673E2DECB73F}" +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 + {6BC7AB97-1F4A-4C37-AA42-9972BE904922}.Debug|Win32.ActiveCfg = Debug|Win32 + {6BC7AB97-1F4A-4C37-AA42-9972BE904922}.Debug|Win32.Build.0 = Debug|Win32 + {6BC7AB97-1F4A-4C37-AA42-9972BE904922}.Debug|x64.ActiveCfg = Debug|x64 + {6BC7AB97-1F4A-4C37-AA42-9972BE904922}.Debug|x64.Build.0 = Debug|x64 + {6BC7AB97-1F4A-4C37-AA42-9972BE904922}.Release|Win32.ActiveCfg = Release|Win32 + {6BC7AB97-1F4A-4C37-AA42-9972BE904922}.Release|Win32.Build.0 = Release|Win32 + {6BC7AB97-1F4A-4C37-AA42-9972BE904922}.Release|x64.ActiveCfg = Release|x64 + {6BC7AB97-1F4A-4C37-AA42-9972BE904922}.Release|x64.Build.0 = Release|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.ActiveCfg = Debug|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.Build.0 = Debug|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.ActiveCfg = Debug|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.Build.0 = Debug|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.ActiveCfg = Release|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.Build.0 = Release|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.ActiveCfg = Release|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B6180127-9081-4796-819C-0E19EBFB2BC4} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_dshow/in_dshow.vcxproj b/Src/Plugins/Input/in_dshow/in_dshow.vcxproj new file mode 100644 index 00000000..795bf801 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/in_dshow.vcxproj @@ -0,0 +1,369 @@ +<?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>{6BC7AB97-1F4A-4C37-AA42-9972BE904922}</ProjectGuid> + <RootNamespace>in_dshow</RootNamespace> + <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" 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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|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> + <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;base;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_WIN32_DCOM;_DEBUG;WIN32;_WINDOWS;_USRDLL;in_dshow_EXPORTS;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;UNICODE_INPUT_PLUGIN;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>winmm.lib;wsock32.lib;strmiids.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>winmm.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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;base;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_WIN32_DCOM;_DEBUG;WIN64;_WINDOWS;_USRDLL;in_dshow_EXPORTS;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;UNICODE_INPUT_PLUGIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>winmm.lib;wsock32.lib;strmiids.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>winmm.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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> + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;base;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>NDEBUG;_WIN32_DCOM;WIN32;_WINDOWS;_USRDLL;in_dshow_EXPORTS;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;UNICODE_INPUT_PLUGIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>winmm.lib;wsock32.lib;strmiids.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>winmm.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <SubSystem>Windows</SubSystem> + </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> + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;base;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>NDEBUG;_WIN32_DCOM;WIN64;_WINDOWS;_USRDLL;in_dshow_EXPORTS;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;UNICODE_INPUT_PLUGIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>winmm.lib;wsock32.lib;strmiids.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>winmm.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <SubSystem>Windows</SubSystem> + </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="audioswitch.cpp" /> + <ClCompile Include="base\amextra.cpp" /> + <ClCompile Include="base\amfilter.cpp" /> + <ClCompile Include="base\amvideo.cpp" /> + <ClCompile Include="base\arithutil.cpp" /> + <ClCompile Include="base\combase.cpp" /> + <ClCompile Include="base\cprop.cpp" /> + <ClCompile Include="base\ctlutil.cpp" /> + <ClCompile Include="base\ddmm.cpp" /> + <ClCompile Include="base\dllentry.cpp" /> + <ClCompile Include="base\dllsetup.cpp" /> + <ClCompile Include="base\mtype.cpp" /> + <ClCompile Include="base\outputq.cpp" /> + <ClCompile Include="base\perflog.cpp" /> + <ClCompile Include="base\pstream.cpp" /> + <ClCompile Include="base\pullpin.cpp" /> + <ClCompile Include="base\refclock.cpp" /> + <ClCompile Include="base\renbase.cpp" /> + <ClCompile Include="base\schedule.cpp" /> + <ClCompile Include="base\seekpt.cpp" /> + <ClCompile Include="base\source.cpp" /> + <ClCompile Include="base\strmctl.cpp" /> + <ClCompile Include="base\sysclock.cpp" /> + <ClCompile Include="base\transfrm.cpp" /> + <ClCompile Include="base\transip.cpp" /> + <ClCompile Include="base\videoctl.cpp" /> + <ClCompile Include="base\vtrans.cpp" /> + <ClCompile Include="base\winctrl.cpp" /> + <ClCompile Include="base\winutil.cpp" /> + <ClCompile Include="base\wxdebug.cpp" /> + <ClCompile Include="base\wxlist.cpp" /> + <ClCompile Include="base\wxutil.cpp" /> + <ClCompile Include="config.cpp" /> + <ClCompile Include="CWAAudioRenderer.cpp" /> + <ClCompile Include="CWAVideoRenderer.cpp" /> + <ClCompile Include="DSTrackSelector.cpp" /> + <ClCompile Include="Header.cpp" /> + <ClCompile Include="header_asf.cpp" /> + <ClCompile Include="header_avi.cpp" /> + <ClCompile Include="header_mpg.cpp" /> + <ClCompile Include="header_wav.cpp" /> + <ClCompile Include="info.cpp" /> + <ClCompile Include="Main.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="base\amextra.h" /> + <ClInclude Include="base\amfilter.h" /> + <ClInclude Include="base\cache.h" /> + <ClInclude Include="base\combase.h" /> + <ClInclude Include="base\cprop.h" /> + <ClInclude Include="base\ctlutil.h" /> + <ClInclude Include="base\ddmm.h" /> + <ClInclude Include="base\dxmperf.h" /> + <ClInclude Include="base\dllsetup.h" /> + <ClInclude Include="base\fourcc.h" /> + <ClInclude Include="base\measure.h" /> + <ClInclude Include="base\msgthrd.h" /> + <ClInclude Include="base\mtype.h" /> + <ClInclude Include="base\outputq.h" /> + <ClInclude Include="base\perflog.h" /> + <ClInclude Include="base\perfstruct.h" /> + <ClInclude Include="base\pstream.h" /> + <ClInclude Include="base\pullpin.h" /> + <ClInclude Include="base\refclock.h" /> + <ClInclude Include="base\reftime.h" /> + <ClInclude Include="base\renbase.h" /> + <ClInclude Include="base\schedule.h" /> + <ClInclude Include="base\seekpt.h" /> + <ClInclude Include="base\source.h" /> + <ClInclude Include="base\streams.h" /> + <ClInclude Include="base\strmctl.h" /> + <ClInclude Include="base\sysclock.h" /> + <ClInclude Include="base\transfrm.h" /> + <ClInclude Include="base\transip.h" /> + <ClInclude Include="base\videoctl.h" /> + <ClInclude Include="base\vtrans.h" /> + <ClInclude Include="base\winctrl.h" /> + <ClInclude Include="base\winutil.h" /> + <ClInclude Include="base\wxdebug.h" /> + <ClInclude Include="base\wxlist.h" /> + <ClInclude Include="base\wxutil.h" /> + <ClInclude Include="audioswitch.h" /> + <ClInclude Include="CSampleCB.h" /> + <ClInclude Include="CWAAudioRenderer.h" /> + <ClInclude Include="CWAVideoRenderer.h" /> + <ClInclude Include="DSTrackSelector.h" /> + <ClInclude Include="Header.h" /> + <ClInclude Include="header_asf.h" /> + <ClInclude Include="header_avi.h" /> + <ClInclude Include="header_mpg.h" /> + <ClInclude Include="header_wav.h" /> + <ClInclude Include="Main.h" /> + <ClInclude Include="resource.h" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_dshow.rc" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\..\nsutil\nsutil.vcxproj"> + <Project>{dabe6307-f8dd-416d-9dac-673e2decb73f}</Project> + </ProjectReference> + <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_dshow/in_dshow.vcxproj.filters b/Src/Plugins/Input/in_dshow/in_dshow.vcxproj.filters new file mode 100644 index 00000000..fed2948e --- /dev/null +++ b/Src/Plugins/Input/in_dshow/in_dshow.vcxproj.filters @@ -0,0 +1,296 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="base\amextra.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\amfilter.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\amvideo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\arithutil.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="audioswitch.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\combase.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="config.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\cprop.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\ctlutil.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="CWAAudioRenderer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="CWAVideoRenderer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\ddmm.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\dllentry.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\dllsetup.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="DSTrackSelector.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Header.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="header_asf.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="header_avi.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="header_mpg.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="header_wav.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="info.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\mtype.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\outputq.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\perflog.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\pstream.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\pullpin.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\refclock.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\renbase.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\schedule.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\seekpt.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\source.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\strmctl.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\sysclock.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\wxutil.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\wxlist.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\wxdebug.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\winutil.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\winctrl.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\vtrans.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\videoctl.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\transip.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="base\transfrm.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="base\amextra.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\amfilter.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="audioswitch.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\cache.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\combase.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\cprop.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="CSampleCB.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\ctlutil.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="CWAAudioRenderer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="CWAVideoRenderer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\ddmm.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\dllsetup.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="DSTrackSelector.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\dxmperf.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\fourcc.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Header.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="header_asf.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="header_avi.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="header_mpg.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="header_wav.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Main.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\measure.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\msgthrd.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\mtype.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\outputq.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\perflog.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\perfstruct.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\pstream.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\pullpin.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\refclock.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\reftime.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\renbase.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\schedule.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\seekpt.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\source.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\streams.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\strmctl.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\sysclock.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\transfrm.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\transip.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\videoctl.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\vtrans.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\winctrl.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\winutil.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\wxdebug.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\wxlist.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base\wxutil.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{0a9553df-7c95-43d2-8a68-6e8e09fbee38}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{d6a46474-31cd-4056-8d3b-4399f69135b5}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{d8df11e4-0882-466f-b8d5-2da6a506e288}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_dshow.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_dshow/info.cpp b/Src/Plugins/Input/in_dshow/info.cpp new file mode 100644 index 00000000..b53f80ba --- /dev/null +++ b/Src/Plugins/Input/in_dshow/info.cpp @@ -0,0 +1,292 @@ +#include "main.h" +#include "resource.h" +//#include <qedit.h> +#include "header_asf.h" +#include "header_avi.h" +#include "header_mpg.h" +#include "header_wav.h" +#include "../nu/AutoChar.h" +#include "../Agave/Language/api_language.h" + +#include <strsafe.h> + +static const wchar_t *m_fn; + +extern const wchar_t *extension(const wchar_t *fn); +extern wchar_t m_lastfn[]; +extern unsigned int m_nbframes; +extern DWORD m_avgfps_start; + +static __int64 FileSize64(HANDLE file) +{ + LARGE_INTEGER position; + position.QuadPart=0; + position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart); + + if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) + return INVALID_FILE_SIZE; + else + return position.QuadPart; +} + +int GetFileLength(const wchar_t *filename); +void getInfo(const wchar_t *fn, wchar_t *linetext, int linetextCch, wchar_t *fulltext, int fulltextCch, int *bitrate, int *channel) +{ + wchar_t tmp[512] = {0, }; + wchar_t tmpline[512] = {0, }; + int br = 0; + + const wchar_t *ext = extension(fn); + __int64 size = 0; + HANDLE hFile = CreateFileW(fn, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + size = FileSize64(hFile); + CloseHandle(hFile); + } + int l = GetFileLength(fn); + wchar_t *pTmpLine = tmpline, *pTmp = tmp; + size_t pTmpLineSize = 512, pTmpSize = 512; + + if (!_wcsicmp(ext, L"asf") || !_wcsicmp(ext, L"wmv") || !_wcsicmp(ext, L"wma")) + { + HeaderAsf ha; + if (ha.getInfos(fn)) + { + StringCchCopyExW(pTmpLine, pTmpLineSize, L"DShow (WMV): ", &pTmpLine, &pTmpLineSize, 0); + if (ha.has_video) + { + StringCchPrintfExW(pTmp, pTmpSize, &pTmp, &pTmpSize, 0, WASABI_API_LNGSTRINGW(IDS_VIDEO_SIZE), ha.video_w, ha.video_h); + StringCchPrintfExW(pTmpLine, pTmpLineSize, &pTmpLine, &pTmpLineSize, 0, WASABI_API_LNGSTRINGW(IDS_VIDEO), ha.video_w, ha.video_h); + } + /* benski> cut + if (ha.has_audio) + { + char type[16] = {0}; + StringCchPrintfEx(pTmp, pTmpSize, &pTmp, &pTmpSize, 0, WASABI_API_LNGSTRING(IDS_AUDIO), ha.audio_srate, ha.audio_bps, ha.audio_nch); + StringCchPrintfEx(pTmpLine, pTmpLineSize, &pTmpLine, &pTmpLineSize, 0, WASABI_API_LNGSTRING(IDS_AUDIO_S), ha.audio_srate / 1000, ha.audio_bps, WASABI_API_LNGSTRING_BUF((ha.audio_nch == 2 ? IDS_STEREO : IDS_MONO),type,16)); + } + int l = ha.length / 1000; + */ + + } + } + else if (!_wcsicmp(ext, L"avi") || !_wcsicmp(ext, L"divx")) + { + HeaderAvi ha; + if (ha.getInfos(fn)) + { + + StringCchCopyExW(pTmpLine, pTmpLineSize, L"DShow (AVI): ", &pTmpLine, &pTmpLineSize, 0); + if (ha.has_video) + { + StringCchPrintfExW(pTmp, pTmpSize, &pTmp, &pTmpSize, 0, WASABI_API_LNGSTRINGW(IDS_VIDEO_SIZE), ha.video_w, ha.video_h); + StringCchPrintfExW(pTmpLine, pTmpLineSize, &pTmpLine, &pTmpLineSize, 0, WASABI_API_LNGSTRINGW(IDS_VIDEO), ha.video_w, ha.video_h); + } + if (ha.has_audio) + { + wchar_t type[16] = {0}; + StringCchPrintfExW(pTmp, pTmpSize, &pTmp, &pTmpSize, 0, WASABI_API_LNGSTRINGW(IDS_AUDIO), ha.audio_srate, ha.audio_bps, ha.audio_nch); + StringCchPrintfExW(pTmpLine, pTmpLineSize, &pTmpLine, &pTmpLineSize, 0, WASABI_API_LNGSTRINGW(IDS_AUDIO_S), ha.audio_srate / 1000, ha.audio_bps, WASABI_API_LNGSTRINGW_BUF((ha.audio_nch == 2 ? IDS_STEREO : IDS_MONO),type,16)); + } + /* benski> cut + int l = ha.length / 1000; + if (l) + { + br = (int)(((double)size * 8 / 1000) / l); + StringCchPrintfEx(pTmp, pTmpSize, &pTmp, &pTmpSize, 0, WASABI_API_LNGSTRING(IDS_LENGTH), l / 3600, (l / 60) % 60, l % 60, br); + StringCchPrintfEx(pTmpLine, pTmpLineSize, &pTmpLine, &pTmpLineSize, 0, WASABI_API_LNGSTRING(IDS_DURATION), l / 3600, (l / 60) % 60, l % 60, br); + } + */ + } + if (channel) + { + *channel = ha.audio_nch; + } + } + else if (!_wcsicmp(ext, L"mpg") || !_wcsicmp(ext, L"mpeg")) + { + HeaderMpg ha; + if (ha.getInfos(fn)) + { + StringCchCopyExW(pTmpLine, pTmpLineSize, L"DShow (MPEG): ", &pTmpLine, &pTmpLineSize, 0); + if (ha.has_video) + { + StringCchPrintfExW(pTmp, pTmpSize, &pTmp, &pTmpSize, 0, WASABI_API_LNGSTRINGW(IDS_VIDEO_SIZE_KBPS), ha.video_w, ha.video_h, ha.video_bitrate / 1000); + StringCchPrintfExW(pTmpLine, pTmpLineSize, &pTmpLine, &pTmpLineSize, 0, WASABI_API_LNGSTRINGW(IDS_VIDEO_KBPS), ha.video_w, ha.video_h, ha.video_bitrate / 1000); + } + if (ha.has_audio) + { + wchar_t type[16] = {0}; + StringCchPrintfExW(pTmp, pTmpSize, &pTmp, &pTmpSize, 0, WASABI_API_LNGSTRINGW(IDS_AUDIO_KBPS), ha.audio_srate, ha.audio_bps, ha.audio_nch, ha.audio_bitrate / 1000); + StringCchPrintfExW(pTmpLine, pTmpLineSize, &pTmpLine, &pTmpLineSize, 0, WASABI_API_LNGSTRINGW(IDS_AUDIO_S), ha.audio_srate / 1000, ha.audio_bps, WASABI_API_LNGSTRINGW_BUF((ha.audio_nch == 2 ? IDS_STEREO : IDS_MONO),type,16), ha.audio_bitrate / 1000); + } + /* benski> cut + int l = ha.length / 1000; + if (l) + { + br = (int)(((double)size * 8 / 1000) / l); + StringCchPrintfEx(pTmp, pTmpSize, &pTmp, &pTmpSize, 0, WASABI_API_LNGSTRING(IDS_LENGTH), l / 3600, (l / 60) % 60, l % 60, br); + StringCchPrintfEx(pTmpLine, pTmpLineSize, &pTmpLine, &pTmpLineSize, 0, WASABI_API_LNGSTRING(IDS_DURATION), l / 3600, (l / 60) % 60, l % 60, br); + } + */ + } + } + else if (!_wcsicmp(ext, L"wav")) + { + HeaderWav ha; + if (ha.getInfos(fn)) + { + + StringCchCopyExW(pTmpLine, pTmpLineSize, L"DShow (WAV): ", &pTmpLine, &pTmpLineSize, 0); + if (ha.has_audio) + { + char type[16] = {0}; + StringCchPrintfExW(pTmp, pTmpSize, &pTmp, &pTmpSize, 0, WASABI_API_LNGSTRINGW(IDS_AUDIO_CH), ha.audio_srate, ha.audio_bps, ha.audio_nch); + StringCchPrintfExW(pTmpLine, pTmpLineSize, &pTmpLine, &pTmpLineSize, 0, WASABI_API_LNGSTRINGW(IDS_AUDIO_S), ha.audio_srate / 1000, ha.audio_bps, WASABI_API_LNGSTRING_BUF((ha.audio_nch == 2 ? IDS_STEREO : IDS_MONO),type,16)); + } + /* benski> cut + int l = ha.length / 1000; + if (l) + { + br = (int)(((double)size * 8 / 1000) / l); + StringCchPrintfEx(pTmp, pTmpSize, &pTmp, &pTmpSize, 0, WASABI_API_LNGSTRING(IDS_LENGTH), l / 3600, (l / 60) % 60, l % 60, br); + StringCchPrintfEx(pTmpLine, pTmpLineSize, &pTmpLine, &pTmpLineSize, 0, WASABI_API_LNGSTRING(IDS_DURATION), l / 3600, (l / 60) % 60, l % 60, br); + } + else + br = MulDiv(ha.audio_bps*ha.audio_nch, ha.audio_srate, 1000); + */ + } + + } + + if (l != -1000) + { + br = (int)(((double)size * 8.0) / l); + l/=1000; + StringCchPrintfExW(pTmp, pTmpSize, &pTmp, &pTmpSize, 0, WASABI_API_LNGSTRINGW(IDS_LENGTH), l / 3600, (l / 60) % 60, l % 60, br); + StringCchPrintfExW(pTmpLine, pTmpLineSize, &pTmpLine, &pTmpLineSize, 0, WASABI_API_LNGSTRINGW(IDS_DURATION), l / 3600, (l / 60) % 60, l % 60, br); + } + + + if (linetext) + lstrcpynW(linetext, tmpline, linetextCch); + if (fulltext) + lstrcpynW(fulltext, tmp, fulltextCch); + if (bitrate) + *bitrate = br; +} + +INT_PTR CALLBACK infoProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + SetDlgItemTextW(hwndDlg, IDC_FILENAME, m_fn); + SetDlgItemTextW(hwndDlg, IDC_INFO, WASABI_API_LNGSTRINGW(IDS_NO_AVAILABLE_INFO)); + SetDlgItemTextW(hwndDlg, IDC_FPS, L""); + + { + wchar_t text[512] = {0}; + getInfo(m_fn, NULL, 0, text, 512, NULL, NULL); + SetDlgItemTextW(hwndDlg, IDC_INFO, text); + } + + if (!_wcsicmp(m_fn, m_lastfn) && pGraphBuilder) + { + CComPtr<IEnumFilters> pEnumFilters; + pGraphBuilder->EnumFilters(&pEnumFilters); + if (!pEnumFilters) break; + + pEnumFilters->Reset(); + do + { + ULONG nfetched = 0; + CComPtr<IBaseFilter> pFilter; + pEnumFilters->Next(1, &pFilter, &nfetched); + if (!nfetched) + break; + + FILTER_INFO fi; + pFilter->QueryFilterInfo(&fi); + + if (!_wcsicmp(fi.achName, L"Null Audio") + || !_wcsicmp(fi.achName, L"Null Video") + || !_wcsicmp(fi.achName, m_fn)) + { + if (fi.pGraph) + fi.pGraph->Release(); + continue; + } + + LRESULT a = SendDlgItemMessageW(hwndDlg, IDC_FILTERLIST, LB_ADDSTRING, 0, (LPARAM)fi.achName); + SendDlgItemMessage(hwndDlg, IDC_FILTERLIST, LB_SETITEMDATA, a, (LPARAM)(IBaseFilter *)pFilter); //FUCKO: if playback changes + + if (fi.pGraph) + fi.pGraph->Release(); + } + while (1); + + SendMessage(hwndDlg, WM_TIMER, 0x123, 0); + SetTimer(hwndDlg, 0x123, 500, NULL); + } + + return TRUE; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_FILTERLIST: + if (HIWORD(wParam) == LBN_DBLCLK) + { + LRESULT sel = SendDlgItemMessage(hwndDlg, IDC_FILTERLIST, LB_GETCURSEL, 0, 0); + if (sel == LB_ERR) break; + + CComPtr<IBaseFilter> pFilter; + pFilter = (IBaseFilter *)SendDlgItemMessage(hwndDlg, IDC_FILTERLIST, LB_GETITEMDATA, sel, 0); + + CComQIPtr<ISpecifyPropertyPages> pSPP(pFilter); + if (!pSPP) break; + + CAUUID pages; + pSPP->GetPages(&pages); + + IBaseFilter *f = pFilter; + OleCreatePropertyFrame( + hwndDlg, 30, 30, NULL, + 1, (IUnknown**)&f, + pages.cElems, pages.pElems, + 0, 0, NULL); + CoTaskMemFree(pages.pElems); + } + break; + case IDOK: + case IDCANCEL: + EndDialog(hwndDlg, 0); + break; + } + break; + case WM_TIMER: + if (wParam == 0x123 && pGraphBuilder && !_wcsicmp(m_fn, m_lastfn)) + { + DWORD t = GetTickCount() - m_avgfps_start; + if (t) + { + wchar_t tmp[512] = {0}; + StringCchPrintfW(tmp, 512, WASABI_API_LNGSTRINGW(IDS_AVERAGE_FPS), (double)m_nbframes*1000 / t); + SetDlgItemTextW(hwndDlg, IDC_FPS, tmp); + } + } + break; + } + return FALSE; +} + +void doInfo(HINSTANCE hInstance, HWND hwndParent, const wchar_t *fn) +{ + static int running; + if (running) return ; + running = 1; + m_fn = fn; + WASABI_API_DIALOGBOXW(IDD_FILEINFO, hwndParent, infoProc); + running = 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_dshow/resource.h b/Src/Plugins/Input/in_dshow/resource.h new file mode 100644 index 00000000..c39b6f1f --- /dev/null +++ b/Src/Plugins/Input/in_dshow/resource.h @@ -0,0 +1,67 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by in_dshow.rc +// +#define IDS_NULLSOFT_DSHOW_PLUGIN_OLD 0 +#define IDS_DSHOW_ABOUT 2 +#define IDS_VIDEO_FILES_OFD 3 +#define IDS_VIDEO_SIZE 4 +#define IDS_VIDEO 5 +#define IDS_VIDEO_KBPS 6 +#define IDS_VIDEO_SIZE_KBPS 7 +#define IDS_AUDIO 8 +#define IDS_AUDIO_KBPS 9 +#define IDS_AUDIO_S 10 +#define IDS_STRING11 11 +#define IDS_AUDIO_CH 12 +#define IDS_STEREO 13 +#define IDS_MONO 14 +#define IDS_LENGTH 15 +#define IDS_DURATION 16 +#define IDS_NO_AVAILABLE_INFO 17 +#define IDS_AVERAGE_FPS 18 +#define IDS_STRING19 19 +#define IDS_BUFFERING 19 +#define IDS_STRING20 20 +#define IDS_FPS 20 +#define IDS_FAMILY_STRING_MPEG 21 +#define IDS_FAMILY_STRING_MPEG2 22 +#define IDS_FAMILY_STRING_AVI 23 +#define IDS_DSHOW_ABOUT_TEXT 24 +#define IDS_ABOUT_TEXT 24 +#define IDS_TRACK_X 25 +#define IDS_STRING26 26 +#define IDS_TRACK_1 26 +#define IDS_FAMILY_STRING_MOV 27 +#define IDS_FAMILY_STRING_FLV 28 +#define IDS_FAMILY_STRING_OGV 29 +#define IDS_FAMILY_STRING_OGA 30 +#define IDS_FAMILY_STRING_OGM 31 +#define IDS_FAMILY_STRING_RM 32 +#define IDS_FAMILY_STRING_VOB 33 +#define IDS_FAMILY_STRING_AC3 34 +#define IDS_FAMILY_STRING_MKV 35 +#define IDS_FAMILY_STRING_MP4 36 +#define IDS_FAMILY_STRING_M4V 37 +#define IDS_FAMILY_STRING_3GPP 38 +#define IDD_FILEINFO 102 +#define IDD_CONFIG 103 +#define IDC_TYPES 1000 +#define IDC_FLIPWMV 1001 +#define IDC_DEFAULTTYPES 1002 +#define IDC_FILENAME 1004 +#define IDC_FILTERLIST 1005 +#define IDC_INFO 1007 +#define IDC_FPS 1008 +#define IDS_NULLSOFT_DSHOW_PLUGIN 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 109 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1009 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_dshow/version.rc2 b/Src/Plugins/Input/in_dshow/version.rc2 new file mode 100644 index 00000000..cc49744d --- /dev/null +++ b/Src/Plugins/Input/in_dshow/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,15,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", "1,15,0,0" + VALUE "InternalName", "Nullsoft DirectShow Decoder" + VALUE "LegalCopyright", "Copyright © 2003-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_dshow.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_flac/About.cpp b/Src/Plugins/Input/in_flac/About.cpp new file mode 100644 index 00000000..ff5f4ccf --- /dev/null +++ b/Src/Plugins/Input/in_flac/About.cpp @@ -0,0 +1,54 @@ +/* +** Copyright © 2007-2014 Winamp SA +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ + +#include "main.h" +#include "../Agave/Language/api_language.h" +#include "resource.h" +#include <FLAC/all.h> +#include <strsafe.h> + +static HMODULE libflac=0; +static char defaultVersionString[64]; +static const char **versionString=0; +static const char *GetFLACVersion() +{ + return "1.4.2"; +} + +int DoAboutMessageBox(HWND parent, wchar_t* title, wchar_t* message) +{ + MSGBOXPARAMSW msgbx = {sizeof(MSGBOXPARAMSW),0}; + msgbx.lpszText = message; + msgbx.lpszCaption = title; + msgbx.lpszIcon = MAKEINTRESOURCEW(102); + msgbx.hInstance = GetModuleHandle(0); + msgbx.dwStyle = MB_USERICON; + msgbx.hwndOwner = parent; + return MessageBoxIndirectW(&msgbx); +} + +void About(HWND hwndParent) +{ + wchar_t message[1024] = {0}; + StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), + plugin.description, __DATE__, GetFLACVersion()); + DoAboutMessageBox(hwndParent,(wchar_t*)plugin.description,message); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/AlbumArt.cpp b/Src/Plugins/Input/in_flac/AlbumArt.cpp new file mode 100644 index 00000000..22944db3 --- /dev/null +++ b/Src/Plugins/Input/in_flac/AlbumArt.cpp @@ -0,0 +1,270 @@ +/* +** Copyright © 2007-2014 Winamp SA +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: July 25, 2007 +** +*/ + +#include "main.h" +#include "api__in_flv.h" +#include "Metadata.h" +#include "../nu/AutoWide.h" +#include "AlbumArt.h" +#include <shlwapi.h> +#include <strsafe.h> + +bool FLAC_AlbumArtProvider::IsMine(const wchar_t *filename) +{ + const wchar_t *extension = PathFindExtensionW(filename); + if (extension && *extension) + { + wchar_t exts[128] = {0}; + GetPrivateProfileStringW(L"in_flac", L"extensions", DEFAULT_EXTENSIONSW, exts, 128, winampINI); + + extension++; + wchar_t *b = exts; + wchar_t *c; + do + { + wchar_t d[20] = {0}; + StringCchCopyW(d, 15, b); + if ((c = wcschr(b, L';'))) + { + if ((c-b)<15) + d[c - b] = 0; + } + + if (!lstrcmpiW(extension, d)) + return true; + + b = c + 1; + } + while (c); + } + return false; +} + +int FLAC_AlbumArtProvider::ProviderType() +{ + return ALBUMARTPROVIDER_TYPE_EMBEDDED; +} + +bool NameToAPICType(const wchar_t *name, FLAC__StreamMetadata_Picture_Type &num) +{ + if (!name || !*name) // default to cover + num=FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER; + else if (!_wcsicmp(name, L"fileicon")) // 32x32 pixels 'file icon' (PNG only) + num=FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD; + else if (!_wcsicmp(name, L"icon")) // Other file icon + num=FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON; + else if (!_wcsicmp(name, L"cover")) // Cover (front) + num=FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER; + else if (!_wcsicmp(name, L"back")) // Cover (back) + num=FLAC__STREAM_METADATA_PICTURE_TYPE_BACK_COVER; + else if (!_wcsicmp(name, L"leaflet")) // Leaflet page + num=FLAC__STREAM_METADATA_PICTURE_TYPE_LEAFLET_PAGE; + else if (!_wcsicmp(name, L"media")) // Media (e.g. lable side of CD) + num=FLAC__STREAM_METADATA_PICTURE_TYPE_MEDIA; + else if (!_wcsicmp(name, L"leadartist")) //Lead artist/lead performer/soloist + num=FLAC__STREAM_METADATA_PICTURE_TYPE_LEAD_ARTIST; + else if (!_wcsicmp(name, L"artist")) // Artist/performer + num=FLAC__STREAM_METADATA_PICTURE_TYPE_ARTIST; + else if (!_wcsicmp(name, L"conductor")) // Conductor + num=FLAC__STREAM_METADATA_PICTURE_TYPE_CONDUCTOR; + else if (!_wcsicmp(name, L"band")) // Band/Orchestra + num=FLAC__STREAM_METADATA_PICTURE_TYPE_BAND; + else if (!_wcsicmp(name, L"composer")) // Composer + num=FLAC__STREAM_METADATA_PICTURE_TYPE_COMPOSER; + else if (!_wcsicmp(name, L"lyricist")) // Lyricist/text writer + num=FLAC__STREAM_METADATA_PICTURE_TYPE_LYRICIST; + else if (!_wcsicmp(name, L"location")) // Recording Location + num=FLAC__STREAM_METADATA_PICTURE_TYPE_RECORDING_LOCATION; + else if (!_wcsicmp(name, L"recording")) // During recording + num=FLAC__STREAM_METADATA_PICTURE_TYPE_DURING_RECORDING; + else if (!_wcsicmp(name, L"performance")) // During performance + num=FLAC__STREAM_METADATA_PICTURE_TYPE_DURING_PERFORMANCE; + else if (!_wcsicmp(name, L"preview")) // Movie/video screen capture + num=FLAC__STREAM_METADATA_PICTURE_TYPE_VIDEO_SCREEN_CAPTURE; + else if (!_wcsicmp(name, L"fish")) // A bright coloured fish + num=FLAC__STREAM_METADATA_PICTURE_TYPE_FISH; + else if (!_wcsicmp(name, L"illustration")) // Illustration + num=FLAC__STREAM_METADATA_PICTURE_TYPE_ILLUSTRATION; + else if (!_wcsicmp(name, L"artistlogo")) // Band/artist logotype + num=FLAC__STREAM_METADATA_PICTURE_TYPE_BAND_LOGOTYPE; + else if (!_wcsicmp(name, L"publisherlogo")) // Publisher/Studio logotype + num=FLAC__STREAM_METADATA_PICTURE_TYPE_PUBLISHER_LOGOTYPE; + else + return false; + return true; +} + +int FLAC_AlbumArtProvider::GetAlbumArtData(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType) +{ + FLACMetadata metadata; + + if (metadata.Open(filename)) + { + FLAC__StreamMetadata_Picture_Type pictype; + if (NameToAPICType(type, pictype)) + { + if (metadata.GetPicture(pictype, bits, len, mimeType)) + return ALBUMARTPROVIDER_SUCCESS; + } + } + + return ALBUMARTPROVIDER_FAILURE; +} + +int FLAC_AlbumArtProvider::SetAlbumArtData(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType) +{ + if (!info || info && _wcsicmp(filename, info->filename)) + { + FLACMetadata metadata; + if (metadata.Open(filename)) + { + FLAC__StreamMetadata_Picture_Type pictype; + if (NameToAPICType(type, pictype)) + { + if (metadata.SetPicture(pictype, bits, len, mimeType, 0/*TODO*/, 0/*TODO*/)) + { + if (metadata.Save(filename)) + return ALBUMARTPROVIDER_SUCCESS; + else + return ALBUMARTPROVIDER_FAILURE; + } + } + } + } + else + { + FLAC__StreamMetadata_Picture_Type pictype; + if (NameToAPICType(type, pictype)) + { + if (info->metadata.SetPicture(pictype, bits, len, mimeType, 0/*TODO*/, 0/*TODO*/)) + { + return ALBUMARTPROVIDER_SUCCESS; + } + } + } + + return ALBUMARTPROVIDER_FAILURE; +} + +int FLAC_AlbumArtProvider::DeleteAlbumArt(const wchar_t *filename, const wchar_t *type) +{ + if (!info || info && _wcsicmp(filename, info->filename)) + { + FLACMetadata metadata; + if (metadata.Open(filename)) + { + FLAC__StreamMetadata_Picture_Type pictype; + if (NameToAPICType(type, pictype)) + { + if (info->metadata.RemovePicture(pictype)) + { + if (info->metadata.Save(filename)) + return ALBUMARTPROVIDER_SUCCESS; + else + return ALBUMARTPROVIDER_FAILURE; + } + } + } + } + else + { + FLAC__StreamMetadata_Picture_Type pictype; + if (NameToAPICType(type, pictype)) + { + if (info->metadata.RemovePicture(pictype)) + { + return ALBUMARTPROVIDER_SUCCESS; + } + } + } + + return ALBUMARTPROVIDER_FAILURE; +} + +#define CBCLASS FLAC_AlbumArtProvider +START_DISPATCH; +CB(SVC_ALBUMARTPROVIDER_PROVIDERTYPE, ProviderType); +CB(SVC_ALBUMARTPROVIDER_GETALBUMARTDATA, GetAlbumArtData); +CB(SVC_ALBUMARTPROVIDER_ISMINE, IsMine); +CB(SVC_ALBUMARTPROVIDER_SETALBUMARTDATA, SetAlbumArtData); +CB(SVC_ALBUMARTPROVIDER_DELETEALBUMART, DeleteAlbumArt); +END_DISPATCH; +#undef CBCLASS + +static FLAC_AlbumArtProvider albumArtProvider; + +// {622C3B42-866E-4935-AA52-3B456AE8B036} +static const GUID flac_albumartproviderGUID = + { 0x622c3b42, 0x866e, 0x4935, { 0xaa, 0x52, 0x3b, 0x45, 0x6a, 0xe8, 0xb0, 0x36 } }; + +FOURCC AlbumArtFactory::GetServiceType() +{ + return svc_albumArtProvider::SERVICETYPE; +} + +const char *AlbumArtFactory::GetServiceName() +{ + return "FLAC Album Art Provider"; +} + +GUID AlbumArtFactory::GetGUID() +{ + return flac_albumartproviderGUID; +} + +void *AlbumArtFactory::GetInterface(int global_lock) +{ + return &albumArtProvider; +} + +int AlbumArtFactory::SupportNonLockingInterface() +{ + return 1; +} + +int AlbumArtFactory::ReleaseInterface(void *ifc) +{ + //plugin.service->service_unlock(ifc); + return 1; +} + +const char *AlbumArtFactory::GetTestString() +{ + return 0; +} + +int AlbumArtFactory::ServiceNotify(int msg, int param1, int param2) +{ + return 1; +} + +#define CBCLASS AlbumArtFactory +START_DISPATCH; +CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType) +CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName) +CB(WASERVICEFACTORY_GETGUID, GetGUID) +CB(WASERVICEFACTORY_GETINTERFACE, GetInterface) +CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface) +CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface) +CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString) +CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/AlbumArt.h b/Src/Plugins/Input/in_flac/AlbumArt.h new file mode 100644 index 00000000..11975e02 --- /dev/null +++ b/Src/Plugins/Input/in_flac/AlbumArt.h @@ -0,0 +1,38 @@ +#ifndef NULLSOFT_IN_FLAC_ALBUMART_H +#define NULLSOFT_IN_FLAC_ALBUMART_H + +#include "../Agave/AlbumArt/svc_albumArtProvider.h" + +class FLAC_AlbumArtProvider : public svc_albumArtProvider +{ +public: + bool IsMine(const wchar_t *filename); + int ProviderType(); + // implementation note: use WASABI_API_MEMMGR to alloc bits and mimetype, so that the recipient can free through that + int GetAlbumArtData(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType); + int SetAlbumArtData(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType); + int DeleteAlbumArt(const wchar_t *filename, const wchar_t *type); +protected: + RECVS_DISPATCH; +}; + +#include <api/service/waservicefactory.h> + +class AlbumArtFactory : public waServiceFactory +{ +public: + FOURCC GetServiceType(); + const char *GetServiceName(); + GUID GetGUID(); + void *GetInterface(int global_lock); + int SupportNonLockingInterface(); + int ReleaseInterface(void *ifc); + const char *GetTestString(); + int ServiceNotify(int msg, int param1, int param2); + +protected: + RECVS_DISPATCH; +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/DecodeThread.cpp b/Src/Plugins/Input/in_flac/DecodeThread.cpp new file mode 100644 index 00000000..38c7fcb6 --- /dev/null +++ b/Src/Plugins/Input/in_flac/DecodeThread.cpp @@ -0,0 +1,719 @@ +/* +** Copyright (C) 2007-2011 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include "main.h" +#include <windows.h> +#include <math.h> +#include <assert.h> +#include <locale.h> +#include <FLAC/all.h> +#include "StreamFileWin32.h" +#include "../Winamp/wa_ipc.h" +#include "QuickBuf.h" +#include "api__in_flv.h" +#include "../nu/AudioOutput.h" +#include "../Agave/Language/api_language.h" +#include "FLACFileCallbacks.h" +#include "nx/nxpath.h" + +int m_force_seek = -1; // el hacko grande +const DWORD PAUSE_TIMEOUT = 100; // number of milliseconds to sleep for when paused + +static bool paused = false; + +// could probably move this inside client_data +int realbits; +int bps; +int samplerate; +int channels; +volatile int currentSongLength=-1000; +int averageBitrate; + +// if input plugins weren't single-instance only, we could put this in thread-local-storage +static FLAC__StreamDecoder *decoder = 0; +static uint64_t fileSize = 0; +static FLAC__uint64 decodePosition = 0; + +double gain = 1.0; +static double buffer_scale=1.0; + +class FLACWait +{ +public: + int WaitOrAbort(int time_in_ms) + { + if (WaitForSingleObject(killswitch, time_in_ms) == WAIT_OBJECT_0) + return 1; + return 0; + } +}; + + +volatile int bufferCount=0; +static void Buffering(int bufStatus, const wchar_t *displayString) +{ + if (bufStatus < 0 || bufStatus > 100) + return; + + char tempdata[75*2] = {0, }; + + int csa = plugin.SAGetMode(); + if (csa & 1) + { + for (int x = 0; x < bufStatus*75 / 100; x ++) + tempdata[x] = x * 16 / 75; + } + else if (csa&2) + { + int offs = (csa & 1) ? 75 : 0; + int x = 0; + while (x < bufStatus*75 / 100) + { + tempdata[offs + x++] = -6 + x * 14 / 75; + } + while (x < 75) + { + tempdata[offs + x++] = 0; + } + } + else if (csa == 4) + { + tempdata[0] = tempdata[1] = (bufStatus * 127 / 100); + } + if (csa) plugin.SAAdd(tempdata, ++bufferCount, (csa == 3) ? 0x80000003 : csa); + + /* + TODO + wchar_t temp[64] = {0}; + StringCchPrintf(temp, 64, L"%s: %d%%",displayString, bufStatus); + SetStatus(temp); + */ + //SetVideoStatusText(temp); // TODO: find a way to set the old status back + // videoOutput->notifyBufferState(static_cast<int>(bufStatus*2.55f)); +} + +static nu::AudioOutput<FLACWait> audio_output(&plugin); + +#pragma region Truncaters +inline static void clip(double &x, double a, double b) +{ + double x1 = fabs(x - a); + double x2 = fabs(x - b); + x = x1 + (a + b); + x -= x2; + x *= 0.5; +} + +static void InterleaveAndTruncate32(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain) +{ + // TODO: this can be sped up significantly + FLAC__int32 *output = (FLAC__int32 *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + double appliedGain = gain * (double)buffer[c][b]; + // TODO: add dither + clip(appliedGain, -2147483648., 2147483647.); + *output = (FLAC__int32)appliedGain; + output++; + } + } +} + +static void InterleaveAndTruncate24(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain) +{ + char *output = (char *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + double appliedGain = gain * (double)buffer[c][b]; + // TODO: add dither + clip(appliedGain, -8388608., 8388607.); + FLAC__int32 sample = (FLAC__int32)appliedGain; + + // little endian specific code + output[0] = (unsigned char)(sample); + output[1] = (unsigned char)(sample >> 8); + output[2] = (unsigned char)(sample >> 16); + output += 3; + } + } +} + +static void InterleaveAndTruncate16(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain) +{ + short *output = (short *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + double appliedGain = gain * (double)buffer[c][b]; + // TODO: add dither + clip(appliedGain, -32768., 32767.); + FLAC__int32 sample = (FLAC__int32)appliedGain; + + *output = (short) sample ; + output ++; + } + } +} + +static void InterleaveAndTruncate8(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain) +{ + unsigned char *output = (unsigned char *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + double appliedGain = gain * (double)buffer[c][b]; + // TODO: add dither + clip(appliedGain, -128., 127.); + FLAC__int32 sample = (FLAC__int32)appliedGain; + + *output = (unsigned char)(sample + 128); + output++; + } + } +} + +/* --- Versions without gain adjustment --- */ + +static void InterleaveAndTruncate32(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize) +{ + FLAC__int32 *output = (FLAC__int32 *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + *output = buffer[c][b]; + output++; + } + } +} + +static void InterleaveAndTruncate24(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize) +{ + char *output = (char *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + FLAC__int32 sample = buffer[c][b]; + + // little endian specific code + output[0] = (unsigned char)(sample); + output[1] = (unsigned char)(sample >> 8); + output[2] = (unsigned char)(sample >> 16); + output += 3; + } + } +} + +static void InterleaveAndTruncate16(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize) +{ + short *output = (short *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + *output = (short) buffer[c][b]; + output ++; + } + } +} + +static void InterleaveAndTruncate8(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize) +{ + unsigned char *output = (unsigned char *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + *output = (unsigned char)(buffer[c][b] + 128); + output++; + } + } +} + +void InterleaveAndTruncate(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain) +{ + if (gain == 1.0) // if it's EXACTLY 1.0, i.e. from a hardcoded value. not meant to be a "RG happens to be 1.0" check + { + switch(bps) + { + case 8: + InterleaveAndTruncate8(buffer, _output, bps, channels, blocksize); + break; + case 16: + InterleaveAndTruncate16(buffer, _output, bps, channels, blocksize); + break; + case 24: + InterleaveAndTruncate24(buffer, _output, bps, channels, blocksize); + break; + case 32: + InterleaveAndTruncate32(buffer, _output, bps, channels, blocksize); + break; + } + } + else // apply replay gain + { + switch(bps) + { + case 8: + InterleaveAndTruncate8(buffer, _output, bps, channels, blocksize, gain); + break; + case 16: + InterleaveAndTruncate16(buffer, _output, bps, channels, blocksize, gain); + break; + case 24: + InterleaveAndTruncate24(buffer, _output, bps, channels, blocksize, gain); + break; + case 32: + InterleaveAndTruncate32(buffer, _output, bps, channels, blocksize, gain); + break; + } + } +} +#pragma endregion + +QuickBuf output; +FLAC__uint64 lastoutputtime; +static FLAC__StreamDecoderWriteStatus OnAudio(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data) +{ + // TODO: if frame bps/samplerate/channels doesn't equal our own, we'll probably have to close & re-open the audio output + // mux buffer into interleaved samples + FLAC__uint64 newPosition; + FLAC__stream_decoder_get_decode_position(decoder, &newPosition); + FLAC__uint64 delta = newPosition - decodePosition; + decodePosition = newPosition; + if (!config_average_bitrate) + plugin.SetInfo((int)(delta / (125.*frame->header.blocksize / samplerate)), -1, -1, -1); + else if (fixBitrate) + { + fixBitrate = false; + plugin.SetInfo(averageBitrate, -1, -1, -1); + } + + if (frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER) + lastoutputtime = 1000ULL*frame->header.number.sample_number / (FLAC__uint64)samplerate; + else + lastoutputtime = 0; + + int byteLength = (bps / 8) * channels * frame->header.blocksize; + output.Reserve(byteLength*2); + InterleaveAndTruncate(buffer, output, bps, channels, frame->header.blocksize, gain); + + audio_output.Write(output, byteLength); + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +static int GetBits(int incoming) +{ + int bits = AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"bits", 16); + if (bits > 16 && AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false)) + return bits; + + return min(bits, incoming); +} + +static double GetGain(const FLAC__StreamMetadata *metadata) +{ + if (AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false)) + { + if (metadata) + { + int gainPos = -1, peakPos = -1; + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_source", 0)) + { + case 0: // track + gainPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_TRACK_GAIN"); + if (gainPos < 0 && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + gainPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_ALBUM_GAIN"); + + peakPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_TRACK_PEAK"); + if (peakPos < 0 && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + peakPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_ALBUM_PEAK"); + + break; + case 1: + gainPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_ALBUM_GAIN"); + if (gainPos < 0 && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + gainPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_TRACK_GAIN"); + + peakPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_ALBUM_PEAK"); + if (peakPos < 0 && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + peakPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_TRACK_PEAK"); + break; + } + + double dB = 0, peak = 1.0; + _locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale(); + if (gainPos >= 0) + { + const char *entry = (const char *)metadata->data.vorbis_comment.comments[gainPos].entry; + const char *value = strchr(entry, '='); // find the first equal + if (value++) + { + if (value[0] == '+') + dB = _atof_l(&value[1], C_locale); + else + dB = _atof_l(value, C_locale); + } + } + else + { + dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0); + return pow(10.0, dB / 20.0); + } + + if (peakPos >= 0) + { + const char *entry = (const char *)metadata->data.vorbis_comment.comments[peakPos].entry; + const char *value = strchr(entry, '='); // find the first equal + if (value++) + { + peak = _atof_l(value, C_locale); + } + } + + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_mode", 1)) + { + case 0: // apply gain + return pow(10.0, dB / 20.0); + break; + case 1: // apply gain, but don't clip + return min(pow(10.0, dB / 20.0), 1.0 / peak); + break; + case 2: // normalize + return 1.0 / peak; + break; + case 3: // prevent clipping + if (peak > 1.0) + return 1.0 / peak; + else + return 1.0; + } + } + else + { + double dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0); + return pow(10.0, dB / 20.0); + } + } + + return 1.0; +} + +static void OnMetadata(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) +{ + switch (metadata->type) + { + case FLAC__METADATA_TYPE_STREAMINFO: + { + realbits = metadata->data.stream_info.bits_per_sample; + channels = metadata->data.stream_info.channels; + samplerate = metadata->data.stream_info.sample_rate; + bps = GetBits(metadata->data.stream_info.bits_per_sample); + gain = GetGain(0) * pow(2., (double)(bps - realbits)); + if (metadata->data.stream_info.total_samples) + { + currentSongLength = (int)((metadata->data.stream_info.total_samples*1000ULL)/(uint64_t)samplerate); + averageBitrate = (int)(fileSize / (125 * metadata->data.stream_info.total_samples / (__int64)samplerate)); + } + else + { + currentSongLength=-1000; + averageBitrate=0; + } + } + break; + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + gain = GetGain(metadata) * pow(2., (double)(bps - realbits)); + break; + } +} + +static void OnError(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + //client_data = client_data; // dummy line so i can set a breakpoint +} + +void CALLBACK APCPause(ULONG_PTR data) +{ + paused = !!data; + plugin.outMod->Pause(!!paused); +} + +void CALLBACK APCSeek(ULONG_PTR data) +{ + // TODO: break out of end-of-file handling if necessary + buffer_scale=1.0; + int time_in_ms = (int)data; + lastoutputtime=time_in_ms; // cheating a bit here :) + audio_output.Flush(time_in_ms); + __int64 frames = Int32x32To64(time_in_ms, samplerate) / 1000; + FLAC__StreamDecoderState state = FLAC__stream_decoder_get_state(decoder); + plugin.outMod->Pause(0); + FLAC__stream_decoder_seek_absolute(decoder, frames); + plugin.outMod->Pause(paused); + if (FLAC__stream_decoder_get_state(decoder) == FLAC__STREAM_DECODER_SEEK_ERROR) + { + FLAC__stream_decoder_flush(decoder); + } +} + +static const double prebuffer_seconds = 0.5; +static bool DoBuffering(nx_file_t file) +{ + // TODO: check for disconnect, etc. + + // anything less than half-a-second and we'll rebuffer. but when we rebuffer, we'll get 2 seconds worth of audio + // also, every time we're forced to re-buffer, we'll double the buffer amount + + uint64_t required; + if (averageBitrate > 0) + { + required = (uint64_t)((double)averageBitrate * 1000.0 * buffer_scale * prebuffer_seconds / 8.0); + } + else /* no bitrate specified, let's assume 1000kbps */ + { + required = (uint64_t)(1000.0 * 1000.0 * buffer_scale * prebuffer_seconds / 8.0); + } + + uint64_t available; + if (NXFileProgressiveDownloaderAvailable(file, required, &available) == NErr_True) + return true; + + Buffering(0, 0); + bufferCount=lastoutputtime; + required *= 4; + buffer_scale *= 2.0; + + while (NXFileProgressiveDownloaderAvailable(file, required, &available) == NErr_False) + { + int percent = (int)((double)available * 100.0 / (double)required); + if (percent <= 0) + percent=1; + if (percent > 99) + percent=99; + + Buffering(percent, 0); + if (WaitForSingleObject(killswitch, 10) == WAIT_OBJECT_0) + { + return false; + } + } + Buffering(100, 0); + bufferCount=0; + return true; +} + +extern HANDLE threadStarted; +DWORD CALLBACK FLACThread(LPVOID param) +{ + buffer_scale=1.0; + bool streaming=false; + nx_file_t file; + FLACClientData state; + audio_output.Init(plugin.outMod); + paused=false; + SetEvent(threadStarted); + nx_uri_t filename = (nx_uri_t)param; + decodePosition = 0; + gain = 1.0; + bufferCount = 0; + decoder = FLAC__stream_decoder_new(); + if (decoder == 0) + { + if (WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + + free(filename); + return 0; + } + + FLAC__stream_decoder_set_md5_checking(decoder, false); + FLAC__stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT); + + plugin.is_seekable = 1; + + + int ret; + + if (NXPathIsURL(filename) == NErr_True) + { + // Display a window to request to download + // + MessageBox(NULL, L"Cannot stream flac file, please download it", NULL, 0); + /* + char agent[256] = { 0 }; + snprintf(agent, 256, "User-Agent: %S/%S", "Winamp", "5.9.1"); + ret = NXFileOpenProgressiveDownloader(&file, filename, nx_file_FILE_read_binary, 0, agent); // TODO: calculate real user agent + */ + return NErr_Disabled; + } + else + { + ret = NXFileOpenFile(&file, filename, nx_file_FILE_read_binary); + } + + if (ret != NErr_Success) + { + FLAC__stream_decoder_delete(decoder); + if (WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + + NXURIRelease(filename); + return 0; + } + + state.SetFile(file); + + NXFileLength(file, &fileSize); + + if (FLAC__stream_decoder_init_stream( + decoder, + FLAC_NXFile_Read, + FLAC_NXFile_Seek, + FLAC_NXFile_Tell, + FLAC_NXFile_Length, + FLAC_NXFile_EOF, + OnAudio, + OnMetadata, // or NULL + OnError, + &state + ) != FLAC__STREAM_DECODER_INIT_STATUS_OK) + { + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + if (WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + + NXURIRelease(filename); + return 0; + } + + FLAC__stream_decoder_process_until_end_of_metadata(decoder); + + + + if (!audio_output.Open(0, channels, samplerate, bps)) + { + FLAC__stream_decoder_finish(decoder); + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + if (WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + + NXURIRelease(filename); + return 0; + } + + plugin.SetInfo(averageBitrate, -1, -1, -1); + plugin.outMod->SetVolume(volume); + plugin.outMod->SetPan(pan); + + if (streaming && !DoBuffering(file)) + { + FLAC__stream_decoder_finish(decoder); + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + NXURIRelease(filename); + + return 0; + } + + if (m_force_seek != -1) + APCSeek((ULONG_PTR)m_force_seek); + m_force_seek = -1; + + HANDLE events[] = {killswitch}; + DWORD timeout = 0; + while (true) + { + DWORD result = WaitForMultipleObjectsEx(sizeof(events) / sizeof(*events), events, FALSE, timeout, TRUE); + switch (result) + { + case WAIT_OBJECT_0:// kill thread + FLAC__stream_decoder_finish(decoder); + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + NXURIRelease(filename); + return 0; + + case WAIT_TIMEOUT: + timeout = paused ? PAUSE_TIMEOUT : 0; + if (streaming && !DoBuffering(file)) + { + FLAC__stream_decoder_finish(decoder); + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + NXURIRelease(filename); + + return 0; + } + + if (!paused) + { + FLAC__bool decode_successful = FLAC__stream_decoder_process_single(decoder); + + FLAC__StreamDecoderState FLACstate = FLAC__stream_decoder_get_state(decoder); + + if (FLACstate == FLAC__STREAM_DECODER_END_OF_STREAM) + { + audio_output.Write(0, 0); + if (audio_output.WaitWhilePlaying()) + { + FLAC__stream_decoder_finish(decoder); + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + NXURIRelease(filename); + return 0; + } + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + timeout = INFINITE; // sit and wait for killswitch + } + else if (!decode_successful) + { + // some other error - abort playback + // if we can find FLAC files with errors, we might be able to gracefully handle some errors + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + timeout = INFINITE; // sit and wait for killswitch + } + } + break; + } + } + + return 0; +} diff --git a/Src/Plugins/Input/in_flac/ExtendedFileInfo.cpp b/Src/Plugins/Input/in_flac/ExtendedFileInfo.cpp new file mode 100644 index 00000000..39405687 --- /dev/null +++ b/Src/Plugins/Input/in_flac/ExtendedFileInfo.cpp @@ -0,0 +1,424 @@ +/* +** Copyright (C) 2007-2011 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ + +#include "main.h" +#include "Metadata.h" +#include "../nu/ns_wc.h" +#include "../nu/AutoChar.h" +#include "../Winamp/wa_ipc.h" +#include <shlwapi.h> +#include "resource.h" +#include "../Agave/Language/api_language.h" +#include "Stopper.h" +#include <strsafe.h> + +static int FillFileInfo(wchar_t *infoStr, size_t len, FLACMetadata &metadata) +{ + const FLAC__StreamMetadata_StreamInfo *info = metadata.GetStreamInfo(); + if (info) + { + unsigned __int64 length = info->total_samples / info->sample_rate; + StringCchPrintfExW(infoStr, len, &infoStr, &len, 0, WASABI_API_LNGSTRINGW(IDS_LENGTH_IN_SECONDS), length); + StringCchPrintfExW(infoStr, len, &infoStr, &len, 0, WASABI_API_LNGSTRINGW(IDS_CHANNELS), info->channels); + StringCchPrintfExW(infoStr, len, &infoStr, &len, 0, WASABI_API_LNGSTRINGW(IDS_BITS_PER_SAMPLE), info->bits_per_sample); + StringCchPrintfExW(infoStr, len, &infoStr, &len, 0, WASABI_API_LNGSTRINGW(IDS_SAMPLE_RATE), info->sample_rate); + __int64 filesize = metadata.GetFileSize(); + StringCchPrintfExW(infoStr, len, &infoStr, &len, 0, WASABI_API_LNGSTRINGW(IDS_FILE_SIZE_IN_BYTES), filesize); + if (info->total_samples) + { + StringCchPrintfExW(infoStr, len, &infoStr, &len, 0, WASABI_API_LNGSTRINGW(IDS_AVERAGE_BITRATE), filesize / (125*info->total_samples / (__int64)info->sample_rate)); // (125 is 1000/8) + int percent = (int)((100*filesize) / (info->total_samples * (info->bits_per_sample/8) * info->channels)); + StringCchPrintfExW(infoStr, len, &infoStr, &len, 0, WASABI_API_LNGSTRINGW(IDS_COMPRESSION_RATIO), percent); + } + return 1; + } + return 0; +} + +bool KeywordMatch(const char *mainString, const char *keyword) +{ + return !_stricmp(mainString, keyword); +} + +Info *info = 0; +FLACMetadata *getMetadata = 0; +wchar_t *getFileInfoFn = 0; +static FILETIME ftLastWriteTime; + +// is used to determine if the last write time of the file has changed when +// asked to get the metadata for the same cached file so we can update things +BOOL HasFileTimeChanged(const wchar_t *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData = {0}; + if (GetFileAttributesExW(fn, GetFileExInfoStandard, &fileData) == TRUE) + { + if(CompareFileTime(&ftLastWriteTime, &fileData.ftLastWriteTime)) + { + ftLastWriteTime = fileData.ftLastWriteTime; + return TRUE; + } + } + return FALSE; +} + +void UpdateFileTimeChanged(const wchar_t *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData; + if (GetFileAttributesExW(fn, GetFileExInfoStandard, &fileData) == TRUE) + { + ftLastWriteTime = fileData.ftLastWriteTime; + } +} + +void ResetMetadataCache() +{ + // cheap way to trigger a metadata reset in a thread-safe manner + wchar_t d[10] = {0}; + extendedFileInfoStructW reset_info = {0}; + reset_info.filename=L".flac"; + reset_info.metadata=L"artist"; + reset_info.ret = d; + reset_info.retlen=10; + SendMessage(plugin.hMainWindow, WM_WA_IPC, (WPARAM)&reset_info, IPC_GET_EXTENDED_FILE_INFOW); +} + +#define START_TAG_ALIAS(name, alias) if (KeywordMatch(data, name)) lookup=alias +#define TAG_ALIAS(name, alias) else if (KeywordMatch(data, name)) lookup=alias +extern "C" __declspec( dllexport ) int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, int destlen) +{ + if (KeywordMatch(data, "type")) + { + dest[0] = '0'; + dest[1] = 0; + return 1; + } + if (KeywordMatch(data, "rateable")) + { + dest[0] = '1'; + dest[1] = 0; + return 1; + } + else if (KeywordMatch(data, "lossless")) + { + dest[0] = '1'; + dest[1] = 0; + return 1; + } + + if (!fn || (fn && !fn[0])) + return 0; + + if (KeywordMatch(data, "family")) + { + LPCWSTR e; + int pID = -1; + DWORD lcid; + e = PathFindExtensionW(fn); + if (L'.' != *e) return 0; + e++; + lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); + if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"FLAC", -1) || + CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"FLA", -1)) pID = IDS_FAMILY_STRING; + + if (pID != -1 && S_OK == StringCchCopyW(dest, destlen, WASABI_API_LNGSTRINGW(pID))) return 1; + return 0; + } + + if (KeywordMatch(data, "mime")) + { + StringCchCopyW(dest, destlen, L"audio/flac"); + return 1; + } + + if (!getMetadata || !getFileInfoFn || _wcsicmp(fn, getFileInfoFn) || HasFileTimeChanged(fn)) + { + if (getMetadata) + getMetadata->Reset(); + else + getMetadata = new FLACMetadata; + + if (!getMetadata->Open(fn)) + { + delete getMetadata; + getMetadata = 0; + dest[0]=0; + return 0; + } + free(getFileInfoFn); + getFileInfoFn = _wcsdup(fn); + } + + FLACMetadata &metadata = *getMetadata; + + if(KeywordMatch(data, "formatinformation")) + return FillFileInfo(dest,destlen,metadata); + + const char *lookup=0; + if (KeywordMatch(data, "length")) + { + unsigned __int64 length_in_msec; + if (metadata.GetLengthMilliseconds(&length_in_msec)) + StringCchPrintfW(dest, destlen, L"%d", length_in_msec); + else + dest[0]=0; + return 1; + } + else if (KeywordMatch(data, "bitrate")) + { + // TODO: move this into FLACMetadata + const FLAC__StreamMetadata_StreamInfo *streaminfo = metadata.GetStreamInfo(); + if (streaminfo) + { + if (streaminfo->total_samples == 0 || streaminfo->sample_rate == 0) // prevent divide-by-zero + dest[0]=0; + else + StringCchPrintfW(dest, destlen, L"%I64d", metadata.GetFileSize() / (125*streaminfo->total_samples / (__int64)streaminfo->sample_rate)); // (125 is 1000/8) + } + else + dest[0]=0; + return 1; + } + TAG_ALIAS("title", "TITLE"); + TAG_ALIAS("artist", "ARTIST"); + TAG_ALIAS("album", "ALBUM"); + TAG_ALIAS("genre", "GENRE"); + TAG_ALIAS("comment", "COMMENT"); + TAG_ALIAS("year", "DATE"); + TAG_ALIAS("track", "TRACKNUMBER"); + TAG_ALIAS("albumartist", "ALBUM ARTIST"); + TAG_ALIAS("composer", "COMPOSER"); + TAG_ALIAS("disc", "DISCNUMBER"); + TAG_ALIAS("publisher", "PUBLISHER"); + TAG_ALIAS("conductor", "CONDUCTOR"); + TAG_ALIAS("tool", "ENCODED-BY"); + TAG_ALIAS("replaygain_track_gain", "REPLAYGAIN_TRACK_GAIN"); + TAG_ALIAS("replaygain_track_peak", "REPLAYGAIN_TRACK_PEAK"); + TAG_ALIAS("replaygain_album_gain", "REPLAYGAIN_ALBUM_GAIN"); + TAG_ALIAS("replaygain_album_peak", "REPLAYGAIN_ALBUM_PEAK"); + TAG_ALIAS("GracenoteFileID", "GRACENOTEFILEID"); + TAG_ALIAS("GracenoteExtData", "GRACENOTEEXTDATA"); + TAG_ALIAS("bpm", "BPM"); + TAG_ALIAS("remixing", "REMIXING"); + TAG_ALIAS("subtitle", "VERSION"); + TAG_ALIAS("isrc", "ISRC"); + TAG_ALIAS("category", "CATEGORY"); + TAG_ALIAS("rating", "RATING"); + TAG_ALIAS("producer", "PRODUCER"); + + if (!lookup) + return 0; + + const char *value = metadata.GetMetadata(lookup); + + if(KeywordMatch("comment",data)) { + if(!value || !*value) value = metadata.GetMetadata("DESCRIPTION"); + } + + if(KeywordMatch("year",data)) { + if(!value || !*value) value = metadata.GetMetadata("YEAR"); + } + + if(KeywordMatch("track",data)) { + if(!value || !*value) value = metadata.GetMetadata("TRACK"); + } + + if(KeywordMatch("albumartist",data)) { + if(!value || !*value) value = metadata.GetMetadata("ALBUMARTIST"); + if(!value || !*value) value = metadata.GetMetadata("ENSEMBLE"); + } + + if(KeywordMatch("publisher",data)) { + if(!value || !*value) value = metadata.GetMetadata("ORGANIZATION"); + } + + if(KeywordMatch("category",data)) { + if(!value || !*value) value = metadata.GetMetadata("CONTENTGROUP"); + if(!value || !*value) value = metadata.GetMetadata("GROUPING"); + } + + if(KeywordMatch(data, "rating")) { + if(!value || !*value) value = metadata.GetMetadata("RATING"); + if(value && *value) { + int rating = atoi(value); + + // keeps things limited to our range of 0-100 + if (rating >= 100) { + rating = 5; + } + // 1-100 case + else if (rating > 5 && rating < 100) { + rating = (rating /= 20); + // shift up by one rating when in next band + // 1-20 = 1, 21-40 = 2, 41-60 = 3, 61-80 = 4, 81-100 = 5 + rating += ((atoi(value) - (rating * 20)) > 0); + } + else if (rating > 0 && rating <= 5) { + } + // otherwise just make sure and set zero + else { + rating = 0; + } + + StringCchPrintfW(dest, destlen, L"%u", rating); + return 1; + } + } + + MultiByteToWideCharSZ(CP_UTF8, 0, value, -1, dest, destlen); + return 1; +} + +FLACMetadata *setMetadata=0; +wchar_t *setFn=0; +extern "C" __declspec( dllexport ) int winampSetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *val) +{ + if (!setMetadata || !setFn || lstrcmpiW(fn, setFn)) + { + free(setFn); + setFn=_wcsdup(fn); + if (!setMetadata) + setMetadata = new FLACMetadata; + if (setMetadata->Open(setFn, true) == false) + { + delete setMetadata; + setMetadata=0; + return 0; + } + } + + const char *lookup=0; + START_TAG_ALIAS("artist", "ARTIST"); + TAG_ALIAS("title", "TITLE"); + TAG_ALIAS("album", "ALBUM"); + TAG_ALIAS("genre", "GENRE"); + TAG_ALIAS("comment", "COMMENT"); + TAG_ALIAS("year", "DATE"); + TAG_ALIAS("track", "TRACKNUMBER"); + TAG_ALIAS("albumartist", "ALBUM ARTIST"); + TAG_ALIAS("composer", "COMPOSER"); + TAG_ALIAS("disc", "DISCNUMBER"); + TAG_ALIAS("publisher", "PUBLISHER"); + TAG_ALIAS("conductor", "CONDUCTOR"); + TAG_ALIAS("tool", "ENCODED-BY"); + TAG_ALIAS("replaygain_track_gain", "REPLAYGAIN_TRACK_GAIN"); + TAG_ALIAS("replaygain_track_peak", "REPLAYGAIN_TRACK_PEAK"); + TAG_ALIAS("replaygain_album_gain", "REPLAYGAIN_ALBUM_GAIN"); + TAG_ALIAS("replaygain_album_peak", "REPLAYGAIN_ALBUM_PEAK"); + TAG_ALIAS("GracenoteFileID", "GRACENOTEFILEID"); + TAG_ALIAS("GracenoteExtData", "GRACENOTEEXTDATA"); + TAG_ALIAS("bpm", "BPM"); + TAG_ALIAS("remixing", "REMIXING"); + TAG_ALIAS("subtitle", "VERSION"); + TAG_ALIAS("isrc", "ISRC"); + TAG_ALIAS("category", "CATEGORY"); + TAG_ALIAS("rating", "RATING"); + TAG_ALIAS("producer", "PRODUCER"); + + if (!lookup) + return 0; + + if (val && *val) + { + if(KeywordMatch("rating",data)) + { + char temp[128] = {0}; + StringCchPrintfA(temp, 128, "%u", _wtoi(val)*20); + setMetadata->SetMetadata(lookup, temp); + } + else + { + setMetadata->SetMetadata(lookup, AutoChar(val, CP_UTF8)); + } + } + else + { + setMetadata->RemoveMetadata(lookup); + if(KeywordMatch("comment",data)) + { + // need to remove this one also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + setMetadata->RemoveMetadata("DESCRIPTION"); + } + else if(KeywordMatch("year",data)) + { + // need to remove this one also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + setMetadata->RemoveMetadata("YEAR"); + } + else if(KeywordMatch("track",data)) + { + // need to remove this one also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + setMetadata->RemoveMetadata("TRACK"); + } + else if(KeywordMatch("albumartist",data)) + { + // need to remove these two, also, or else it's gonna look like delete doesn't work + // if the file was tagged using these alternate fields + setMetadata->RemoveMetadata("ALBUMARTIST"); + setMetadata->RemoveMetadata("ENSEMBLE"); + } + else if(KeywordMatch("publisher",data)) + { + // need to remove this one also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + setMetadata->RemoveMetadata("ORGANIZATION"); + } + else if(KeywordMatch("category",data)) + { + // need to remove these two also, or else it's gonna look like delete doesn't work + // if the file was tagged using these alternate fields + setMetadata->RemoveMetadata("CONTENTGROUP"); + setMetadata->RemoveMetadata("GROUPING"); + } + } + + return 1; +} + +extern "C" __declspec(dllexport) int winampWriteExtendedFileInfo() +{ + if (setFn && setMetadata) + { + Stopper stopper; + if (lastfn && !_wcsicmp(lastfn, setFn)) + stopper.Stop(); + bool success = setMetadata->Save(setFn); + stopper.Play(); + setMetadata->Reset(); + free(setFn); + setFn=0; + + delete getMetadata; + getMetadata=0; + + // update last modified so we're not re-queried on our own updates + UpdateFileTimeChanged(setFn); + + return !!success; + } + return 1; +} + +extern "C" __declspec(dllexport) const wchar_t *winampWriteExtendedGetLastError() +{ + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/ExtendedRead.cpp b/Src/Plugins/Input/in_flac/ExtendedRead.cpp new file mode 100644 index 00000000..d3c08fc8 --- /dev/null +++ b/Src/Plugins/Input/in_flac/ExtendedRead.cpp @@ -0,0 +1,166 @@ +/* +** Copyright (C) 2007-2011 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ + +#include "main.h" +#include <FLAC/all.h> +#include "StreamFileWin32.h" +#include "QuickBuf.h" +#include <bfc/platform/types.h> +#include <assert.h> +#include "FLACFileCallbacks.h" +#include "nswasabi/ReferenceCounted.h" + +struct ExtendedRead +{ + int bps, channels, samplerate, truebps; + uint64_t samples; + QuickBuf output; + FLAC__StreamDecoder *decoder; + size_t used; + FLACClientData client_data; +}; + +static void OnError(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + //client_data=client_data; // dummy line so i can set a breakpoint +} + +static void OnMetadata(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) +{ + ExtendedRead *ext = FLAC_GetObject<ExtendedRead>(client_data); + + switch(metadata->type) + { + case FLAC__METADATA_TYPE_STREAMINFO: + { + ext->truebps=metadata->data.stream_info.bits_per_sample; + ext->bps = (ext->truebps +7) & (~7); + ext->channels=metadata->data.stream_info.channels; + ext->samplerate=metadata->data.stream_info.sample_rate; + ext->samples=metadata->data.stream_info.total_samples; + } + break; + } +} + +static FLAC__StreamDecoderWriteStatus OnAudio(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data) +{ + ExtendedRead *ext = FLAC_GetObject<ExtendedRead>(client_data); + + int byteLength = (ext->bps/8) * ext->channels * frame->header.blocksize; + ext->output.Reserve(byteLength); + InterleaveAndTruncate(buffer, ext->output, ext->bps, ext->channels, frame->header.blocksize); + ext->used = byteLength; + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +extern "C" +{ + __declspec( dllexport ) intptr_t winampGetExtendedRead_openW(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) + { + nx_file_t file; + ReferenceCountedNXString filename_nx; + ReferenceCountedNXURI filename_uri; + NXStringCreateWithUTF16(&filename_nx, fn); + NXURICreateWithNXString(&filename_uri, filename_nx); + + int ret = NXFileOpenFile(&file, filename_uri, nx_file_FILE_read_binary); + if (ret != NErr_Success) + return 0; + + ExtendedRead * e = (ExtendedRead *)calloc(sizeof(ExtendedRead), 1); + e->decoder = FLAC__stream_decoder_new(); + if (e->decoder == 0) + { + NXFileRelease(file); + free(e); + return 0; + } + + e->client_data.SetFile(file); + e->client_data.SetObject(e); + + FLAC__stream_decoder_set_md5_checking(e->decoder, true); + if(FLAC__stream_decoder_init_stream( + e->decoder, + FLAC_NXFile_Read, + FLAC_NXFile_Seek, + FLAC_NXFile_Tell, + FLAC_NXFile_Length, + FLAC_NXFile_EOF, + OnAudio, + OnMetadata, + OnError, + &e->client_data + ) != FLAC__STREAM_DECODER_INIT_STATUS_OK) + { + FLAC__stream_decoder_delete(e->decoder); + NXFileRelease(file); + free(e); + return 0; + } + + FLAC__stream_decoder_process_until_end_of_metadata(e->decoder); + *bps = e->truebps; + *nch = e->channels; + *srate = e->samplerate; + *size = (int)(e->samples * (e->bps/8) * e->channels); + return (intptr_t) e; + } + + __declspec( dllexport ) intptr_t winampGetExtendedRead_getData(intptr_t handle, char *dest, size_t len, int *killswitch) + { + ExtendedRead *ext = (ExtendedRead *)handle; + + while(!ext->used) // loop until we get some data + { + if (FLAC__stream_decoder_process_single(ext->decoder) == 0) + break; // break out if there's an error + + FLAC__StreamDecoderState FLACstate = FLAC__stream_decoder_get_state(ext->decoder); + if (FLACstate == FLAC__STREAM_DECODER_END_OF_STREAM) // break out if we hit EOF + break; + } + + if (ext->used) + { + size_t toCopy = min(len, ext->used); + memcpy(dest, ext->output, toCopy); + if (toCopy < ext->used) + ext->output.Move(toCopy); + ext->used-=toCopy; + return toCopy; + } + + return 0; + } + + __declspec( dllexport ) void winampGetExtendedRead_close(intptr_t handle) + { + ExtendedRead *ext = (ExtendedRead *)handle; + + ext->output.Free(); + FLAC__stream_decoder_finish(ext->decoder); + FLAC__stream_decoder_delete(ext->decoder); + NXFileRelease(ext->client_data.GetFile()); + free(ext); + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/FLACFileCallbacks.cpp b/Src/Plugins/Input/in_flac/FLACFileCallbacks.cpp new file mode 100644 index 00000000..23a53b91 --- /dev/null +++ b/Src/Plugins/Input/in_flac/FLACFileCallbacks.cpp @@ -0,0 +1,57 @@ +#include "FLACFileCallbacks.h" + + +FLAC__StreamDecoderReadStatus FLAC_NXFile_Read(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data) +{ + nx_file_t file = FLAC_GetFile(client_data); + size_t bytes_to_read = *bytes; + size_t bytes_read=0; + ns_error_t ret = NXFileRead(file, buffer, bytes_to_read, &bytes_read); + *bytes = bytes_read; + + if (ret == NErr_EndOfFile) + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + + if (ret != NErr_Success) + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +} + +FLAC__StreamDecoderSeekStatus FLAC_NXFile_Seek(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data) +{ + nx_file_t file = FLAC_GetFile(client_data); + if (NXFileSeek(file, absolute_byte_offset) == NErr_Success) + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; + else + return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; +} + +FLAC__StreamDecoderTellStatus FLAC_NXFile_Tell(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data) +{ + nx_file_t file = FLAC_GetFile(client_data); + + if (NXFileTell(file, absolute_byte_offset) == NErr_Success) + return FLAC__STREAM_DECODER_TELL_STATUS_OK; + else + return FLAC__STREAM_DECODER_TELL_STATUS_ERROR; +} + +FLAC__StreamDecoderLengthStatus FLAC_NXFile_Length(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data) +{ + nx_file_t file = FLAC_GetFile(client_data); + + if (NXFileLength(file, stream_length) == NErr_Success) + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; + else + return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR; +} + +FLAC__bool FLAC_NXFile_EOF(const FLAC__StreamDecoder *decoder, void *client_data) +{ + nx_file_t file = FLAC_GetFile(client_data); + if (NXFileEndOfFile(file) == NErr_False) + return false; + else + return true; +} diff --git a/Src/Plugins/Input/in_flac/FLACFileCallbacks.h b/Src/Plugins/Input/in_flac/FLACFileCallbacks.h new file mode 100644 index 00000000..7bd2538c --- /dev/null +++ b/Src/Plugins/Input/in_flac/FLACFileCallbacks.h @@ -0,0 +1,35 @@ +#pragma once +#include <FLAC/all.h> +#include "nx/nxfile.h" + + +FLAC__StreamDecoderReadStatus FLAC_NXFile_Read(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data); +FLAC__StreamDecoderSeekStatus FLAC_NXFile_Seek(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data); +FLAC__StreamDecoderTellStatus FLAC_NXFile_Tell(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data); +FLAC__StreamDecoderLengthStatus FLAC_NXFile_Length(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data); +FLAC__bool FLAC_NXFile_EOF(const FLAC__StreamDecoder *decoder, void *client_data); + + +class FLACClientData +{ +public: + FLACClientData() : object(0) {} + void SetFile(nx_file_t file) { this->file = file; } + void SetObject(void *object) { this->object = object; } + nx_file_t GetFile() { return file; } + void *GetObject() { return object; } +private: + nx_file_t file; + void *object; +}; + +template <typename _t> +static _t *FLAC_GetObject(void *client_data) +{ + return (_t *)((FLACClientData *)client_data)->GetObject(); +} + +static nx_file_t FLAC_GetFile(void *client_data) +{ + return ((FLACClientData *)client_data)->GetFile(); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/FileInfo.cpp b/Src/Plugins/Input/in_flac/FileInfo.cpp new file mode 100644 index 00000000..d0684c59 --- /dev/null +++ b/Src/Plugins/Input/in_flac/FileInfo.cpp @@ -0,0 +1,400 @@ +/* +** Copyright (C) 2007-2011 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ + +#include <FLAC/all.h> +#include "main.h" +#include "../nu/ns_wc.h" +#include <windows.h> +#include "resource.h" +#include "Metadata.h" +#include "../nu/AutoWide.h" +#include "../nu/AutoChar.h" +#include "Stopper.h" +#include <strsafe.h> +#include <commctrl.h> +#include "../Agave/Language/api_language.h" + +bool FlacTagToWinampTag(wchar_t * tag, int len) +{ +#define TAG_ALIAS(b,a) if(!_wcsicmp(L ## a, tag)) { lstrcpynW(tag, L ## b, len); return true; } + TAG_ALIAS("title", "TITLE"); + TAG_ALIAS("artist", "ARTIST"); + TAG_ALIAS("album", "ALBUM"); + TAG_ALIAS("genre", "GENRE"); + TAG_ALIAS("comment", "COMMENT"); + TAG_ALIAS("year", "DATE"); + TAG_ALIAS("track", "TRACKNUMBER"); + TAG_ALIAS("albumartist", "ALBUM ARTIST"); + TAG_ALIAS("composer", "COMPOSER"); + TAG_ALIAS("disc", "DISCNUMBER"); + TAG_ALIAS("publisher", "PUBLISHER"); + TAG_ALIAS("conductor", "CONDUCTOR"); + TAG_ALIAS("bpm", "BPM"); + return false; +#undef TAG_ALIAS +} + +bool WinampTagToFlacTag(wchar_t * tag, int len) +{ +#define TAG_ALIAS(a,b) if(!_wcsicmp(L ## a, tag)) { lstrcpynW(tag, L ## b, len); return true; } + TAG_ALIAS("title", "TITLE"); + TAG_ALIAS("artist", "ARTIST"); + TAG_ALIAS("album", "ALBUM"); + TAG_ALIAS("genre", "GENRE"); + TAG_ALIAS("comment", "COMMENT"); + TAG_ALIAS("year", "DATE"); + TAG_ALIAS("track", "TRACKNUMBER"); + TAG_ALIAS("albumartist", "ALBUM ARTIST"); + TAG_ALIAS("composer", "COMPOSER"); + TAG_ALIAS("disc", "DISCNUMBER"); + TAG_ALIAS("publisher", "PUBLISHER"); + TAG_ALIAS("conductor", "CONDUCTOR"); + TAG_ALIAS("bpm", "BPM"); + return false; +#undef TAG_ALIAS +} + +static INT_PTR CALLBACK ChildProc_Advanced(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { + static int sel=-1; + static int ismychange=0; + wchar_t key[512]={0}; + wchar_t value[32768]={0}; + + switch(msg) + { + case WM_NOTIFYFORMAT: + return NFR_UNICODE; + case WM_INITDIALOG: + { + #define ListView_InsertColumnW(hwnd, iCol, pcol) \ + (int)SNDMSG((hwnd), LVM_INSERTCOLUMNW, (WPARAM)(int)(iCol), (LPARAM)(const LV_COLUMNW *)(pcol)) + SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam); + sel=-1; + HWND hwndlist = GetDlgItem(hwndDlg,IDC_LIST); + ListView_SetExtendedListViewStyle(hwndlist, LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP); + LVCOLUMNW lvc = {0, }; + lvc.mask = LVCF_TEXT|LVCF_WIDTH; + lvc.pszText = WASABI_API_LNGSTRINGW(IDS_NAME); + lvc.cx = 82; + ListView_InsertColumnW(hwndlist, 0, &lvc); + lvc.pszText = WASABI_API_LNGSTRINGW(IDS_VALUE); + lvc.cx = 160; + ListView_InsertColumnW(hwndlist, 1, &lvc); + + Info *info = (Info *)lParam; + int n = info->metadata.GetNumMetadataItems(); + for(int i=0; i<n; i++) { + char key_[512] = {0}; + const char* value_ = info->metadata.EnumMetadata(i,key_,512); + if(value_ && key_[0]) { + AutoWide k(key_, CP_UTF8); + AutoWide v(value_, CP_UTF8); + LVITEMW lvi={LVIF_TEXT,i,0}; + lvi.pszText = k; + SendMessage(hwndlist,LVM_INSERTITEMW,0,(LPARAM)&lvi); + lvi.iSubItem=1; + lvi.pszText = v; + SendMessage(hwndlist,LVM_SETITEMW,0,(LPARAM)&lvi); + } + } + ListView_SetColumnWidth(hwndlist,0,LVSCW_AUTOSIZE); + ListView_SetColumnWidth(hwndlist,1,LVSCW_AUTOSIZE); + + SetDlgItemTextW(hwndDlg,IDC_NAME,L""); + SetDlgItemTextW(hwndDlg,IDC_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),FALSE); + } + break; + case WM_DESTROY: + { + HWND hwndlist = GetDlgItem(hwndDlg,IDC_LIST); + ListView_DeleteAllItems(hwndlist); + while(ListView_DeleteColumn(hwndlist,0)); + Info * info = (Info*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + delete info; + info = 0; + } + break; + case WM_USER: + if(wParam && lParam && !ismychange) + { + wchar_t * value = (wchar_t*)lParam; + wchar_t tag[100] = {0}; + lstrcpynW(tag,(wchar_t*)wParam,100); + WinampTagToFlacTag(tag,100); + Info *info = (Info *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if(!*value) + { + info->metadata.RemoveMetadata(AutoChar(tag,CP_UTF8)); + if(!_wcsicmp(L"ALBUM ARTIST",tag)) + { + // need to remove these two, also, or else it's gonna look like delete doesn't work + // if the file was tagged using these alternate fields + info->metadata.RemoveMetadata("ALBUMARTIST"); + info->metadata.RemoveMetadata("ENSEMBLE"); + } + if(!_wcsicmp(L"PUBLISHER",tag)) + { + // need to remove this also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + info->metadata.RemoveMetadata("ORGANIZATION"); + } + if(!_wcsicmp(L"DATE",tag)) + { + // need to remove this also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + info->metadata.RemoveMetadata("YEAR"); + } + if(!_wcsicmp(L"TRACKNUMBER",tag)) + { + // need to remove this also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + info->metadata.RemoveMetadata("TRACK"); + } + } + else + { + info->metadata.SetMetadata(AutoChar(tag,CP_UTF8),AutoChar(value,CP_UTF8)); + } + HWND hlist = GetDlgItem(hwndDlg,IDC_LIST); + int n = ListView_GetItemCount(hlist); + for(int i=0; i<n; i++) + { + key[0]=0; + LVITEMW lvi={LVIF_TEXT,i,0}; + lvi.pszText=key; + lvi.cchTextMax=sizeof(key)/sizeof(*key); + SendMessage(hlist,LVM_GETITEMW,0,(LPARAM)&lvi); + if(!_wcsicmp(key,tag)) + { + lvi.iSubItem = 1; + lvi.pszText = value; + SendMessage(hlist,LVM_SETITEMW,0,(LPARAM)&lvi); + if(!*value) + ListView_DeleteItem(hlist,i); + else if(ListView_GetItemState(hlist,i,LVIS_SELECTED)) + SetDlgItemTextW(hwndDlg,IDC_VALUE,value); + return 0; + } + } + // bew hew, not found + LVITEMW lvi={0,0x7FFFFFF0,0}; + n = SendMessage(hlist,LVM_INSERTITEMW,0,(LPARAM)&lvi); + lvi.mask = LVIF_TEXT; + lvi.iItem = n; + lvi.iSubItem = 0; + lvi.pszText = tag; + SendMessage(hlist,LVM_SETITEMW,0,(LPARAM)&lvi); + lvi.iSubItem = 1; + lvi.pszText = value; + SendMessage(hlist,LVM_SETITEMW,0,(LPARAM)&lvi); + } + break; + case WM_NOTIFY: + { + LPNMHDR l=(LPNMHDR)lParam; + if(l->idFrom==IDC_LIST && l->code == LVN_KEYDOWN) { + if((((LPNMLVKEYDOWN)l)->wVKey) == VK_DELETE){ + int selitem = ListView_GetNextItem(l->hwndFrom,-1,LVNI_SELECTED|LVNI_FOCUSED); + if(selitem != -1) + SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_BUTTON_DEL,BN_CLICKED),(LPARAM)GetDlgItem(hwndDlg,IDC_BUTTON_DEL)); + } + } + else if(l->idFrom==IDC_LIST && l->code == LVN_ITEMCHANGED) { + LPNMLISTVIEW lv=(LPNMLISTVIEW)lParam; + if(lv->uNewState & LVIS_SELECTED) { + int n = lv->iItem; + LVITEMW lvi={LVIF_TEXT,lv->iItem,0}; + lvi.pszText=key; + lvi.cchTextMax=sizeof(key)/sizeof(*key); + SendMessage(l->hwndFrom,LVM_GETITEMW,0,(LPARAM)&lvi); + lvi.pszText=value; + lvi.cchTextMax=sizeof(value)/sizeof(*value); + lvi.iSubItem=1; + SendMessage(l->hwndFrom,LVM_GETITEMW,0,(LPARAM)&lvi); + SetDlgItemTextW(hwndDlg,IDC_NAME,key); + SetDlgItemTextW(hwndDlg,IDC_VALUE,value); + sel = n; + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),TRUE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),TRUE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),TRUE); + } + if(lv->uOldState & LVIS_SELECTED) { + sel = -1; + SetDlgItemTextW(hwndDlg,IDC_NAME,L""); + SetDlgItemTextW(hwndDlg,IDC_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),FALSE); + } + } + } + break; + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDOK: + { + Info * info = (Info*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + Stopper stopper; + if (lastfn && !_wcsicmp(lastfn, info->filename)) + stopper.Stop(); + bool success = info->metadata.Save(info->filename); + stopper.Play(); + if (success) + { + ResetMetadataCache(); + } + else + { + wchar_t title[128] = {0}; + MessageBoxW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_CANNOT_SAVE_METADATA), + WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SAVING_METADATA,title,128), + MB_OK | MB_ICONWARNING); + } + } + break; + case IDC_NAME: + case IDC_VALUE: + if(HIWORD(wParam) == EN_CHANGE && sel>=0) { + LVITEMW lvi={LVIF_TEXT,sel,0}; + GetDlgItemTextW(hwndDlg,IDC_NAME,key,sizeof(key)/sizeof(*key)); + GetDlgItemTextW(hwndDlg,IDC_VALUE,value,sizeof(value)/sizeof(*value)); + lvi.pszText=key; + lvi.cchTextMax=sizeof(key)/sizeof(*key); + SendMessage(GetDlgItem(hwndDlg,IDC_LIST),LVM_SETITEMW,0,(LPARAM)&lvi); + lvi.pszText=value; + lvi.cchTextMax=sizeof(value)/sizeof(*value); + lvi.iSubItem=1; + SendMessage(GetDlgItem(hwndDlg,IDC_LIST),LVM_SETITEMW,0,(LPARAM)&lvi); + FlacTagToWinampTag(key,sizeof(key)/sizeof(*key)); + ismychange=1; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)key,(WPARAM)value); + ismychange=0; + } + else if(HIWORD(wParam) == EN_KILLFOCUS && sel>=0) { + GetDlgItemTextW(hwndDlg,IDC_NAME,key,sizeof(key)/sizeof(*key)); + GetDlgItemTextW(hwndDlg,IDC_VALUE,value,sizeof(value)/sizeof(*value)); + Info *info = (Info *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + char oldkeyA[100]=""; + bool newitem=true; + if(sel < info->metadata.GetNumMetadataItems()) { + info->metadata.EnumMetadata(sel,oldkeyA,100); + newitem=false; + } + AutoWide oldkey(oldkeyA); + if(!newitem && _wcsicmp(oldkey,key)) { // key changed + info->metadata.SetTag(sel,AutoChar(key,CP_UTF8)); + } else { + info->metadata.SetMetadata(AutoChar(key,CP_UTF8),AutoChar(value,CP_UTF8)); + } + FlacTagToWinampTag(key,sizeof(key)/sizeof(*key)); + ismychange=1; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)key,(WPARAM)value); + ismychange=0; + } + break; + case IDC_BUTTON_DEL: + if(sel >= 0){ + GetDlgItemTextW(hwndDlg,IDC_NAME,key,sizeof(key)/sizeof(*key)); + SetDlgItemTextW(hwndDlg,IDC_NAME,L""); + SetDlgItemTextW(hwndDlg,IDC_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),FALSE); + Info *info = (Info *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if(sel < info->metadata.GetNumMetadataItems()) + info->metadata.RemoveMetadata(sel); + ListView_DeleteItem(GetDlgItem(hwndDlg,IDC_LIST),sel); + sel=-1; + FlacTagToWinampTag(key,sizeof(key)/sizeof(*key)); + ismychange=1; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)key,(WPARAM)L""); + ismychange=0; + } + break; + case IDC_BUTTON_DELALL: + ListView_DeleteAllItems(GetDlgItem(hwndDlg,IDC_LIST)); + SetDlgItemTextW(hwndDlg,IDC_NAME,L""); + SetDlgItemTextW(hwndDlg,IDC_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),FALSE); + sel=-1; + { + Info *info = (Info *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + int n = info->metadata.GetNumMetadataItems(); + while(n>0) { + --n; + char tag[100] = {0}; + info->metadata.EnumMetadata(n,tag,100); + MultiByteToWideCharSZ(CP_UTF8, 0, tag, -1, key, sizeof(key)/sizeof(*key)); + FlacTagToWinampTag(key,sizeof(key)/sizeof(*key)); + ismychange=1; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)key,(WPARAM)L""); + ismychange=0; + info->metadata.RemoveMetadata(n); + } + } + break; + case IDC_BUTTON_ADD: + { + HWND hwndlist = GetDlgItem(hwndDlg,IDC_LIST); + LVITEMW lvi={0,0x7FFFFFF0,0}; + int n = SendMessage(hwndlist,LVM_INSERTITEMW,0,(LPARAM)&lvi); + ListView_SetItemState(hwndlist,n,LVIS_SELECTED,LVIS_SELECTED); + } + break; + } + break; + } + return 0; +} + +extern "C" +{ + // return 1 if you want winamp to show it's own file info dialogue, 0 if you want to show your own (via In_Module.InfoBox) + // if returning 1, remember to implement winampGetExtendedFileInfo("formatinformation")! + __declspec(dllexport) int winampUseUnifiedFileInfoDlg(const wchar_t * fn) + { + return 1; + } + + // should return a child window of 513x271 pixels (341x164 in msvc dlg units), or return NULL for no tab. + // Fill in name (a buffer of namelen characters), this is the title of the tab (defaults to "Advanced"). + // filename will be valid for the life of your window. n is the tab number. This function will first be + // called with n == 0, then n == 1 and so on until you return NULL (so you can add as many tabs as you like). + // The window you return will recieve WM_COMMAND, IDOK/IDCANCEL messages when the user clicks OK or Cancel. + // when the user edits a field which is duplicated in another pane, do a SendMessage(GetParent(hwnd),WM_USER,(WPARAM)L"fieldname",(LPARAM)L"newvalue"); + // this will be broadcast to all panes (including yours) as a WM_USER. + __declspec(dllexport) HWND winampAddUnifiedFileInfoPane(int n, const wchar_t * filename, HWND parent, wchar_t *name, size_t namelen) + { + if(n == 0) { // add first pane + SetPropW(parent,L"INBUILT_NOWRITEINFO", (HANDLE)1); + info = new Info; + info->filename = filename; + info->metadata.Open(filename, true); + return WASABI_API_CREATEDIALOGPARAMW(IDD_INFOCHILD_ADVANCED,parent,ChildProc_Advanced,(LPARAM)info); + } + return NULL; + } +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/Metadata.cpp b/Src/Plugins/Input/in_flac/Metadata.cpp new file mode 100644 index 00000000..49815c54 --- /dev/null +++ b/Src/Plugins/Input/in_flac/Metadata.cpp @@ -0,0 +1,577 @@ +/* +** Copyright (C) 2007-2011 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ +#include "Metadata.h" +#include <FLAC/all.h> +#include "StreamFileWin32.h" // for FileSize64 +#include "api__in_flv.h" +#include <strsafe.h> + +struct MetadataReader +{ + MetadataReader(HANDLE _handle) + { + handle = _handle; + endOfFile=false; + } + HANDLE handle; + bool endOfFile; +}; + +static size_t win32_read(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle) +{ + MetadataReader *reader = (MetadataReader *)handle; + DWORD bytesRead=0; + BOOL result = ReadFile(reader->handle, ptr, size*nmemb, &bytesRead, NULL); + if (result == TRUE && bytesRead == 0) + reader->endOfFile=true; + return bytesRead/size; +} + +static size_t win32_write(const void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle) +{ + MetadataReader *reader = (MetadataReader *)handle; + DWORD bytesWritten=0; + WriteFile(reader->handle, ptr, size*nmemb, &bytesWritten, NULL); + return bytesWritten/size; +} + +static __int64 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; +} + +static int win32_seek(FLAC__IOHandle handle, FLAC__int64 offset, int whence) +{ + MetadataReader *reader = (MetadataReader *)handle; + if (Seek64(reader->handle, offset, whence) == -1) + return -1; + else + return 0; + +} + +static FLAC__int64 win32_tell(FLAC__IOHandle handle) +{ + MetadataReader *reader = (MetadataReader *)handle; + return Seek64(reader->handle, 0, FILE_CURRENT); +} + +static int win32_eof(FLAC__IOHandle handle) +{ + MetadataReader *reader = (MetadataReader *)handle; + return !!reader->endOfFile; +} + +static int win32_close(FLAC__IOHandle handle) +{ + MetadataReader *reader = (MetadataReader *)handle; + + CloseHandle(reader->handle); + reader->handle = INVALID_HANDLE_VALUE; + return 0; +} + +static FLAC__IOCallbacks unicodeIO = +{ + win32_read, + win32_write, + win32_seek, + win32_tell, + win32_eof, + win32_close, +}; + +FLACMetadata::FLACMetadata() +{ + chain=FLAC__metadata_chain_new(); + itr = FLAC__metadata_iterator_new(); + block=0; + streamInfo=0; + filesize=0; +} + +FLACMetadata::~FLACMetadata() +{ + if (chain) + FLAC__metadata_chain_delete(chain); + if (itr) + FLAC__metadata_iterator_delete(itr); +} + +void FLACMetadata::Reset() +{ + if (chain) + FLAC__metadata_chain_delete(chain); + if (itr) + FLAC__metadata_iterator_delete(itr); + chain=FLAC__metadata_chain_new(); + itr = FLAC__metadata_iterator_new(); + block=0; + streamInfo=0; + filesize=0; +} + +const FLAC__StreamMetadata_StreamInfo *FLACMetadata::GetStreamInfo() +{ + if (streamInfo) + return &streamInfo->data.stream_info; + else + return 0; +} + +bool FLACMetadata::Open(const wchar_t *filename, bool optimize) +{ + if (!chain || !itr) + return false; + + HANDLE hfile = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); + MetadataReader reader(hfile); + if (reader.handle == INVALID_HANDLE_VALUE) + return false; + filesize = FileSize64(reader.handle); + + FLAC__bool success = FLAC__metadata_chain_read_with_callbacks(chain, &reader, unicodeIO); + CloseHandle(hfile); + if (!success) + return false; + + if (optimize) + { + FLAC__metadata_chain_sort_padding(chain); + FLAC__metadata_chain_merge_padding(chain); + } + + FLAC__metadata_iterator_init(itr, chain); + while (1) + { + FLAC__MetadataType type=FLAC__metadata_iterator_get_block_type(itr); + switch (type) + { + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + block = FLAC__metadata_iterator_get_block(itr); + break; + case FLAC__METADATA_TYPE_STREAMINFO: + streamInfo = FLAC__metadata_iterator_get_block(itr); + break; + } + if (FLAC__metadata_iterator_next(itr) == false) + break; + } + return true; +} + +const char *FLACMetadata::GetMetadata(const char *tag) +{ + if (!block) + return 0; + + int pos = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, tag); + if (pos < 0) + { + // fail + } + else + { + const char *entry = (const char *)block->data.vorbis_comment.comments[pos].entry; + const char *metadata = strchr(entry, '='); // find the first equal + if (metadata) + { + return metadata+1; + } + } + + return 0; +} + +void FLACMetadata::SetMetadata(const char *tag, const char *value) +{ + if (!block) + { + FLAC__metadata_iterator_init(itr, chain); + do + { + if (FLAC__METADATA_TYPE_VORBIS_COMMENT == FLAC__metadata_iterator_get_block_type(itr)) + { + block = FLAC__metadata_iterator_get_block(itr); + break; + } + } + while (FLAC__metadata_iterator_next(itr) != 0); + if (!block) + { + block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); + FLAC__metadata_iterator_insert_block_after(itr, block); + } + } + + if (!block) + return; + + FLAC__StreamMetadata_VorbisComment_Entry entry; + size_t totalLen = strlen(tag) + 1 /* = */ + strlen(value); + entry.entry = (FLAC__byte *)malloc(totalLen + 1); + entry.length = totalLen; + + StringCchPrintfA((char *)entry.entry, totalLen+1, "%s=%s", tag, value); + + int pos = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, tag); + if (pos < 0) + { + //new comment + FLAC__metadata_object_vorbiscomment_append_comment(block, entry, true); + // would love to not copy, but we can't guarantee that FLAC links to the same CRT as us + } + else + { + FLAC__metadata_object_vorbiscomment_set_comment(block, pos, entry, true); + // would love to not copy, but we can't guarantee that FLAC links to the same CRT as us + } + free(entry.entry); +} + +void FLACMetadata::RemoveMetadata(const char *tag) +{ + if (!block) + return; + + FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, tag); +} + +void FLACMetadata::RemoveMetadata(int n) +{ + if (!block) + return; + + FLAC__metadata_object_vorbiscomment_delete_comment(block, (unsigned int)n); +} + +static FLAC__StreamMetadata *GetOrMakePadding(FLAC__Metadata_Chain *chain, FLAC__Metadata_Iterator *itr) +{ + FLAC__metadata_iterator_init(itr, chain); + while (1) + { + if (FLAC__METADATA_TYPE_PADDING == FLAC__metadata_iterator_get_block_type(itr)) + { + FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(itr); + return block; + } + + if (FLAC__metadata_iterator_next(itr) == false) + break; + } + FLAC__StreamMetadata *padding = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING); + if (padding) + FLAC__metadata_iterator_insert_block_after(itr, padding); + + return padding; +} + +bool FLACMetadata::Save(const wchar_t *filename) +{ + if (FLAC__metadata_chain_check_if_tempfile_needed(chain, true)) + { + // since we needed to write a tempfile, let's add some more padding so it doesn't happen again + FLAC__metadata_chain_sort_padding(chain); + + FLAC__StreamMetadata *padding = GetOrMakePadding(chain, itr); + if (padding && padding->length < 16384) + padding->length = 16384; // TODO: configurable padding size + + HANDLE hfile = CreateFileW(filename, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0); + MetadataReader reader(hfile); + if (reader.handle == INVALID_HANDLE_VALUE) + return false; + + wchar_t tempPath[MAX_PATH-14] = {0}, tempFile[MAX_PATH] = {0}; + GetTempPathW(MAX_PATH-14, tempPath); + GetTempFileNameW(tempPath, L"waf", 0, tempFile); + + HANDLE hTempFile = CreateFileW(tempFile, GENERIC_READ|GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); + MetadataReader tempReader(hTempFile); + + FLAC__bool res = FLAC__metadata_chain_write_with_callbacks_and_tempfile(chain, false, &reader, unicodeIO, &tempReader, unicodeIO); + + CloseHandle(hfile); + CloseHandle(hTempFile); + if (!MoveFileW(tempFile, filename)) + { + if (CopyFileW(tempFile, filename, FALSE)) + { + DeleteFileW(tempFile); + } + else + { + DeleteFileW(tempFile); + return false; + } + } + return !!res; + } + else + { + HANDLE hfile = CreateFileW(filename, GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0); + MetadataReader reader(hfile); + if (reader.handle == INVALID_HANDLE_VALUE) + return false; + + FLAC__bool res = FLAC__metadata_chain_write_with_callbacks(chain, true, &reader, unicodeIO); + + CloseHandle(hfile); + return !!res; + } +} + +bool FLACMetadata::GetLengthMilliseconds(unsigned __int64 *length) +{ + if (!streamInfo) + return false; + *length = (__int64)((double)(FLAC__int64)streamInfo->data.stream_info.total_samples / (double)streamInfo->data.stream_info.sample_rate * 1000.0 + 0.5); + return true; +} + +int FLACMetadata::GetNumMetadataItems() +{ + if (block) return block->data.vorbis_comment.num_comments; + else return 0; +} + +const char* FLACMetadata::EnumMetadata(int n, char *tag, int taglen) +{ + if (tag) tag[0]=0; + if (!block) return 0; + const char *entry = (const char *)block->data.vorbis_comment.comments[n].entry; + const char *metadata = strchr(entry, '='); // find the first equal + if (metadata) + { + if (tag) lstrcpynA(tag,entry,min(metadata-entry+1,taglen)); + return metadata+1; + } + else return 0; +} + +void FLACMetadata::SetTag(int pos, const char *tag) +{ + char * value = (char*)EnumMetadata(pos,0,0); + value = _strdup(value?value:""); + FLAC__StreamMetadata_VorbisComment_Entry entry; + size_t totalLen = strlen(tag) + 1 /* = */ + strlen(value); + entry.entry = (FLAC__byte *)malloc(totalLen + 1); + entry.length = totalLen; + StringCchPrintfA((char *)entry.entry, totalLen+1, "%s=%s", tag, value); + FLAC__metadata_object_vorbiscomment_set_comment(block, pos, entry, true); + free(value); +} + +bool FLACMetadata::GetPicture(FLAC__StreamMetadata_Picture_Type type, void **data, size_t *len, wchar_t **mimeType) +{ + if (!chain || !itr) + return false; + + FLAC__metadata_iterator_init(itr, chain); + while (1) + { + if (FLAC__METADATA_TYPE_PICTURE == FLAC__metadata_iterator_get_block_type(itr)) + { + FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(itr); + FLAC__StreamMetadata_Picture &picture = block->data.picture; + if (picture.type == type) + { + *len = picture.data_length; + *data = WASABI_API_MEMMGR->sysMalloc(picture.data_length); + if (!*data) + return false; + memcpy(*data, picture.data, picture.data_length); + + char *type = 0; + if (picture.mime_type) + type = strchr(picture.mime_type, '/'); + + if (type && *type) + { + type++; + + char *type2 = strchr(type, '/'); + if (type2 && *type2) type2++; + else type2 = type; + + int typelen = MultiByteToWideChar(CP_ACP, 0, type2, -1, 0, 0); + *mimeType = (wchar_t *)WASABI_API_MEMMGR->sysMalloc(typelen * sizeof(wchar_t)); + if (*mimeType) + MultiByteToWideChar(CP_ACP, 0, type, -1, *mimeType, typelen); + } + else + *mimeType = 0; // unknown! + + return true; + } + } + + if (FLAC__metadata_iterator_next(itr) == false) + break; + } + + return false; +} + +bool FLACMetadata::RemovePicture(FLAC__StreamMetadata_Picture_Type type) +{ + if (!chain || !itr) + return false; + + FLAC__metadata_iterator_init(itr, chain); + while (1) + { + if (FLAC__METADATA_TYPE_PICTURE == FLAC__metadata_iterator_get_block_type(itr)) + { + FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(itr); + FLAC__StreamMetadata_Picture &picture = block->data.picture; + if (picture.type == type) + { + FLAC__metadata_iterator_delete_block(itr, false); + return true; + } + } + + if (FLAC__metadata_iterator_next(itr) == false) + break; + } + + return false; +} + +bool FLACMetadata::SetPicture(FLAC__StreamMetadata_Picture_Type type, void *data, size_t len, const wchar_t *mimeType, int width, int height) +{ + if (!chain || !itr) + return false; + + FLAC__metadata_iterator_init(itr, chain); + while (1) + { + if (FLAC__METADATA_TYPE_PICTURE == FLAC__metadata_iterator_get_block_type(itr)) + { + FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(itr); + FLAC__StreamMetadata_Picture &picture = block->data.picture; + if (picture.type == type) + { + FLAC__metadata_object_picture_set_data(block, (FLAC__byte *)data, len, true); + picture.width = width; + picture.height = height; + picture.depth = 32; + picture.colors = 0; + + FLAC__metadata_object_picture_set_description(block, (FLAC__byte *)"", true);// TODO? + + char mime[256] = {0}; + if (wcsstr(mimeType, L"/") != 0) + { + StringCchPrintfA(mime, 256, "%S", mimeType); + } + else + { + StringCchPrintfA(mime, 256, "image/%S", mimeType); + } + FLAC__metadata_object_picture_set_mime_type(block, mime, true); + return true; + } + } + + if (FLAC__metadata_iterator_next(itr) == false) + break; + } + + // not found. let's add it + FLAC__StreamMetadata *newBlock = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE); + + FLAC__metadata_object_picture_set_data(newBlock, (FLAC__byte *)data, len, true); + FLAC__StreamMetadata_Picture &picture = newBlock->data.picture; + picture.type = type; + picture.width = width; + picture.height = height; + picture.depth = 32; + picture.colors = 0; + + FLAC__metadata_object_picture_set_description(newBlock, (FLAC__byte *)"", true);// TODO? + + char mime[256] = {0}; + StringCchPrintfA(mime, 256, "image/%S", mimeType); + FLAC__metadata_object_picture_set_mime_type(newBlock, mime, true); + + FLAC__metadata_iterator_insert_block_after(itr, newBlock); + return true; +} + +bool FLACMetadata::GetIndexPicture(int index, FLAC__StreamMetadata_Picture_Type *type, void **data, size_t *len, wchar_t **mimeType) +{ + if (!chain || !itr) + return false; + int i=0; + FLAC__metadata_iterator_init(itr, chain); + while (1) + { + if (FLAC__METADATA_TYPE_PICTURE == FLAC__metadata_iterator_get_block_type(itr)) + { + FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(itr); + FLAC__StreamMetadata_Picture &picture = block->data.picture; + if (i++ == index) + { + *type = picture.type; + *len = picture.data_length; + *data = WASABI_API_MEMMGR->sysMalloc(picture.data_length); + if (!*data) + return false; + memcpy(*data, picture.data, picture.data_length); + + char *type = 0; + if (picture.mime_type) + type = strchr(picture.mime_type, '/'); + + if (type && *type) + { + type++; + int typelen = MultiByteToWideChar(CP_ACP, 0, type, -1, 0, 0); + *mimeType = (wchar_t *)WASABI_API_MEMMGR->sysMalloc(typelen * sizeof(wchar_t)); + if (*mimeType) + MultiByteToWideChar(CP_ACP, 0, type, -1, *mimeType, typelen); + } + else + *mimeType = 0; // unknown! + + return true; + } + } + + if (FLAC__metadata_iterator_next(itr) == false) + break; + } + + return false; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/Metadata.h b/Src/Plugins/Input/in_flac/Metadata.h new file mode 100644 index 00000000..90616aeb --- /dev/null +++ b/Src/Plugins/Input/in_flac/Metadata.h @@ -0,0 +1,48 @@ +#ifndef NULLSOFT_IN_FLAC_METADATA_H +#define NULLSOFT_IN_FLAC_METADATA_H + +#include <FLAC/all.h> + +class FLACMetadata +{ +public: + FLACMetadata(); + ~FLACMetadata(); + bool Open(const wchar_t *filename, bool optimize=false); + void Reset(); + const char *GetMetadata(const char *tag); + void SetMetadata(const char *tag, const char *value); + void RemoveMetadata(const char *tag); + void RemoveMetadata(int n); + bool Save(const wchar_t *filename); + const FLAC__StreamMetadata_StreamInfo *GetStreamInfo(); + __int64 GetFileSize() { return filesize; } + bool GetLengthMilliseconds(unsigned __int64 *length); + int GetNumMetadataItems(); + const char* EnumMetadata(int n, char *tag, int len); + void SetTag(int n, const char *tag); + + bool GetPicture(FLAC__StreamMetadata_Picture_Type type, void **data, size_t *len, wchar_t **mimeType); + bool GetIndexPicture(int index, FLAC__StreamMetadata_Picture_Type *type, void **data, size_t *len, wchar_t **mimeType); + bool RemovePicture(FLAC__StreamMetadata_Picture_Type type); + bool SetPicture(FLAC__StreamMetadata_Picture_Type type, void *data, size_t len, const wchar_t *mimeType, int width, int height); +private: + FLAC__Metadata_Chain *chain; + FLAC__Metadata_Iterator *itr; + FLAC__StreamMetadata *block; + FLAC__StreamMetadata *streamInfo; + __int64 filesize; +}; + +class Info +{ +public: + FLACMetadata metadata; + const wchar_t *filename; +}; + +extern FLACMetadata *getMetadata; +extern wchar_t *getFileInfoFn; +extern Info *info; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/Preferences.cpp b/Src/Plugins/Input/in_flac/Preferences.cpp new file mode 100644 index 00000000..95500b1c --- /dev/null +++ b/Src/Plugins/Input/in_flac/Preferences.cpp @@ -0,0 +1,110 @@ +/* +** Copyright (C) 2007-2011 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ +#include "main.h" +#include <FLAC/all.h> +#include "resource.h" +#include "../Agave/Language/api_language.h" +#include "../nu/AutoChar.h" +#include <assert.h> +#include <strsafe.h> + +bool fixBitrate=false; +bool config_average_bitrate=true; + +// the return pointer has been malloc'd. Use free() when you are done. +char *BuildExtensions(const char *extensions) +{ + char name[64] = {0}; + WASABI_API_LNGSTRING_BUF(IDS_FLAC_FILES,name,64); + size_t length = strlen(extensions) + 1 + strlen(name) + 2; + char *newExt = (char *)malloc(length); + char *ret = newExt; // save because we modify newExt + + // copy extensions + StringCchCopyExA(newExt, length, extensions, &newExt, &length, 0); + newExt++; + length--; + + // copy description + StringCchCopyExA(newExt, length, name, &newExt, &length, 0); + newExt++; + length--; + + // double null terminate + assert(length == 1); + *newExt = 0; + + return ret; +} + +static INT_PTR CALLBACK PreferencesProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) + { + case WM_INITDIALOG: + { + wchar_t config_extensions[128] = {0}; + GetPrivateProfileStringW(L"in_flac", L"extensions", DEFAULT_EXTENSIONSW, config_extensions, 128, winampINI); + SetDlgItemTextW(hwndDlg, IDC_EXTENSIONS, config_extensions); + CheckDlgButton(hwndDlg, IDC_AVERAGE_BITRATE, config_average_bitrate?BST_CHECKED:BST_UNCHECKED); + } + return TRUE; + + case WM_DESTROY: + break; + + case WM_COMMAND: + switch(LOWORD(wParam)) + { + case IDOK: + { + wchar_t config_extensions[128] = {0}; + GetDlgItemTextW(hwndDlg, IDC_EXTENSIONS, config_extensions, 128); + if (!lstrcmpiW(config_extensions, DEFAULT_EXTENSIONSW)) + WritePrivateProfileStringW(L"in_flac", L"extensions", 0, winampINI); + else + WritePrivateProfileStringW(L"in_flac", L"extensions", config_extensions, winampINI); + + plugin.FileExtensions = BuildExtensions(AutoChar(config_extensions)); + config_average_bitrate = !!IsDlgButtonChecked(hwndDlg, IDC_AVERAGE_BITRATE); + if (config_average_bitrate) + WritePrivateProfileStringW(L"in_flac", L"average_bitrate", L"1", winampINI); + else + WritePrivateProfileStringW(L"in_flac", L"average_bitrate", L"0", winampINI); + + fixBitrate=true; + EndDialog(hwndDlg, 0); + } + break; + + case IDCANCEL: + EndDialog(hwndDlg, 0); + break; + } + break; + } + return 0; +} + +void Config(HWND hwndParent) +{ + WASABI_API_DIALOGBOXW(IDD_PREFERENCES, hwndParent, PreferencesProc); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/QuickBuf.h b/Src/Plugins/Input/in_flac/QuickBuf.h new file mode 100644 index 00000000..c615613b --- /dev/null +++ b/Src/Plugins/Input/in_flac/QuickBuf.h @@ -0,0 +1,50 @@ +#ifndef NULLSOFT_IN_FLAC_QUICKBUF_H +#define NULLSOFT_IN_FLAC_QUICKBUF_H + +#include <malloc.h> +class QuickBuf +{ +public: + QuickBuf() : buffer(0), len(0) + { + } + + void Reserve(size_t res) + { + if (res > len) + { + len=res; + free(buffer); + buffer = malloc(len); + } + } + + void Free() + { + free(buffer); + buffer=0; + len=0; + } + + void Move(size_t offset) + { + memmove(buffer, (char *)buffer + offset, len-offset); + } + + + operator void *() + { + return buffer; + } + + operator char *() + { + return (char *)buffer; + } + +private: + void *buffer; + size_t len; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/RawReader.cpp b/Src/Plugins/Input/in_flac/RawReader.cpp new file mode 100644 index 00000000..9c6814f5 --- /dev/null +++ b/Src/Plugins/Input/in_flac/RawReader.cpp @@ -0,0 +1,174 @@ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include "RawReader.h" +#include "main.h" +#include <limits.h> +#include <shlwapi.h> +#include "../nu/AutoWide.h" +#include <new> +#include <strsafe.h> +#include "nswasabi/ReferenceCounted.h" + +static bool IsMyExtension(const wchar_t *filename) +{ + const wchar_t *extension = PathFindExtensionW(filename); + if (extension && *extension) + { + wchar_t exts[128] = {0}; + GetPrivateProfileStringW(L"in_flac", L"extensions", DEFAULT_EXTENSIONSW, exts, 128, winampINI); + + extension++; + wchar_t *b = exts; + wchar_t *c; + do + { + wchar_t d[20] = {0}; + StringCchCopyW(d, 15, b); + if ((c = wcschr(b, L';'))) + { + if ((c-b)<15) + d[c - b] = 0; + } + + if (!lstrcmpiW(extension, d)) + return true; + + b = c + 1; + } + while (c); + } + return false; +} + +int RawMediaReaderService::CreateRawMediaReader(const wchar_t *filename, ifc_raw_media_reader **out_reader) +{ + if (IsMyExtension(filename)) + { + nx_file_t file; + ReferenceCountedNXString filename_nx; + ReferenceCountedNXURI filename_uri; + NXStringCreateWithUTF16(&filename_nx, filename); + NXURICreateWithNXString(&filename_uri, filename_nx); + + int ret = NXFileOpenFile(&file, filename_uri, nx_file_FILE_read_binary); + if (ret != NErr_Success) + return ret; + + RawMediaReader *reader = new (std::nothrow) RawMediaReader(); + if (!reader) + { + NXFileRelease(file); + return NErr_OutOfMemory; + } + + ret = reader->Initialize(file); + NXFileRelease(file); + if (ret != NErr_Success) + { + delete reader; + return ret; + } + + *out_reader = reader; + return NErr_Success; + + } + else + { + return NErr_False; + } +} + +#define CBCLASS RawMediaReaderService +START_DISPATCH; +CB(CREATERAWMEDIAREADER, CreateRawMediaReader); +END_DISPATCH; +#undef CBCLASS + +static void OnError(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + //client_data=client_data; // dummy line so i can set a breakpoint +} + +static void OnMetadata(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) +{ + +} + +static FLAC__StreamDecoderWriteStatus OnAudio(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data) +{ + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +RawMediaReader::RawMediaReader() +{ + decoder=0; + file=0; +} + +RawMediaReader::~RawMediaReader() +{ + if (decoder) + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); +} + +int RawMediaReader::Initialize(nx_file_t file) +{ + this->file = NXFileRetain(file); + decoder = FLAC__stream_decoder_new(); + if (!decoder) + return NErr_FailedCreate; + + FLAC__stream_decoder_set_metadata_ignore(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT); + state.SetFile(file); + state.SetObject(this); + FLAC__stream_decoder_set_md5_checking(decoder, true); + if(FLAC__stream_decoder_init_stream( + decoder, + FLAC_NXFile_Read, + FLAC_NXFile_Seek, + FLAC_NXFile_Tell, + FLAC_NXFile_Length, + FLAC_NXFile_EOF, + OnAudio, + OnMetadata, + OnError, + &state + ) != FLAC__STREAM_DECODER_INIT_STATUS_OK) + { + FLAC__stream_decoder_delete(decoder); + decoder=0; + return NErr_Error; + } + + FLAC__stream_decoder_process_until_end_of_metadata(decoder); + uint64_t position; + FLAC__stream_decoder_get_decode_position(decoder, &position); + FLAC__stream_decoder_delete(decoder); + decoder=0; + NXFileSeek(file, position); + + return NErr_Success; +} + +int RawMediaReader::Read(void *buffer, size_t buffer_size, size_t *bytes_read) +{ + return NXFileRead(file, buffer, buffer_size, bytes_read); +} + +size_t RawMediaReader::Release() +{ + + delete this; + return 0; +} + +#define CBCLASS RawMediaReader +START_DISPATCH; +CB(RELEASE, Release); +CB(RAW_READ, Read); +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/RawReader.h b/Src/Plugins/Input/in_flac/RawReader.h new file mode 100644 index 00000000..68348e38 --- /dev/null +++ b/Src/Plugins/Input/in_flac/RawReader.h @@ -0,0 +1,36 @@ +#pragma once +#include "../Agave/DecodeFile/svc_raw_media_reader.h" +#include "../Agave/DecodeFile/ifc_raw_media_reader.h" +#include "FLACFileCallbacks.h" +#include <FLAC/all.h> + +// {E906F4DC-3080-4B9B-951F-85950193ACBF} +static const GUID flac_raw_reader_guid = +{ 0xe906f4dc, 0x3080, 0x4b9b, { 0x95, 0x1f, 0x85, 0x95, 0x1, 0x93, 0xac, 0xbf } }; + + +class RawMediaReaderService : public svc_raw_media_reader +{ +public: + static const char *getServiceName() { return "FLAC Raw Reader"; } + static GUID getServiceGuid() { return flac_raw_reader_guid; } + int CreateRawMediaReader(const wchar_t *filename, ifc_raw_media_reader **reader); +protected: + RECVS_DISPATCH; +}; + +class RawMediaReader : public ifc_raw_media_reader +{ +public: + RawMediaReader(); + ~RawMediaReader(); + int Initialize(nx_file_t ); + int Read(void *buffer, size_t buffer_size, size_t *bytes_read); + size_t Release(); +protected: + RECVS_DISPATCH; +private: + FLAC__StreamDecoder *decoder; + FLACClientData state; + nx_file_t file; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/Stopper.cpp b/Src/Plugins/Input/in_flac/Stopper.cpp new file mode 100644 index 00000000..3ff4cad3 --- /dev/null +++ b/Src/Plugins/Input/in_flac/Stopper.cpp @@ -0,0 +1,65 @@ +/* +** Copyright (C) 2008 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: January 31, 2008 +** +*/ +#include "Stopper.h" +#include "main.h" +#include "../Winamp/wa_ipc.h" + +Stopper::Stopper() : isplaying(0), timems(0) +{ +} + +void Stopper::ChangeTracking(bool mode) +{ + SendMessage(plugin.hMainWindow, WM_USER, mode, IPC_ALLOW_PLAYTRACKING); // enable / disable stats updating +} + +void Stopper::Stop() +{ + isplaying = SendMessage(plugin.hMainWindow, WM_USER, 0, IPC_ISPLAYING); + if (isplaying) + { + ChangeTracking(0); // disable stats updating + timems = SendMessage(plugin.hMainWindow, WM_USER, 0, IPC_GETOUTPUTTIME); + SendMessage(plugin.hMainWindow, WM_COMMAND, 40047, 0); // Stop + } +} + +void Stopper::Play() +{ + if (isplaying) // this works _most_ of the time, not sure why a small portion of the time it doesnt hrmph :/ + // ideally we should replace it with a system that pauses the decode thread, closes its file, + // does the shit, and reopens and reseeks to the new offset. for gaplessness + { + if (timems) + { + m_force_seek = timems; // SendMessage(mod.hMainWindow,WM_USER,timems,106); + } + else + m_force_seek = -1; + SendMessage(plugin.hMainWindow, WM_COMMAND, 40045, 0); // Play + //m_force_seek = -1; + if (isplaying & 2) + { + SendMessage(plugin.hMainWindow, WM_COMMAND, 40046, 0); // Pause + } + ChangeTracking(1); // enable stats updating + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/Stopper.h b/Src/Plugins/Input/in_flac/Stopper.h new file mode 100644 index 00000000..05d5ac6f --- /dev/null +++ b/Src/Plugins/Input/in_flac/Stopper.h @@ -0,0 +1,10 @@ +#pragma once +class Stopper +{ +public: + Stopper(); + void ChangeTracking(bool); + void Stop(); + void Play(); + int isplaying, timems; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/StreamFileWin32.cpp b/Src/Plugins/Input/in_flac/StreamFileWin32.cpp new file mode 100644 index 00000000..f34b0863 --- /dev/null +++ b/Src/Plugins/Input/in_flac/StreamFileWin32.cpp @@ -0,0 +1,129 @@ +/* +** Copyright (C) 2007-2012 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ +#include <windows.h> +#include <FLAC/all.h> +#include <assert.h> +#include "StreamFileWin32.h" + +FLAC__StreamDecoderReadStatus Win32_Read(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data) +{ + assert(*bytes <= 4294967295U); + + HANDLE file = ((Win32_State *)client_data)->handle; + if(*bytes > 0) + { + assert(sizeof(FLAC__byte) == 1); + DWORD bytesRead=0, bytesToRead=*bytes; + BOOL result = ReadFile(file, buffer, bytesToRead, &bytesRead, NULL); + *bytes = bytesRead; + + if (!result) + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + else if(bytesRead == 0) + { + ((Win32_State *)client_data)->endOfFile = true; + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + } + else + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; + } + else + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; +} + +__int64 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; +} + +FLAC__StreamDecoderSeekStatus Win32_Seek(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data) +{ + HANDLE file = ((Win32_State *)client_data)->handle; + + __int64 result = Seek64(file, absolute_byte_offset, FILE_BEGIN); + + if (result == INVALID_SET_FILE_POINTER) + return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; + else + { + ((Win32_State *)client_data)->endOfFile = false; + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; + } +} + +FLAC__StreamDecoderTellStatus Win32_Tell(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data) +{ + HANDLE file = ((Win32_State *)client_data)->handle; + + __int64 position = Seek64(file, 0, FILE_CURRENT); + + if (position == INVALID_SET_FILE_POINTER) + return FLAC__STREAM_DECODER_TELL_STATUS_ERROR; + else + { + *absolute_byte_offset=position; + return FLAC__STREAM_DECODER_TELL_STATUS_OK; + } +} +__int64 FileSize64(HANDLE file) +{ + LARGE_INTEGER position; + position.QuadPart=0; + position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart); + + if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) + return INVALID_FILE_SIZE; + else + return position.QuadPart; +} + +FLAC__StreamDecoderLengthStatus Win32_Length(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data) +{ + HANDLE file = ((Win32_State *)client_data)->handle; + + LARGE_INTEGER position; + position.QuadPart=0; + position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart); + + if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) + return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR; + else + { + *stream_length = position.QuadPart; + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; + } +} + +FLAC__bool Win32_EOF(const FLAC__StreamDecoder *decoder, void *client_data) +{ + return ((Win32_State *)client_data)->endOfFile; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/StreamFileWin32.h b/Src/Plugins/Input/in_flac/StreamFileWin32.h new file mode 100644 index 00000000..e3a98d79 --- /dev/null +++ b/Src/Plugins/Input/in_flac/StreamFileWin32.h @@ -0,0 +1,23 @@ +#ifndef NULLSOFT_IN_FLAC_STREAMFILEWIN32_H +#define NULLSOFT_IN_FLAC_STREAMFILEWIN32_H + +#include <FLAC/all.h> +#include <windows.h> + +struct Win32_State +{ + void *userData; + HANDLE handle; + bool endOfFile; +}; + +FLAC__StreamDecoderReadStatus Win32_Read(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data); +FLAC__StreamDecoderSeekStatus Win32_Seek(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data); +FLAC__StreamDecoderTellStatus Win32_Tell(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data); +FLAC__StreamDecoderLengthStatus Win32_Length(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data); +FLAC__bool Win32_EOF(const FLAC__StreamDecoder *decoder, void *client_data); + +// helper function extern'd here because DecodeThread needs it +__int64 FileSize64(HANDLE file); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/api__in_flv.h b/Src/Plugins/Input/in_flac/api__in_flv.h new file mode 100644 index 00000000..f82c0db4 --- /dev/null +++ b/Src/Plugins/Input/in_flac/api__in_flv.h @@ -0,0 +1,11 @@ +#ifndef NULLSOFT_IN_FLAC_API_H +#define NULLSOFT_IN_FLAC_API_H + +#include "../Agave/Config/api_config.h" +extern api_config *AGAVE_API_CONFIG; + +#include <api/memmgr/api_memmgr.h> +extern api_memmgr *memmgr; +#define WASABI_API_MEMMGR memmgr + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/in_flac.rc b/Src/Plugins/Input/in_flac/in_flac.rc new file mode 100644 index 00000000..937bcb8b --- /dev/null +++ b/Src/Plugins/Input/in_flac/in_flac.rc @@ -0,0 +1,163 @@ +// 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 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_INFOCHILD_ADVANCED DIALOGEX 0, 0, 341, 164 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Advanced",IDC_STATIC,0,0,341,164 + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,6,13,164,143 + LTEXT "Name:",IDC_STATIC,175,13,22,8 + EDITTEXT IDC_NAME,175,23,159,14,ES_AUTOHSCROLL + LTEXT "Value:",IDC_STATIC,175,39,21,8 + EDITTEXT IDC_VALUE,175,49,159,90,ES_MULTILINE | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL + PUSHBUTTON "Add New",IDC_BUTTON_ADD,176,142,50,13 + PUSHBUTTON "Delete",IDC_BUTTON_DEL,230,142,50,13 + PUSHBUTTON "Delete All",IDC_BUTTON_DELALL,284,142,50,13 +END + +IDD_PREFERENCES DIALOGEX 0, 0, 175, 84 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Nullsoft FLAC Decoder Preferences" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Extensions",IDC_STATIC,5,4,166,43 + EDITTEXT IDC_EXTENSIONS,12,16,153,13,ES_AUTOHSCROLL + LTEXT "Semi-colon separated (e.g. FLAC;FLA)",IDC_STATIC,12,33,124,8 + CONTROL "Show average bitrate",IDC_AVERAGE_BITRATE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,5,52,110,10 + DEFPUSHBUTTON "OK",IDOK,67,67,50,13 + PUSHBUTTON "Cancel",IDCANCEL,121,67,50,13 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_INFOCHILD_ADVANCED, DIALOG + BEGIN + RIGHTMARGIN, 340 + BOTTOMMARGIN, 163 + END + + IDD_PREFERENCES, DIALOG + BEGIN + LEFTMARGIN, 5 + RIGHTMARGIN, 171 + TOPMARGIN, 4 + BOTTOMMARGIN, 80 + END +END +#endif // APSTUDIO_INVOKED + + +#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 + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_FLAC_DECODER_OLD "Nullsoft FLAC Decoder" + IDS_TO_ADVANCED_MODE "to advanced mode >>" + IDS_TO_SIMPLE_MODE "<< to simple mode" + IDS_NAME "Name" + IDS_VALUE "Value" + IDS_TRACK_GAIN "Track Gain: %s\n" + IDS_ALBUM_GAIN "Album Gain: %s\n" + IDS_NOT_PRESENT "not present" + IDS_LENGTH_IN_SECONDS "Length: %I64d seconds\n" + IDS_CHANNELS "Channels: %d\n" + IDS_BITS_PER_SAMPLE "Bits per sample: %d\n" + IDS_SAMPLE_RATE "Sample Rate: %d Hz\n" + IDS_FILE_SIZE_IN_BYTES "File Size: %I64d bytes\n" + IDS_AVERAGE_BITRATE "Average bitrate: %I64d kbps\n" + IDS_COMPRESSION_RATIO "Compression Ratio: %u%%\n" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_FLAC_DECODER "Nullsoft FLAC Decoder v%s" + 65535 "{9475116B-F8C4-4dff-BC19-9601B238557D}" +END + +STRINGTABLE +BEGIN + IDS_LIBFLAC_DLL_MISSING "[libflac.dll missing]" + IDS_FLAC_FILES "FLAC Files" + IDS_FAMILY_STRING "Free Lossless Audio Codec File" + IDS_ABOUT_TEXT "%s\n© 2007-2023 Winamp SA\nWritten by Ben Allison\nBuild date: %hs\n\nUsing FLAC v%hs" + IDS_CANNOT_SAVE_METADATA "Cannot save metadata: Error writing data." + IDS_ERROR_SAVING_METADATA "Error saving metadata" +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_flac/in_flac.sln b/Src/Plugins/Input/in_flac/in_flac.sln new file mode 100644 index 00000000..192ea80b --- /dev/null +++ b/Src/Plugins/Input/in_flac/in_flac.sln @@ -0,0 +1,105 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29613.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_flac", "in_flac.vcxproj", "{2B754A9B-B449-45F2-93B4-49884A7691B6}" + ProjectSection(ProjectDependencies) = postProject + {4FC28B55-2A14-43D5-86F7-201054F338A9} = {4FC28B55-2A14-43D5-86F7-201054F338A9} + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} + {4CEFBC83-C215-11DB-8314-0800200C9A66} = {4CEFBC83-C215-11DB-8314-0800200C9A66} + {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D} + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\replicant\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}" + ProjectSection(ProjectDependencies) = postProject + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\replicant\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\replicant\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}" + ProjectSection(ProjectDependencies) = postProject + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\replicant\zlib\zlib.vcxproj", "{0F9730E4-45DA-4BD2-A50A-403A4BC9751A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libFLAC_dynamic", "..\libFLAC\libFLAC_dynamic.vcxproj", "{4CEFBC83-C215-11DB-8314-0800200C9A66}" + ProjectSection(ProjectDependencies) = postProject + {4FC28B55-2A14-43D5-86F7-201054F338A9} = {4FC28B55-2A14-43D5-86F7-201054F338A9} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libogg", "..\libogg\libogg.vcxproj", "{4FC28B55-2A14-43D5-86F7-201054F338A9}" +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 + {2B754A9B-B449-45F2-93B4-49884A7691B6}.Debug|Win32.ActiveCfg = Debug|Win32 + {2B754A9B-B449-45F2-93B4-49884A7691B6}.Debug|Win32.Build.0 = Debug|Win32 + {2B754A9B-B449-45F2-93B4-49884A7691B6}.Debug|x64.ActiveCfg = Debug|Win32 + {2B754A9B-B449-45F2-93B4-49884A7691B6}.Release|Win32.ActiveCfg = Release|Win32 + {2B754A9B-B449-45F2-93B4-49884A7691B6}.Release|Win32.Build.0 = Release|Win32 + {2B754A9B-B449-45F2-93B4-49884A7691B6}.Release|x64.ActiveCfg = Release|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.ActiveCfg = Debug|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.Build.0 = Debug|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.ActiveCfg = Debug|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.Build.0 = Debug|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.ActiveCfg = Release|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.Build.0 = Release|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.ActiveCfg = Release|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.Build.0 = Release|x64 + {4CEFBC83-C215-11DB-8314-0800200C9A66}.Debug|Win32.ActiveCfg = Debug|Win32 + {4CEFBC83-C215-11DB-8314-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 + {4CEFBC83-C215-11DB-8314-0800200C9A66}.Debug|x64.ActiveCfg = Debug|x64 + {4CEFBC83-C215-11DB-8314-0800200C9A66}.Debug|x64.Build.0 = Debug|x64 + {4CEFBC83-C215-11DB-8314-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 + {4CEFBC83-C215-11DB-8314-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 + {4CEFBC83-C215-11DB-8314-0800200C9A66}.Release|x64.ActiveCfg = Release|x64 + {4CEFBC83-C215-11DB-8314-0800200C9A66}.Release|x64.Build.0 = Release|x64 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Debug|Win32.ActiveCfg = Debug|Win32 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Debug|Win32.Build.0 = Debug|Win32 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Debug|x64.ActiveCfg = Debug|x64 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Debug|x64.Build.0 = Debug|x64 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Release|Win32.ActiveCfg = Release|Win32 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Release|Win32.Build.0 = Release|Win32 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Release|x64.ActiveCfg = Release|x64 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A0F24E75-21BB-4DDF-A287-A481B554B4EB} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_flac/in_flac.vcxproj b/Src/Plugins/Input/in_flac/in_flac.vcxproj new file mode 100644 index 00000000..cb9250cc --- /dev/null +++ b/Src/Plugins/Input/in_flac/in_flac.vcxproj @@ -0,0 +1,307 @@ +<?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>{2B754A9B-B449-45F2-93B4-49884A7691B6}</ProjectGuid> + <RootNamespace>in_flac</RootNamespace> + <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" 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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|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> + <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"> + <VcpkgEnableManifest>false</VcpkgEnableManifest> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>..\..\..\Wasabi;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;IN_FLAC2_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS;REPLICANT_INCLUDE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4244;4995;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>nxlite.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </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;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;_DEBUG;_WINDOWS;_USRDLL;IN_FLAC2_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS;REPLICANT_INCLUDE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4244;4267;4995;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>nxlite.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </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;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;IN_FLAC2_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS;REPLICANT_INCLUDE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <PrecompiledHeader /> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4995;4244;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>nxlite.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <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> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </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;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;IN_FLAC2_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS;REPLICANT_INCLUDE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <PrecompiledHeader /> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4244;4267;4995;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>nxlite.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <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> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </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> + <ProjectReference Include="..\..\..\replicant\nx\nx.vcxproj"> + <Project>{57c90706-b25d-4aca-9b33-95cdb2427c27}</Project> + <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies> + <ReferenceOutputAssembly>true</ReferenceOutputAssembly> + </ProjectReference> + <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj"> + <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\..\..\nu\SpillBuffer.cpp" /> + <ClCompile Include="About.cpp" /> + <ClCompile Include="AlbumArt.cpp" /> + <ClCompile Include="DecodeThread.cpp" /> + <ClCompile Include="ExtendedFileInfo.cpp" /> + <ClCompile Include="ExtendedRead.cpp" /> + <ClCompile Include="FileInfo.cpp" /> + <ClCompile Include="FLACFileCallbacks.cpp" /> + <ClCompile Include="main.cpp" /> + <ClCompile Include="Metadata.cpp" /> + <ClCompile Include="mkv_flac_decoder.cpp" /> + <ClCompile Include="Preferences.cpp" /> + <ClCompile Include="RawReader.cpp" /> + <ClCompile Include="Stopper.cpp" /> + <ClCompile Include="StreamFileWin32.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\Agave\AlbumArt\svc_albumArtProvider.h" /> + <ClInclude Include="..\..\..\Agave\api\config\api_config.h" /> + <ClInclude Include="..\..\..\Wasabi\api\memmgr\api_memmgr.h" /> + <ClInclude Include="..\..\..\Wasabi\api\service\api_service.h" /> + <ClInclude Include="..\..\..\Winamp\api_language.h" /> + <ClInclude Include="..\..\..\Winamp\IN2.H" /> + <ClInclude Include="..\..\..\Winamp\wa_ipc.h" /> + <ClInclude Include="AlbumArt.h" /> + <ClInclude Include="api__in_flv.h" /> + <ClInclude Include="FLACFileCallbacks.h" /> + <ClInclude Include="main.h" /> + <ClInclude Include="Metadata.h" /> + <ClInclude Include="mkv_flac_decoder.h" /> + <ClInclude Include="QuickBuf.h" /> + <ClInclude Include="RawReader.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="Stopper.h" /> + <ClInclude Include="StreamFileWin32.h" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_flac.rc" /> + </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_flac/in_flac.vcxproj.filters b/Src/Plugins/Input/in_flac/in_flac.vcxproj.filters new file mode 100644 index 00000000..06016ed5 --- /dev/null +++ b/Src/Plugins/Input/in_flac/in_flac.vcxproj.filters @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="About.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="AlbumArt.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="DecodeThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedFileInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedRead.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="FileInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="FLACFileCallbacks.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Metadata.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="mkv_flac_decoder.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Preferences.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="RawReader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Stopper.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="StreamFileWin32.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\SpillBuffer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="StreamFileWin32.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Stopper.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="RawReader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="QuickBuf.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="mkv_flac_decoder.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Metadata.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="main.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FLACFileCallbacks.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="api__in_flv.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="AlbumArt.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Agave\api\config\api_config.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Winamp\api_language.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Wasabi\api\memmgr\api_memmgr.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Wasabi\api\service\api_service.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Winamp\IN2.H"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Agave\AlbumArt\svc_albumArtProvider.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Winamp\wa_ipc.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{be0e665c-0dc5-41ca-98ab-39750a1644f6}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{f00291da-d3eb-4848-bb67-7282ea12bb80}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{f3f6b1c6-d21e-44bd-8b2e-10cdfa6c3e18}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_flac.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/main.cpp b/Src/Plugins/Input/in_flac/main.cpp new file mode 100644 index 00000000..047d1529 --- /dev/null +++ b/Src/Plugins/Input/in_flac/main.cpp @@ -0,0 +1,277 @@ +/* +** Copyright © 2007-2014 Winamp SA +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ +#include "main.h" +#include "api__in_flv.h" +#include "Metadata.h" +#include "../Agave/Language/api_language.h" +#include <api/service/waServiceFactory.h> +#include "../Winamp/wa_ipc.h" +#include "../nu/Singleton.h" +#include "../nu/Autochar.h" +#include <shlwapi.h> +#include "resource.h" +#include "AlbumArt.h" +#include "RawReader.h" +#include <strsafe.h> +#include "nswasabi/ReferenceCounted.h" +#include "mkv_flac_decoder.h" + +api_config *AGAVE_API_CONFIG = 0; +api_memmgr *WASABI_API_MEMMGR=0; +AlbumArtFactory albumArtFactory; +static RawMediaReaderService raw_media_reader_service; +static SingletonServiceFactory<svc_raw_media_reader, RawMediaReaderService> raw_factory; +static MKVDecoder mkv_service; +static SingletonServiceFactory<svc_mkvdecoder, MKVDecoder> mkv_factory; +// wasabi based services for localisation support +api_language *WASABI_API_LNG = 0; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; + +HANDLE killswitch=0; +HANDLE playThread=0; + +const wchar_t *winampINI=0; +void Config(HWND hwndParent); +void About(HWND hwndParent); +wchar_t pluginName[256] = {0}; + +int Init() +{ + if (!IsWindow(plugin.hMainWindow)) + return IN_INIT_FAILURE; + + killswitch = CreateEvent(0, TRUE, FALSE, 0); + plugin.service->service_register(&albumArtFactory); + raw_factory.Register(plugin.service, &raw_media_reader_service); + mkv_factory.Register(plugin.service, &mkv_service); + + waServiceFactory *sf = (waServiceFactory *)plugin.service->service_getServiceByGuid(AgaveConfigGUID); + if (sf) AGAVE_API_CONFIG= (api_config *)sf->getInterface(); + sf = (waServiceFactory *)plugin.service->service_getServiceByGuid(memMgrApiServiceGuid); + if (sf) WASABI_API_MEMMGR= (api_memmgr *)sf->getInterface(); + + // loader so that we can get the localisation service api for use + sf = plugin.service->service_getServiceByGuid(languageApiGUID); + if (sf) WASABI_API_LNG = reinterpret_cast<api_language*>(sf->getInterface()); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(plugin.hDllInstance,InFlacLangGUID); + + StringCchPrintfW(pluginName,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_FLAC_DECODER),PLUGIN_VER); + plugin.description = (char*)pluginName; + + winampINI = (const wchar_t *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GETINIFILEW); + + wchar_t exts[1024] = {0}; + GetPrivateProfileStringW(L"in_flac", L"extensions", DEFAULT_EXTENSIONSW, exts, 1024, winampINI); + plugin.FileExtensions = BuildExtensions(AutoChar(exts)); + + config_average_bitrate = !!GetPrivateProfileIntW(L"in_flac", L"average_bitrate", 1, winampINI); + + plugin.UsesOutputPlug|=8; + return IN_INIT_SUCCESS; +} + +void Quit() +{ + CloseHandle(killswitch); + waServiceFactory *sf = (waServiceFactory *)plugin.service->service_getServiceByGuid(AgaveConfigGUID); + if (sf) sf->releaseInterface(AGAVE_API_CONFIG); + sf = (waServiceFactory *)plugin.service->service_getServiceByGuid(memMgrApiServiceGuid); + if (sf) sf->releaseInterface(WASABI_API_MEMMGR); + + plugin.service->service_deregister(&albumArtFactory); + raw_factory.Deregister(plugin.service); +} + +void GetFileInfo(const in_char *file, in_char *title, int *length_in_ms) +{ + if (length_in_ms) + { + if (!file || !*file && currentSongLength != -1000) + *length_in_ms = currentSongLength; + else + { + FLACMetadata metadata; + unsigned __int64 length_in_msec; + if (metadata.Open(file) && metadata.GetLengthMilliseconds(&length_in_msec)) + *length_in_ms = (int)length_in_msec; + else + *length_in_ms=-1000; + } + } + if (title) *title=0; +} + +int InfoBox(const in_char *file, HWND hwndParent) { return 0; } + +int IsOurFile(const in_char *fn) +{ + return 0; +} + +wchar_t *lastfn=0; +HANDLE threadStarted; +extern FLAC__uint64 lastoutputtime; +extern volatile int bufferCount; +int Play(const in_char *fn) +{ + free(lastfn); + lastfn=_wcsdup(fn); + currentSongLength=-1000; + plugin.is_seekable = 0; + plugin.SetInfo(0,0,0,0); + lastoutputtime=0; + bufferCount=0; + ResetEvent(killswitch); + DWORD threadId; + threadStarted = CreateEvent(0, TRUE, FALSE, 0); + + ReferenceCountedNXString filename_nx; + nx_uri_t filename_uri; + NXStringCreateWithUTF16(&filename_nx, fn); + NXURICreateWithNXString(&filename_uri, filename_nx); + + playThread=CreateThread(0, 0, FLACThread, filename_uri, 0, &threadId); + SetThreadPriority(playThread, AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + WaitForSingleObject(threadStarted, INFINITE); + CloseHandle(threadStarted); + return 0; +} + +int localPause=0; +void Pause() +{ + localPause=1; + QueueUserAPC(APCPause, playThread, (ULONG_PTR)1); +} + +void UnPause() +{ + localPause=0; + QueueUserAPC(APCPause, playThread, (ULONG_PTR)0); +} + +int IsPaused() +{ + return localPause; +} + +void Stop() +{ + SetEvent(killswitch); + WaitForSingleObject(playThread, INFINITE); + currentSongLength=-1000; + plugin.outMod->Close(); + plugin.SAVSADeInit(); + free(lastfn); + lastfn=0; +} + +int GetLength() +{ + return currentSongLength; +} + + +int GetOutputTime() +{ + if (bufferCount) + return bufferCount; + + if (plugin.outMod) + { + return (int)lastoutputtime + (plugin.outMod->GetOutputTime() - plugin.outMod->GetWrittenTime()); + } + else + return 0; +} + +void SetOutputTime(int time_in_ms) +{ + lastoutputtime=time_in_ms; // cheating a bit here :) + QueueUserAPC(APCSeek, playThread, (ULONG_PTR)time_in_ms); +} + +int pan = 0, volume = -666; +void SetVolume(int _volume) +{ + volume = _volume; + if (plugin.outMod) + plugin.outMod->SetVolume(volume); +} + +void SetPan(int _pan) +{ + pan = _pan; + if (plugin.outMod) + plugin.outMod->SetPan(pan); +} + +void EQSet(int on, char data[10], int preamp) +{} + +In_Module plugin = +{ + IN_VER_RET, + "nullsoft(in_flac.dll)", + 0, + 0, + "FLAC\0FLAC Files\0", + 1, + 1, + Config, + 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_flac/main.h b/Src/Plugins/Input/in_flac/main.h new file mode 100644 index 00000000..bbe316a4 --- /dev/null +++ b/Src/Plugins/Input/in_flac/main.h @@ -0,0 +1,40 @@ +#ifndef NULLSOFT_IN_FLAC_MAIN_H +#define NULLSOFT_IN_FLAC_MAIN_H + +#define PLUGIN_VER L"3.2" + +#include <windows.h> +extern HANDLE killswitch; + +#include "../Winamp/in2.h" +extern In_Module plugin; + +DWORD CALLBACK FLACThread(LPVOID param); +extern int pan, volume; +extern volatile int currentSongLength; + +void CALLBACK APCPause(ULONG_PTR data); +void CALLBACK APCSeek(ULONG_PTR data); + +void ResetMetadataCache(); + +#include <FLAC/all.h> +void InterleaveAndTruncate(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain=1.0); + +extern const wchar_t *winampINI; +char *BuildExtensions(const char *extensions); +extern bool config_average_bitrate; +extern bool fixBitrate; + +extern int m_force_seek; // set this to something other than -1 to make the file start from the given time (in ms) +extern wchar_t *lastfn; + +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = + { + 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } + }; + +#define DEFAULT_EXTENSIONS "FLAC" +#define DEFAULT_EXTENSIONSW L"FLAC" +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/mkv_flac_decoder.cpp b/Src/Plugins/Input/in_flac/mkv_flac_decoder.cpp new file mode 100644 index 00000000..f7ef28da --- /dev/null +++ b/Src/Plugins/Input/in_flac/mkv_flac_decoder.cpp @@ -0,0 +1,204 @@ +#include"mkv_flac_decoder.h" +#include "main.h" + +static FLAC__StreamDecoderReadStatus Packet_Read(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data) +{ + packet_client_data_t packet = (packet_client_data_t)client_data; + size_t to_copy = *bytes; + if (to_copy > packet->buffer_length) { + to_copy = packet->buffer_length; + } + memcpy(buffer, packet->buffer, to_copy); + *bytes = to_copy; + packet->buffer += to_copy; + packet->buffer_length -= to_copy; + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +} + +static FLAC__StreamDecoderSeekStatus Packet_Seek(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data) +{ + return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; +} + +static FLAC__StreamDecoderTellStatus Packet_Tell(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data) +{ + return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED; +} + +static FLAC__StreamDecoderLengthStatus Packet_Length(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data) +{ + return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; +} + +static FLAC__bool Packet_EOF(const FLAC__StreamDecoder *decoder, void *client_data) +{ + return 0; +} + +static void OnError(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + //client_data=client_data; // dummy line so i can set a breakpoint +} + +static void OnMetadata(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) +{ + packet_client_data_t packet = (packet_client_data_t)client_data; + switch(metadata->type) + { + case FLAC__METADATA_TYPE_STREAMINFO: + { + packet->frame_size = metadata->data.stream_info.max_blocksize; + packet->bps=metadata->data.stream_info.bits_per_sample; + packet->bytes_per_sample = (packet->bps + 7) / 8; + packet->channels=metadata->data.stream_info.channels; + packet->sample_rate=metadata->data.stream_info.sample_rate; + packet->samples=metadata->data.stream_info.total_samples; + } + break; + } +} + +static FLAC__StreamDecoderWriteStatus OnAudio(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data) +{ + packet_client_data_t packet = (packet_client_data_t)client_data; + + size_t byteLength = packet->bytes_per_sample * packet->channels * frame->header.blocksize; + if (byteLength > packet->outputBufferBytes[0]) { + FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + InterleaveAndTruncate(buffer, packet->outputBuffer, packet->bytes_per_sample * 8, packet->channels, frame->header.blocksize); + packet->outputBufferBytes[0] = byteLength; + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + + +MKVFLACDecoder *MKVFLACDecoder::Create(const nsmkv::TrackEntryData *track_entry_data, const nsmkv::AudioData *audio_data, unsigned int preferred_bits, unsigned int max_channels) +{ + FLAC__StreamDecoder *decoder = FLAC__stream_decoder_new(); + if (!decoder) { + return 0; + } + packet_client_data_t packet = new packet_client_data_s; + packet->buffer = 0; + packet->buffer_length = 0; + + if(FLAC__stream_decoder_init_stream( + decoder, + Packet_Read, + Packet_Seek, + Packet_Tell, + Packet_Length, + Packet_EOF, + OnAudio, + OnMetadata, + OnError, + packet + ) != FLAC__STREAM_DECODER_INIT_STATUS_OK) + { + delete packet; + FLAC__stream_decoder_delete(decoder); + return 0; + } + + packet->buffer = (const uint8_t *)track_entry_data->codec_private; + packet->buffer_length = track_entry_data->codec_private_len; + if (!FLAC__stream_decoder_process_until_end_of_metadata(decoder)) { + delete packet; + FLAC__stream_decoder_delete(decoder); + return 0; + } + + MKVFLACDecoder *mkv_decoder = new MKVFLACDecoder(decoder, packet, preferred_bits); + if (!mkv_decoder) { + delete packet; + FLAC__stream_decoder_delete(decoder); + return 0; + } + return mkv_decoder; +} + +MKVFLACDecoder::MKVFLACDecoder(FLAC__StreamDecoder *decoder, packet_client_data_t packet, unsigned int bps) +: decoder(decoder), packet(packet), bps(bps) +{ +} + +MKVFLACDecoder::~MKVFLACDecoder() +{ + delete packet; + FLAC__stream_decoder_delete(decoder); +} + +int MKVFLACDecoder::OutputFrameSize(size_t *frame_size) +{ + *frame_size = packet->frame_size * packet->bytes_per_sample * packet->channels; + return MKV_SUCCESS; +} + +int MKVFLACDecoder::GetOutputProperties(unsigned int *sampleRate, unsigned int *channels, unsigned int *bitsPerSample, bool *isFloat) +{ + *sampleRate = packet->sample_rate; + *channels = packet->channels; + *bitsPerSample = packet->bps; + *isFloat = false; + + return MKV_SUCCESS; +} + +int MKVFLACDecoder::DecodeBlock(void *inputBuffer, size_t inputBufferBytes, void *outputBuffer, size_t *outputBufferBytes) +{ + packet->buffer = (const uint8_t *)inputBuffer; + packet->buffer_length = inputBufferBytes; + + packet->outputBuffer = outputBuffer; + packet->outputBufferBytes = outputBufferBytes; + + if (FLAC__stream_decoder_process_single(decoder) == 0) { + return MKV_FAILURE; + } + + return MKV_SUCCESS; +} + +void MKVFLACDecoder::Flush() +{ + FLAC__stream_decoder_flush(decoder); +} + +void MKVFLACDecoder::Close() +{ + delete this; +} + +#define CBCLASS MKVFLACDecoder +START_DISPATCH; +CB(OUTPUT_FRAME_SIZE, OutputFrameSize) +CB(GET_OUTPUT_PROPERTIES, GetOutputProperties) +CB(DECODE_BLOCK, DecodeBlock) +VCB(FLUSH, Flush) +VCB(CLOSE, Close) +END_DISPATCH; +#undef CBCLASS + + +int MKVDecoder::CreateAudioDecoder(const char *codec_id, const nsmkv::TrackEntryData *track_entry_data, const nsmkv::AudioData *audio_data, unsigned int preferred_bits, unsigned int max_channels,bool floating_point, ifc_mkvaudiodecoder **decoder) +{ + if (!strcmp(codec_id, "A_FLAC")) + { + MKVFLACDecoder *flac_decoder = MKVFLACDecoder::Create(track_entry_data, audio_data, preferred_bits, max_channels); + if (flac_decoder) + { + *decoder = flac_decoder; + return CREATEDECODER_SUCCESS; + } + return CREATEDECODER_FAILURE; + } + + return CREATEDECODER_NOT_MINE; +} + +#define CBCLASS MKVDecoder +START_DISPATCH; +CB(CREATE_AUDIO_DECODER, CreateAudioDecoder) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/mkv_flac_decoder.h b/Src/Plugins/Input/in_flac/mkv_flac_decoder.h new file mode 100644 index 00000000..b4392d11 --- /dev/null +++ b/Src/Plugins/Input/in_flac/mkv_flac_decoder.h @@ -0,0 +1,65 @@ +#pragma once +#include "../in_mkv/svc_mkvdecoder.h" +#include "../in_mkv/ifc_mkvaudiodecoder.h" +#include <FLAC/all.h> + +// {F6AF0AD9-608F-4206-892F-765412574A7D} +static const GUID FLACMKVGUID = +{ 0xf6af0ad9, 0x608f, 0x4206, { 0x89, 0x2f, 0x76, 0x54, 0x12, 0x57, 0x4a, 0x7d } }; + + + +class packet_client_data_s +{ +public: + const uint8_t *buffer; + size_t buffer_length; + + void *outputBuffer; + size_t *outputBufferBytes; + + uint32_t frame_size; + uint32_t bps; + uint32_t bytes_per_sample; + uint32_t channels; + uint32_t sample_rate; + uint64_t samples; +}; + +typedef packet_client_data_s *packet_client_data_t; + +class MKVDecoder : public svc_mkvdecoder +{ +public: + static const char *getServiceName() { return "FLAC MKV Decoder"; } + static GUID getServiceGuid() { return FLACMKVGUID; } + int CreateAudioDecoder(const char *codec_id, const nsmkv::TrackEntryData *track_entry_data, const nsmkv::AudioData *audio_data, unsigned int preferred_bits, unsigned int preferred_channels, bool floating_point, ifc_mkvaudiodecoder **decoder); +protected: + RECVS_DISPATCH; +}; + +class MKVFLACDecoder : public ifc_mkvaudiodecoder +{ +public: + static MKVFLACDecoder *Create(const nsmkv::TrackEntryData *track_entry_data, const nsmkv::AudioData *audio_data, unsigned int preferred_bits, unsigned int max_channels); + +protected: + RECVS_DISPATCH; +private: + /* ifc_mkvaudiodecoder implementation */ + int OutputFrameSize(size_t *frame_size); + int GetOutputProperties(unsigned int *sampleRate, unsigned int *channels, unsigned int *bitsPerSample, bool *isFloat); + int DecodeBlock(void *inputBuffer, size_t inputBufferBytes, void *outputBuffer, size_t *outputBufferBytes); + void Flush(); + void Close(); + +private: + MKVFLACDecoder(FLAC__StreamDecoder *decoder, packet_client_data_t packet, unsigned int bps); + ~MKVFLACDecoder(); + /* internal implementation */ + + /* data */ + FLAC__StreamDecoder *decoder; + unsigned int bps; +packet_client_data_t packet; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flac/resource.h b/Src/Plugins/Input/in_flac/resource.h new file mode 100644 index 00000000..341dd0ab --- /dev/null +++ b/Src/Plugins/Input/in_flac/resource.h @@ -0,0 +1,71 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by in_flac.rc +// +#define IDS_NULLSOFT_FLAC_DECODER_OLD 0 +#define IDS_TO_ADVANCED_MODE 1 +#define IDS_TO_SIMPLE_MODE 2 +#define IDS_NAME 3 +#define IDS_VALUE 4 +#define IDS_TRACK_GAIN 5 +#define IDS_ALBUM_GAIN 6 +#define IDS_NOT_PRESENT 7 +#define IDS_LENGTH_IN_SECONDS 8 +#define IDS_CHANNELS 9 +#define IDS_BITS_PER_SAMPLE 10 +#define IDS_SAMPLE_RATE 11 +#define IDS_FILE_SIZE_IN_BYTES 12 +#define IDS_AVERAGE_BITRATE 13 +#define IDS_COMPRESSION_RATIO 14 +#define IDS_ABOUT_STR 15 +#define IDS_LIBFLAC_DLL_MISSING 16 +#define IDS_FLAC_FILES 17 +#define IDS_FAMILY_STRING 18 +#define IDS_ABOUT_TEXT 19 +#define IDS_CANNOT_SAVE_METADATA 20 +#define IDS_ERROR_SAVING_METADATA 21 +#define IDD_PREFERENCES 102 +#define IDD_INFOCHILD_ADVANCED 105 +#define IDC_FILENAME 1001 +#define IDC_TITLE 1002 +#define IDC_ARTIST 1003 +#define IDC_YEAR 1004 +#define IDC_ALBUM 1005 +#define IDC_GENRE 1006 +#define IDC_COMMENTS 1007 +#define IDC_FILEINFO 1008 +#define IDC_ALBUMARTIST 1009 +#define IDC_UPDATE 1010 +#define IDC_COMPOSER 1011 +#define IDC_EDIT1 1012 +#define IDC_TRACK 1012 +#define IDC_EXTENSIONS 1012 +#define IDC_NAME 1012 +#define IDC_REPLAYGAIN 1013 +#define IDC_DISC 1014 +#define IDC_PUBLISHER 1015 +#define IDC_PUBLISHER2 1017 +#define IDC_TOOL 1017 +#define IDC_CHECK1 1017 +#define IDC_AVERAGE_BITRATE 1017 +#define IDC_COMBO1 1018 +#define IDC_SWITCH 1019 +#define IDC_PLACEHOLDER 1020 +#define IDC_LIST 1022 +#define IDC_VALUE 1024 +#define IDC_BUTTON_ADD 1025 +#define IDC_BUTTON_DEL 1026 +#define IDC_BUTTON5 1028 +#define IDC_BUTTON_DELALL 1028 +#define IDS_NULLSOFT_FLAC_DECODER 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 112 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1029 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_flac/version.rc2 b/Src/Plugins/Input/in_flac/version.rc2 new file mode 100644 index 00000000..da1d44c9 --- /dev/null +++ b/Src/Plugins/Input/in_flac/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 3,2,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", "3,2,0,0" + VALUE "InternalName", "Nullsoft FLAC Decoder" + VALUE "LegalCopyright", "Copyright © 2007-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_flac.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_flv/AMFDispatch.cpp b/Src/Plugins/Input/in_flv/AMFDispatch.cpp new file mode 100644 index 00000000..0bd8af76 --- /dev/null +++ b/Src/Plugins/Input/in_flv/AMFDispatch.cpp @@ -0,0 +1,238 @@ +#include "AMFDispatch.h" + +AMFDispatch::AMFDispatch(AMFMixedArray *array) +{ + object=array; + if (object) + object->AddRef(); + refCount=1; +} + +AMFDispatch::~AMFDispatch() +{ + if (object) + object->Release(); +} + +STDMETHODIMP AMFDispatch::QueryInterface(REFIID riid, PVOID *ppvObject) +{ + if (!ppvObject) + return E_POINTER; + + else if (IsEqualIID(riid, IID_IDispatch)) + *ppvObject = (IDispatch *)this; + else if (IsEqualIID(riid, IID_IUnknown)) + *ppvObject = this; + else + { + *ppvObject = NULL; + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; +} + +ULONG AMFDispatch::AddRef(void) +{ + return InterlockedIncrement((volatile LONG *)&refCount); +} + +ULONG AMFDispatch::Release(void) +{ + ULONG count = InterlockedDecrement((volatile LONG *)&refCount); + if (count == 0) + delete this; + return count; +} + +enum +{ + DISP_AMF_DEBUGPRINT, + DISP_AMF_MAX, +}; + +#define CHECK_ID(str, id) if (wcscmp(rgszNames[i], L##str) == 0) { rgdispid[i] = id; continue; } +HRESULT AMFDispatch::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid) +{ + bool unknowns = false; + for (unsigned int i = 0;i != cNames;i++) + { + CHECK_ID("DebugPrint", DISP_AMF_DEBUGPRINT) + if (object) + { + //size_t index = object->array.getPosition(rgszNames[i]); + size_t index = 0; + for (auto it = object->array.begin(); it != object->array.end(); it++, index++) + { + if (wcscmp(it->first.c_str(), rgszNames[i]) == 0) + { + break; + } + } + + if (index != object->array.size()) + { + rgdispid[i] = (DISPID)index + DISP_AMF_MAX; + continue; + } + } + + rgdispid[i] = DISPID_UNKNOWN; + unknowns = true; + + } + if (unknowns) + return DISP_E_UNKNOWNNAME; + else + return S_OK; +} + +HRESULT AMFDispatch::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo) +{ + return E_NOTIMPL; +} + +HRESULT AMFDispatch::GetTypeInfoCount(unsigned int FAR * pctinfo) +{ + return E_NOTIMPL; +} + +static void AMFType_To_Variant(AMFType *obj, VARIANT *pvarResult) +{ + VariantInit(pvarResult); + switch(obj->type) + { + case AMFType::TYPE_DOUBLE: // double + { + AMFDouble *cast_obj = static_cast<AMFDouble *>(obj); + V_VT(pvarResult) = VT_R8; + V_R8(pvarResult) = cast_obj->val; + } + break; + case AMFType::TYPE_BOOL: // bool + { + AMFBoolean *cast_obj = static_cast<AMFBoolean *>(obj); + V_VT(pvarResult) = VT_BOOL; + V_BOOL(pvarResult) = cast_obj->boolean; + } + break; + case AMFType::TYPE_MOVIE: // movie (basically just a URL) + case AMFType::TYPE_STRING: // string + { + AMFString *cast_obj = static_cast<AMFString *>(obj); + V_VT(pvarResult) = VT_BSTR; + V_BSTR(pvarResult) = SysAllocString(cast_obj->str); + } + break; + case AMFType::TYPE_LONG_STRING: // string + { + AMFLongString *cast_obj = static_cast<AMFLongString *>(obj); + V_VT(pvarResult) = VT_BSTR; + V_BSTR(pvarResult) = SysAllocString(cast_obj->str); + } + break; + case AMFType::TYPE_MIXEDARRAY: + { + AMFMixedArray *cast_obj = static_cast<AMFMixedArray *>(obj); + V_VT(pvarResult) = VT_DISPATCH; + V_DISPATCH(pvarResult) = new AMFDispatch(cast_obj); + } + break; + case AMFType::TYPE_DATE: + { + AMFTime *cast_obj = static_cast<AMFTime *>(obj); + V_VT(pvarResult) = VT_DATE; + V_DATE(pvarResult) = cast_obj->val; + } + break; + case AMFType::TYPE_ARRAY: + { + AMFArray *cast_obj = static_cast<AMFArray *>(obj); + SAFEARRAYBOUND rgsabound[1]; + rgsabound[0].lLbound = 0; + rgsabound[0].cElements = (ULONG)cast_obj->array.size(); + SAFEARRAY *psa = SafeArrayCreate(VT_VARIANT, 1, rgsabound); + VARIANT **data; + SafeArrayAccessData(psa, (void **)&data); + for (size_t i=0;i!=cast_obj->array.size();i++) + { + AMFType_To_Variant(cast_obj->array[i], data[i]); + } + SafeArrayUnaccessData(psa); + V_VT(pvarResult) = VT_ARRAY; + V_ARRAY(pvarResult) = psa; + } + break; + } +} + +HRESULT AMFDispatch::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr) +{ + if (pvarResult) + VariantInit(pvarResult); + + switch(dispid) + { + case DISP_AMF_DEBUGPRINT: + { + wchar_t debugstring[4096]=L""; + wchar_t *str = debugstring; + size_t len = 4096; + object->DebugPrint(1, str, len); + V_VT(pvarResult) = VT_BSTR; + V_BSTR(pvarResult) = SysAllocString(debugstring); + } + return S_OK; + } + size_t index = dispid - DISP_AMF_MAX; + if (index >= object->array.size()) + return DISP_E_MEMBERNOTFOUND; + + //AMFType *obj = object->array.at(index).second; + AMFType* obj = 0; + auto it = object->array.begin(); + while (index--) + { + it++; + } + if (it != object->array.end()) + { + obj = it->second; + } + + if (!obj) + return S_OK; + + switch(obj->type) + { + case AMFType::TYPE_DOUBLE: + case AMFType::TYPE_BOOL: + case AMFType::TYPE_STRING: + case AMFType::TYPE_MIXEDARRAY: + case AMFType::TYPE_ARRAY: + AMFType_To_Variant(obj, pvarResult); + return S_OK; + + case AMFType::TYPE_OBJECT: // object + // TODO + return DISP_E_TYPEMISMATCH; + case AMFType::TYPE_NULL: // null + return S_OK; + case AMFType::TYPE_REFERENCE: // reference + return DISP_E_TYPEMISMATCH; + case AMFType::TYPE_TERMINATOR: + // TODO? + return DISP_E_TYPEMISMATCH; + case AMFType::TYPE_DATE: // date + return DISP_E_TYPEMISMATCH; + case AMFType::TYPE_LONG_STRING: // long string + return DISP_E_TYPEMISMATCH; + case AMFType::TYPE_XML: // XML + return DISP_E_TYPEMISMATCH; + default: + return DISP_E_TYPEMISMATCH; + + } + return S_OK; +} diff --git a/Src/Plugins/Input/in_flv/AMFDispatch.h b/Src/Plugins/Input/in_flv/AMFDispatch.h new file mode 100644 index 00000000..3a593875 --- /dev/null +++ b/Src/Plugins/Input/in_flv/AMFDispatch.h @@ -0,0 +1,28 @@ +#pragma once +/* +Ben Allison +April 30, 2008 + +This class implements an IDispatch interface around an AMFObject +*/ +#include <oaidl.h> +#include "AMFObject.h" +class AMFDispatch : public IDispatch +{ +public: + AMFDispatch(AMFMixedArray *array); + ~AMFDispatch(); + STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject); + STDMETHOD_(ULONG, AddRef)(void); + STDMETHOD_(ULONG, Release)(void); + +private: + // *** IDispatch Methods *** + STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid); + STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo); + STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo); + STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr); + + volatile ULONG refCount; + AMFMixedArray *object; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/AMFObject.cpp b/Src/Plugins/Input/in_flv/AMFObject.cpp new file mode 100644 index 00000000..a0c5be38 --- /dev/null +++ b/Src/Plugins/Input/in_flv/AMFObject.cpp @@ -0,0 +1,319 @@ +#include "AMFObject.h" +#include <strsafe.h> +void AMFMixedArray::DebugPrint(int spaces, wchar_t *&str, size_t &len) +{ + AMFTypeList::iterator itr; + StringCchCopyEx(str, len, L"Mixed Array [\n", &str, &len, 0); + for (itr=array.begin();itr!=array.end();itr++) + { + for (int i=0;i<spaces;i++) + StringCchCopyEx(str, len, L" ", &str, &len,0); + + if (itr->first.c_str() != 0 && itr->first.c_str()[0]) + StringCchPrintfEx(str, len, &str, &len, 0, L"%s: ", itr->first.c_str()); + if (itr->second) + itr->second->DebugPrint(spaces+1, str, len); + else + StringCchCopyEx(str, len, L"(null)\n", &str, &len, 0); + } + StringCchCopyEx(str, len, L"]\n", &str, &len, 0); + +} + +size_t AMFMixedArray::Read(uint8_t *data, size_t size) +{ + size_t read = 0; + uint32_t maxIndex = FLV::Read32(data); +// TODO? array.reserve(maxIndex); + data += 4; + size -= 4; + read += 4; + + while (size) + { + AMFString amfString; + size_t skip = amfString.Read(data, size); + data += skip; + size -= skip; + read += skip; + + uint8_t type = *data; + data++; + size--; + read++; + AMFType *obj = MakeObject(type); + if (obj) + { + obj->type = type; + size_t skip = obj->Read(data, size); + data += skip; + size -= skip; + read += skip; + + array[amfString.str] = obj; + } + else + break; + + if (type == TYPE_TERMINATOR) + break; + + } + + return read; +} + +AMFMixedArray::~AMFMixedArray() +{ + for (AMFTypeList::iterator itr=array.begin();itr!=array.end();itr++) + { + delete itr->second; + } +} + + +void AMFObj::DebugPrint(int spaces, wchar_t *&str, size_t &len) +{ + StringCchCopyEx(str, len, L"Object (TODO)\n", &str, &len, 0); +} + +size_t AMFObj::Read(uint8_t *data, size_t size) +{ + size_t read = 0; + while (size) + { + AMFString amfString; + size_t skip = amfString.Read(data, size); + data += skip; + size -= skip; + read += skip; + + uint8_t type = *data; + data++; + size--; + read++; + AMFType *obj = MakeObject(type); + if (obj) + { + obj->type = type; + size_t skip = obj->Read(data, size); + data += skip; + size -= skip; + read += skip; + } + else + return false; + + if (type == TYPE_TERMINATOR) + break; + } + return read; +} + + +void AMFArray::DebugPrint(int spaces, wchar_t *&str, size_t &len) +{ + + StringCchCopyEx(str, len, L"Array [\n", &str, &len, 0); + for (size_t i=0;i!=array.size();i++) + { + for (int s=0;s<spaces;s++) + StringCchCopyEx(str, len, L" ", &str, &len,0); + StringCchPrintfEx(str, len, &str, &len, 0, L"%u: ", i); + array[i]->DebugPrint(spaces+1, str, len); + } + StringCchCopyEx(str, len, L"]\n", &str, &len, 0); +} + +size_t AMFArray::Read(uint8_t *data, size_t size) +{ + size_t read = 0; + uint32_t arrayLength = FLV::Read32(data); + array.reserve(arrayLength); + data += 4; + read += 4; + size -= 4; + + for (uint32_t i=0;i!=arrayLength;i++) + { + uint8_t type = *data; + data++; + read++; + size--; + + AMFType *obj = MakeObject(type); + size_t skip = obj->Read(data, size); + //array[i]=obj; + array.push_back(obj); + data += skip; + read += skip; + size -= skip; + } + + return read; +} + +AMFArray::~AMFArray() +{ + for (size_t i=0;i!=array.size();i++) + { + delete array[i]; + } +} + +/* --- String --- */ +AMFString::AMFString() : str(0) +{} +AMFString::~AMFString() +{ + free(str); +} + +void AMFString::DebugPrint(int spaces, wchar_t *&str, size_t &len) +{ + StringCchPrintfEx(str, len, &str, &len, 0, L"%s\n", this->str); +} + +size_t AMFString::Read(uint8_t *data, size_t size) +{ + if (size < 2) + return 0; + + unsigned __int16 strlength = FLV::Read16(data); + data += 2; + size -= 2; + + if (strlength > size) + return 0; + + char *utf8string = (char *)calloc(strlength, sizeof(char)); + memcpy(utf8string, data, strlength); + + int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8string, strlength, 0, 0); + str = (wchar_t *)calloc(wideLen + 2, sizeof(wchar_t)); + + MultiByteToWideChar(CP_UTF8, 0, utf8string, strlength, str, wideLen); + str[wideLen] = 0; + free(utf8string); + + return strlength + 2; +} + +/* --- Long String --- */ +AMFLongString::AMFLongString() : str(0) +{} +AMFLongString::~AMFLongString() +{ + free(str); +} + +void AMFLongString::DebugPrint(int spaces, wchar_t *&str, size_t &len) +{ + StringCchPrintfEx(str, len, &str, &len, 0, L"%s\n", this->str); +} + +size_t AMFLongString::Read(uint8_t *data, size_t size) +{ + if (size < 4) + return 0; + + uint32_t strlength = FLV::Read32(data); + data += 4; + size -= 4; + + if (strlength > size) + return 0; + + char *utf8string = (char *)calloc(strlength, sizeof(char)); + memcpy(utf8string, data, strlength); + + int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8string, strlength, 0, 0); + str = (wchar_t *)calloc(wideLen + 2, sizeof(wchar_t)); + + MultiByteToWideChar(CP_UTF8, 0, utf8string, strlength, str, wideLen); + str[wideLen] = 0; + free(utf8string); + + return strlength + 4; +} + +/* --- Double --- */ +void AMFDouble::DebugPrint(int spaces, wchar_t *&str, size_t &len) +{ + StringCchPrintfEx(str, len, &str, &len, 0, L"%f\n", val); +} + +/* --- Boolean --- */ +void AMFBoolean::DebugPrint(int spaces, wchar_t *&str, size_t &len) +{ + StringCchPrintfEx(str, len, &str, &len, 0, L"%s\n", boolean?L"true":L"false"); +} + +/* --- Time --- */ +static size_t MakeDateString(__time64_t convertTime, wchar_t *dest, size_t destlen) +{ + SYSTEMTIME sysTime; + tm *newtime = _localtime64(&convertTime); + dest[0] = 0; // so we can bail out easily + if (newtime) + { + sysTime.wYear = (WORD)(newtime->tm_year + 1900); + sysTime.wMonth = (WORD)(newtime->tm_mon + 1); + sysTime.wDayOfWeek = (WORD)newtime->tm_wday; + sysTime.wDay = (WORD)newtime->tm_mday; + sysTime.wHour = (WORD)newtime->tm_hour; + sysTime.wMinute = (WORD)newtime->tm_min; + sysTime.wSecond = (WORD)newtime->tm_sec; + sysTime.wMilliseconds = 0; + + int charsWritten = GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &sysTime, NULL, dest, (int)destlen); + if (charsWritten) + { + size_t dateSize = charsWritten-1; + dest += dateSize; + destlen -= dateSize; + if (destlen) + { + *dest++ = L' '; + destlen--; + dateSize++; + } + + int charsWritten2 = GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &sysTime, NULL, dest, (int)destlen); + if (charsWritten2) + { + dateSize+=(charsWritten2-1); + } + return dateSize; + } + } + + return 1; + +} + +void AMFTime::DebugPrint(int spaces, wchar_t *&str, size_t &len) +{ + size_t written = MakeDateString((__time64_t)val, str, len); + str+=written; + len-=written; + if (len>=2) + { + str[0]='\n'; + str[1]=0; + len--; + str++; + } +} + +/* --- Terminator --- */ +void AMFTerminator::DebugPrint(int spaces, wchar_t *&str, size_t &len) +{ + StringCchCopyEx(str, len, L"array terminator\n", &str, &len, 0); +} + +/* --- Reference --- */ +void AMFReference::DebugPrint(int spaces, wchar_t *&str, size_t &len) +{ + StringCchPrintfEx(str, len, &str, &len, 0, L"%u\n", val); +} diff --git a/Src/Plugins/Input/in_flv/AMFObject.h b/Src/Plugins/Input/in_flv/AMFObject.h new file mode 100644 index 00000000..bb423a9c --- /dev/null +++ b/Src/Plugins/Input/in_flv/AMFObject.h @@ -0,0 +1,182 @@ +#ifndef NULLSOFT_AMFOBJECT_H +#define NULLSOFT_AMFOBJECT_H + +#include "FLVUtil.h" +#include <windows.h> +#include <map> +#include <string> +#include <vector> +#include <time.h> + + +class AMFType // no, not bowling, ActionScript Message Format +{ +public: + AMFType() : refCount(1) {} + virtual ~AMFType() {} + virtual size_t Read(uint8_t *data, size_t size)=0; // returns number of bytes read, 0 on failure + uint8_t type; + virtual void DebugPrint(int spaces, wchar_t *&str, size_t &len)=0; + enum + { + TYPE_DOUBLE = 0x0, + TYPE_BOOL = 0x1, + TYPE_STRING = 0x2, + TYPE_OBJECT = 0x3, + TYPE_MOVIE = 0x4, + TYPE_NULL = 0x5, + TYPE_REFERENCE = 0x7, + TYPE_MIXEDARRAY = 0x8, + TYPE_TERMINATOR = 0x9, + TYPE_ARRAY = 0xA, + TYPE_DATE = 0xB, + TYPE_LONG_STRING = 0xC, + TYPE_XML = 0xF, + }; + + ULONG AddRef(void) + { + return InterlockedIncrement((volatile LONG *)&refCount); + } + + ULONG Release(void) + { + ULONG count = InterlockedDecrement((volatile LONG *)&refCount); + if (count == 0) + delete this; + return count; + } +private: + ULONG refCount; +}; + +class AMFString : public AMFType +{ +public: + AMFString(); + ~AMFString(); + void DebugPrint(int spaces, wchar_t *&str, size_t &len); + size_t Read(uint8_t *data, size_t size); + wchar_t *str; +}; + +class AMFLongString : public AMFType +{ +public: + AMFLongString(); + ~AMFLongString(); + void DebugPrint(int spaces, wchar_t *&str, size_t &len); + size_t Read(uint8_t *data, size_t size); + wchar_t *str; +}; + +class AMFObj : public AMFType +{ +public: + size_t Read(uint8_t *data, size_t size); + void DebugPrint(int spaces, wchar_t *&str, size_t &len); +}; + +class AMFArray : public AMFType +{ +public: + size_t Read(uint8_t *data, size_t size); + void DebugPrint(int spaces, wchar_t *&str, size_t &len); + ~AMFArray(); + std::vector<AMFType*> array; +}; + +class AMFMixedArray : public AMFType +{ +public: + size_t Read(uint8_t *data, size_t size); + void DebugPrint(int spaces, wchar_t *&str, size_t &len); + ~AMFMixedArray(); + typedef std::map<std::wstring, AMFType *> AMFTypeList; + AMFTypeList array; +}; + +class AMFDouble : public AMFType +{ +public: +void DebugPrint(int spaces, wchar_t *&str, size_t &len); + size_t Read(uint8_t *data, size_t size) + { + if (size < 8) + return 0; + + val = FLV::ReadDouble(data); + return 8; + } + double val; +}; + +class AMFTime : public AMFType +{ +public: + void DebugPrint(int spaces, wchar_t *&str, size_t &len); + size_t Read(uint8_t *data, size_t size) + { + if (size < 10) + return 0; + + val = FLV::ReadDouble(data); + data+=8; + + offset = FLV::Read16(data); + + return 10; + } + double val; // same epoch as time_t, just stored as a double instead of an unsigned int + int16_t offset; // offset in minutes from UTC. presumably from the encoding machine's timezone +}; + +class AMFBoolean : public AMFType +{ +public: + void DebugPrint(int spaces, wchar_t *&str, size_t &len); + size_t Read(uint8_t *data, size_t size) + { + if (size < 1) + return 0; + boolean = !!data[0]; + return 1; + } + + bool boolean; +}; + +class AMFTerminator : public AMFType +{ +public: + void DebugPrint(int spaces, wchar_t *&str, size_t &len); + size_t Read(uint8_t *data, size_t size) + { + return 0; + } +}; + +class AMFReference : public AMFType +{ +public: + void DebugPrint(int spaces, wchar_t *&str, size_t &len); + size_t Read(uint8_t *data, size_t size) + { + if (size < 2) + return 0; + val = FLV::Read16(data); + return 2; + } + uint16_t val; +}; + +inline double AMFGetDouble(AMFType *obj) +{ + if (obj->type == 0x0) + return ((AMFDouble *)obj)->val; + + return 0; +} + +AMFType *MakeObject(uint8_t type); +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/BackgroundDownloader.cpp b/Src/Plugins/Input/in_flv/BackgroundDownloader.cpp new file mode 100644 index 00000000..bcb3b716 --- /dev/null +++ b/Src/Plugins/Input/in_flv/BackgroundDownloader.cpp @@ -0,0 +1,129 @@ +#include "Main.h" +#include "BackgroundDownloader.h" +#include "..\..\..\Components\wac_network\wac_network_http_receiver_api.h" +#include "api__in_flv.h" +#include "api/service/waservicefactory.h" +#include "../nu/AutoChar.h" + +#include <strsafe.h> + +#define HTTP_BUFFER_SIZE 65536 + +// {C0A565DC-0CFE-405a-A27C-468B0C8A3A5C} +static const GUID internetConfigGroupGUID = +{ + 0xc0a565dc, 0xcfe, 0x405a, { 0xa2, 0x7c, 0x46, 0x8b, 0xc, 0x8a, 0x3a, 0x5c } +}; + + +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 ); +} + +static int FeedHTTP( api_httpreceiver *http, Downloader::DownloadCallback *callback, int *downloaded ) +{ + char downloadedData[ HTTP_BUFFER_SIZE ] = { 0 }; + int result = 0; + int downloadSize = http->get_bytes( downloadedData, HTTP_BUFFER_SIZE ); + + *downloaded = downloadSize; + if ( downloadSize ) + { + result = callback->OnData( downloadedData, downloadSize ); + } + + return result; +} + +static void RunDownload( api_httpreceiver *http, Downloader::DownloadCallback *callback ) +{ + int ret; + int downloaded = 0; + do + { + ret = http->run(); + Sleep( 55 ); + do + { + if ( FeedHTTP( http, callback, &downloaded ) != 0 ) + return; + } while ( downloaded == HTTP_BUFFER_SIZE ); + } while ( ret == HTTPRECEIVER_RUN_OK ); + + // finish off the data + do + { + if ( FeedHTTP( http, callback, &downloaded ) != 0 ) + return; + } while ( downloaded ); +} + + +bool Downloader::Download(const char *url, Downloader::DownloadCallback *callback, uint64_t startPosition) +{ + api_httpreceiver *http = 0; + waServiceFactory *sf = plugin.service->service_getServiceByGuid(httpreceiverGUID); + if (sf) http = (api_httpreceiver *)sf->getInterface(); + + if (!http) + return false; + + int use_proxy = 1; + 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; + + const wchar_t *proxy = use_proxy?AGAVE_API_CONFIG->GetString(internetConfigGroupGUID, L"proxy", 0):0; + + http->AllowCompression(); + http->open(API_DNS_AUTODNS, HTTP_BUFFER_SIZE, (proxy && proxy[0]) ? (const char *)AutoChar(proxy) : NULL); + if (startPosition > 0) + { + char temp[128] = {0}; + StringCchPrintfA(temp, 128, "Range: bytes=%d-", startPosition); + http->addheader(temp); + } + SetUserAgent(http); + http->connect(url); + int ret; + + do + { + Sleep(55); + ret = http->run(); + if (ret == -1) // connection failed + break; + + // ---- check our reply code ---- + int replycode = http->getreplycode(); + switch (replycode) + { + case 0: + case 100: + break; + case 200: + { + if (callback->OnConnect(http) != 0) + { + sf->releaseInterface(http); + return false; + } + + RunDownload(http, callback); + sf->releaseInterface(http); + return true; + } + break; + default: + sf->releaseInterface(http); + return false; + } + } + while (ret == HTTPRECEIVER_RUN_OK); + //const char *er = http->geterrorstr(); + sf->releaseInterface(http); + return false; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/BackgroundDownloader.h b/Src/Plugins/Input/in_flv/BackgroundDownloader.h new file mode 100644 index 00000000..01b08e9a --- /dev/null +++ b/Src/Plugins/Input/in_flv/BackgroundDownloader.h @@ -0,0 +1,19 @@ +#pragma once +#include <bfc/platform/types.h> + +class api_httpreceiver; + +class Downloader +{ +public: + class DownloadCallback + { + public: + virtual int OnConnect(api_httpreceiver *http)=0; + virtual int OnData(void *buffer, size_t bufferSize)=0; + }; + + + bool Download(const char *url, DownloadCallback *callback, uint64_t startPosition = 0); + +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/ExtendedInfo.cpp b/Src/Plugins/Input/in_flv/ExtendedInfo.cpp new file mode 100644 index 00000000..c7a807f0 --- /dev/null +++ b/Src/Plugins/Input/in_flv/ExtendedInfo.cpp @@ -0,0 +1,29 @@ +#include <bfc/platform/types.h> +#include <windows.h> +#include <strsafe.h> +#include "api__in_flv.h" +#include "resource.h" + +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"FLV") && S_OK == StringCchCopyW(dest, destlen, WASABI_API_LNGSTRINGW(IDS_FAMILY_STRING))) return 1; + return 0; + } + + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVAudioHeader.cpp b/Src/Plugins/Input/in_flv/FLVAudioHeader.cpp new file mode 100644 index 00000000..5042bf42 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVAudioHeader.cpp @@ -0,0 +1,34 @@ +#include "FLVAudioHeader.h" + +/* +(c) 2006 Nullsoft, Inc. +Author: Ben Allison benski@nullsoft.com +*/ + +/* +soundType 1 bit (byte & 0x01) >> 0 0: mono, 1: stereo +soundSize 1 bit (byte & 0x02)>> 1 0: 8-bit, 2: 16-bit +soundRate 2 bits (byte & 0x0C) >> 2 0: 5.5 kHz, 1: 11 kHz, 2: 22 kHz, 3: 44 kHz +soundFormat 4 bits (byte & 0xf0) >> 4 0: Uncompressed, 1: ADPCM, 2: MP3, 5: Nellymoser 8kHz mono, 6: Nellymoser +*/ + +bool FLVAudioHeader::Read(unsigned __int8 *data, size_t size) +{ + if (size < 1) + return false; // header size too small + + + unsigned __int8 byte = data[0]; + + stereo = !!((byte & 0x01) >> 0); + bits = ((byte & 0x02) >> 1) ? 16 : 8; + switch ((byte & 0x0C) >> 2) + { + case 0: sampleRate = 5512; break; + case 1: sampleRate = 11025; break; + case 2: sampleRate = 22050; break; + case 3: sampleRate = 44100; break; + } + format = (byte & 0xf0) >> 4; + return true; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVAudioHeader.h b/Src/Plugins/Input/in_flv/FLVAudioHeader.h new file mode 100644 index 00000000..8a749a4a --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVAudioHeader.h @@ -0,0 +1,31 @@ +#ifndef NULLSOFT_FLVAUDIOHEADER_H +#define NULLSOFT_FLVAUDIOHEADER_H + +namespace FLV +{ + const int AUDIO_FORMAT_PCM = 0; + const int AUDIO_FORMAT_ADPCM = 1; + const int AUDIO_FORMAT_MP3 = 2; + const int AUDIO_FORMAT_PCM_LE = 3; // little endian + const int AUDIO_FORMAT_NELLYMOSER_16KHZ = 4; + const int AUDIO_FORMAT_NELLYMOSER_8KHZ = 5; + const int AUDIO_FORMAT_NELLYMOSER = 6; + const int AUDIO_FORMAT_A_LAW = 7; + const int AUDIO_FORMAT_MU_LAW = 8; + const int AUDIO_FORMAT_AAC = 10; + const int AUDIO_FORMAT_MP3_8KHZ = 14; + +}; + +class FLVAudioHeader +{ +public: + bool Read(unsigned __int8 *data, size_t size); // size must be >=1, returns "true" if this was a valid header + + // attributes, consider these read-only + bool stereo; + int bits; + int sampleRate; + int format; +}; +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVCOM.cpp b/Src/Plugins/Input/in_flv/FLVCOM.cpp new file mode 100644 index 00000000..5d6b4cc5 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVCOM.cpp @@ -0,0 +1,189 @@ +#include "FLVCOM.h" +#include "AMFDispatch.h" +FLVCOM flvCOM; +extern bool mute; + +static HANDLE DuplicateCurrentThread() +{ + HANDLE fakeHandle = GetCurrentThread(); + HANDLE copiedHandle = 0; + HANDLE processHandle = GetCurrentProcess(); + DuplicateHandle(processHandle, fakeHandle, processHandle, &copiedHandle, 0, FALSE, DUPLICATE_SAME_ACCESS); + return copiedHandle; +} + +enum +{ + DISP_FLV_REGISTER_CALLBACK, + DISP_FLV_UNREGISTER_CALLBACK, + DISP_FLV_SETMUTE, +}; + +#define CHECK_ID(str, id) if (wcscmp(rgszNames[i], L##str) == 0) { rgdispid[i] = id; continue; } +HRESULT FLVCOM::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid) +{ + bool unknowns = false; + for (unsigned int i = 0;i != cNames;i++) + { + CHECK_ID("RegisterCallback", DISP_FLV_REGISTER_CALLBACK) + CHECK_ID("UnregisterCallback", DISP_FLV_UNREGISTER_CALLBACK) + CHECK_ID("SetMute", DISP_FLV_SETMUTE) + rgdispid[i] = DISPID_UNKNOWN; + unknowns = true; + } + if (unknowns) + return DISP_E_UNKNOWNNAME; + else + return S_OK; +} + +HRESULT FLVCOM::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo) +{ + return E_NOTIMPL; +} + +HRESULT FLVCOM::GetTypeInfoCount(unsigned int FAR * pctinfo) +{ + return E_NOTIMPL; +} + +HRESULT FLVCOM::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr) +{ + if (pvarResult) + VariantInit(pvarResult); + + switch (dispid) + { + case DISP_FLV_SETMUTE: + { + if (pdispparams->rgvarg[0].boolVal) + mute = true; + else + mute = false; + } + return S_OK; + case DISP_FLV_REGISTER_CALLBACK: + { + IDispatch *callback = pdispparams->rgvarg[0].pdispVal; + + callbacks.push_back(DispatchCallbackInfo(callback, GetCurrentThreadId(), DuplicateCurrentThread())); + return S_OK; + } + break; + case DISP_FLV_UNREGISTER_CALLBACK: + { + IDispatch *callback = pdispparams->rgvarg[0].pdispVal; + size_t numCallbacks = callbacks.size(); + while (numCallbacks--) + { + if (callbacks[numCallbacks].dispatch == callback) + { + CloseHandle(callbacks[numCallbacks].threadHandle); + callbacks.erase(callbacks.begin() + numCallbacks); + } + } + return S_OK; + } + break; + } + return DISP_E_MEMBERNOTFOUND; +} + +STDMETHODIMP FLVCOM::QueryInterface(REFIID riid, PVOID *ppvObject) +{ + if (!ppvObject) + return E_POINTER; + + else if (IsEqualIID(riid, IID_IDispatch)) + *ppvObject = (IDispatch *)this; + else if (IsEqualIID(riid, IID_IUnknown)) + *ppvObject = this; + else + { + *ppvObject = NULL; + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; +} + +ULONG FLVCOM::AddRef(void) +{ + return 0; +} + +ULONG FLVCOM::Release(void) +{ + return 0; +} + +static void CallDispatchMethod(IDispatch *dispatch, DISPPARAMS ¶ms, OLECHAR *name) +{ + try + { + unsigned int ret; + DISPID dispid; + if (SUCCEEDED(dispatch->GetIDsOfNames(IID_NULL, &name, 1, LOCALE_SYSTEM_DEFAULT, &dispid))) + dispatch->Invoke(dispid, GUID_NULL, 0, DISPATCH_METHOD, ¶ms, 0, 0, &ret); + } + catch (...) + {} +} + +struct APCdata +{ + IDispatch *disp; + wchar_t *name; + AMFDispatch *amf; +}; + +static VOID CALLBACK MetadataAPC(ULONG_PTR param) +{ + APCdata *data = (APCdata *)param; + + DISPPARAMS params; + VARIANT argument; + params.cArgs = 1; + params.cNamedArgs = 0; + params.rgdispidNamedArgs = 0; + params.rgvarg = &argument; + VariantInit(&argument); + V_VT(&argument) = VT_DISPATCH; + V_DISPATCH(&argument) = data->amf; + + CallDispatchMethod(data->disp, params, data->name); + + data->disp->Release(); + data->amf->Release(); + free(data->name); + delete data; +} + +void FLVCOM::MetadataCallback(FLVMetadata::Tag *tag) +{ + if (!callbacks.empty()) + { + AMFDispatch *disp = new AMFDispatch(tag->parameters); // we're newing this we can refcount + + DWORD curThreadId = GetCurrentThreadId(); + for (size_t i = 0;i != callbacks.size();i++) + { + APCdata *data = new APCdata; + data->disp = callbacks[i].dispatch; + data->disp->AddRef(); + data->name = _wcsdup(tag->name.str); + data->amf = disp; + data->amf->AddRef(); + + if (curThreadId == callbacks[i].threadId) + MetadataAPC((ULONG_PTR)data); + else + { + if (callbacks[i].threadHandle) + QueueUserAPC(MetadataAPC, callbacks[i].threadHandle, (ULONG_PTR)data); + } + } + disp->Release(); + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVCOM.h b/Src/Plugins/Input/in_flv/FLVCOM.h new file mode 100644 index 00000000..e521bf80 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVCOM.h @@ -0,0 +1,37 @@ +#pragma once +/* +Ben Allison +April 30, 2008 +*/ +#include <oaidl.h> +#include <vector> +#include "FLVMetadata.h" + +struct DispatchCallbackInfo +{ + DispatchCallbackInfo() : dispatch(0), threadId(0), threadHandle(0) {} + DispatchCallbackInfo(IDispatch *d, DWORD id, HANDLE _handle) : dispatch(d), threadId(id), threadHandle(_handle) {} + IDispatch *dispatch; + DWORD threadId; + HANDLE threadHandle; +}; + +class FLVCOM : public IDispatch +{ +public: + STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject); + STDMETHOD_(ULONG, AddRef)(void); + STDMETHOD_(ULONG, Release)(void); + + void MetadataCallback(FLVMetadata::Tag *tag); +private: + // *** IDispatch Methods *** + STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid); + STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo); + STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo); + STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr); + + std::vector<DispatchCallbackInfo> callbacks; +}; + +extern FLVCOM flvCOM;
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVHeader.cpp b/Src/Plugins/Input/in_flv/FLVHeader.cpp new file mode 100644 index 00000000..3b5bc1ee --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVHeader.cpp @@ -0,0 +1,41 @@ +#include "FLVHeader.h" +#include "FLVUtil.h" +/* +(c) 2006 Nullsoft, Inc. +Author: Ben Allison benski@nullsoft.com +*/ + +#define FLV_BITMASK_AUDIO 0x4 +#define FLV_BITMASK_VIDEO 0x1 + +/* +FLV Header spec + +Signature - uint8[3] - must equal "FLV" +Version - uint8 - only known version is 1 +Flags - uint8 - bitmask, 4 is audio, 1 is video +Offset - uint32 - total size of header (9), big endian +*/ + + + + +bool FLVHeader::Read(uint8_t *data, size_t size) +{ + if (size < 9) + return false; // too small to be an FLV header + + if (data[0] != 'F' || data[1] != 'L' || data[2] != 'V') + return false; // invalid signature + + version = data[3]; + + hasAudio = !!(data[4] & FLV_BITMASK_AUDIO); + hasVideo = data[4] & FLV_BITMASK_VIDEO; + + headerSize = FLV::Read32(&data[5]); + if (headerSize != 9) + return false; + + return true; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVHeader.h b/Src/Plugins/Input/in_flv/FLVHeader.h new file mode 100644 index 00000000..9fc283c5 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVHeader.h @@ -0,0 +1,18 @@ +#ifndef NULLSOFT_FLVHEADER_H +#define NULLSOFT_FLVHEADER_H + +#include <bfc/platform/types.h> + +class FLVHeader +{ +public: + FLVHeader() : version(0), hasAudio(0), hasVideo(0), headerSize(0) {} + bool Read(uint8_t *data, size_t size); // size must be >=9, returns "true" if this was a valid header + + // attributes, consider these read-only + uint8_t version; + bool hasAudio, hasVideo; + uint32_t headerSize; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVMetadata.cpp b/Src/Plugins/Input/in_flv/FLVMetadata.cpp new file mode 100644 index 00000000..b98e801b --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVMetadata.cpp @@ -0,0 +1,112 @@ +#include "FLVMetadata.h" +#include "FLVUtil.h" +#include <windows.h> + +/* +(c) 2006 Nullsoft, Inc. +Author: Ben Allison benski@nullsoft.com +*/ + +/* + +type - uint8 - +length - uint16 + +*/ + + +AMFType *MakeObject(uint8_t type) +{ + switch(type) + { + case AMFType::TYPE_DOUBLE: // double + return new AMFDouble; + case AMFType::TYPE_BOOL: // bool + return new AMFBoolean; + case AMFType::TYPE_STRING: // string + return new AMFString; + case AMFType::TYPE_OBJECT: // object + return new AMFObj; + case AMFType::TYPE_MOVIE: // movie (basically just a URL) + return new AMFString; + case AMFType::TYPE_NULL: // null + return 0; + case AMFType::TYPE_REFERENCE: // reference + return 0; + case AMFType::TYPE_MIXEDARRAY: + return new AMFMixedArray; + case AMFType::TYPE_TERMINATOR: + return new AMFTerminator; + case AMFType::TYPE_ARRAY: + return new AMFArray; + case AMFType::TYPE_DATE: // date + return new AMFTime; + case AMFType::TYPE_LONG_STRING: // long string + return new AMFLongString; + case AMFType::TYPE_XML: // XML + return 0; + default: + return 0; + } +} + +FLVMetadata::FLVMetadata() +{ +} + +FLVMetadata::~FLVMetadata() +{ + for ( FLVMetadata::Tag *tag : tags ) + delete tag; +} + +bool FLVMetadata::Read(uint8_t *data, size_t size) +{ + // TODO: there can be multiple name/value pairs so we could read them all + while(size) + { + uint8_t type=*data; + data++; + size--; + + if (type == 0 && size >= 2 && data[0] == 0 && data[1] == AMFType::TYPE_TERMINATOR) // check for terminator + return true; // array is done + + if (type != AMFType::TYPE_STRING) // first entry is a string, verify this + return false; // malformed, lets bail + + FLVMetadata::Tag *tag = new FLVMetadata::Tag; + + // read name + size_t skip = tag->name.Read(data, size); + data+=skip; + size-=skip; + + type=*data; + data++; + size--; + + if (type != AMFType::TYPE_MIXEDARRAY) // second entry is an associative array, verify this + { + delete tag; + return false; // malformed, lets bail + } + + tag->parameters = new AMFMixedArray; // we're new'ing this because we need to reference count + skip = tag->parameters->Read(data, size); + data+=skip; + size-=skip; + tags.push_back(tag); + } + + return true; +} + +FLVMetadata::Tag::Tag() : parameters(0) +{ +} + FLVMetadata::Tag::~Tag() + { + if (parameters) + parameters->Release(); + }
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVMetadata.h b/Src/Plugins/Input/in_flv/FLVMetadata.h new file mode 100644 index 00000000..e67f26bc --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVMetadata.h @@ -0,0 +1,24 @@ +#ifndef NULLSOFT_FLVMETADATA_H +#define NULLSOFT_FLVMETADATA_H + +#include "AMFObject.h" +#include <vector> + +class FLVMetadata +{ +public: + FLVMetadata(); + ~FLVMetadata(); + bool Read(uint8_t *data, size_t size); + struct Tag + { + Tag(); + ~Tag(); + + AMFString name; + AMFMixedArray *parameters; // needs to be pointer so we can refcount + }; + std::vector<Tag*> tags; + +}; +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVProcessor.cpp b/Src/Plugins/Input/in_flv/FLVProcessor.cpp new file mode 100644 index 00000000..961549d8 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVProcessor.cpp @@ -0,0 +1 @@ +#include "FLVProcessor.h" diff --git a/Src/Plugins/Input/in_flv/FLVProcessor.h b/Src/Plugins/Input/in_flv/FLVProcessor.h new file mode 100644 index 00000000..061ff08b --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVProcessor.h @@ -0,0 +1,40 @@ +#pragma once +#include <bfc/platform/types.h> +#include "FLVStreamHeader.h" +#include "FLVHeader.h" + +struct FrameData +{ + FLVStreamHeader header; + uint64_t location; + bool keyFrame; +}; + +enum +{ + FLV_OK=0, + FLV_NEED_MORE_DATA=1, + FLV_ERROR=-1, + + FLVPROCESSOR_WRITE_OK=0, + FLVPROCESSOR_WRITE_ERROR=1, + FLVPROCESSOR_WRITE_WAIT=2, +}; + +class FLVProcessor +{ +public: + virtual ~FLVProcessor() {} + virtual int Write(void *data, size_t datalen, size_t *written) = 0; + virtual int Process()=0; + virtual uint64_t Seek(uint64_t position)=0; + virtual size_t Read(void *data, size_t bytes)=0; + virtual uint64_t GetProcessedPosition()=0; + virtual bool GetFrame(size_t frameIndex, FrameData &frameData)=0; + virtual uint32_t GetMaxTimestamp()=0; + virtual bool GetPosition(int time_in_ms, size_t *frameIndex, bool needsVideoKeyFrame)=0; + virtual bool IsStreaming()=0; + virtual FLVHeader *GetHeader() = 0; +}; + + diff --git a/Src/Plugins/Input/in_flv/FLVReader.cpp b/Src/Plugins/Input/in_flv/FLVReader.cpp new file mode 100644 index 00000000..c756cc70 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVReader.cpp @@ -0,0 +1,211 @@ +#include "FLVReader.h" +#include "FLVHeader.h" +#include "FLVStreamheader.h" +#include "BackgroundDownloader.h" +#include "../nu/AutoChar.h" +#include "FileProcessor.h" +#include "ProgressiveProcessor.h" +#include "StreamProcessor.h" +#include "../../..\Components\wac_network\wac_network_http_receiver_api.h" +#include <shlwapi.h> + +FLVReader::FLVReader(const wchar_t *_url) +{ + processor = 0; + url = _wcsdup(_url); + end_of_stream=false; + + killswitch=false; + DWORD threadId; + flvThread=CreateThread(NULL, NULL, ParserThreadStub, this, 0, &threadId); + SetThreadPriority(flvThread, THREAD_PRIORITY_BELOW_NORMAL); +} + +FLVReader::~FLVReader() +{ + free(url); + delete processor; +} + +uint64_t FLVReader::Seek(uint64_t position) +{ + if (processor) + return processor->Seek(position); + else + return -1; +} + +size_t FLVReader::Read(void *data, size_t bytes) +{ + if (processor) + return processor->Read(data, bytes); + else + return 0; +} + +uint64_t FLVReader::GetProcessedPosition() +{ + if (processor) + return processor->GetProcessedPosition(); + else + return 0; +} + +void FLVReader::ProcessFile() +{ + if (!processor) + return; + + while (processor->Process() == FLV_OK) + { + if (killswitch) + return ; + } +} + +int FLVReader::OnConnect(api_httpreceiver *http) +{ + if (http->content_length()) + processor = new ProgressiveProcessor; + else + processor = new StreamProcessor; + return 0; +} + +int FLVReader::OnData(void *data, size_t datalen) +{ + if (!processor) + return 1; + + bool needSleep=false; + while (datalen) + { + if (killswitch) + return 1; + + if (needSleep) + { + Sleep(10); + needSleep=false; + } + + size_t written=0; + switch (processor->Write(data, datalen, &written)) + { + case FLVPROCESSOR_WRITE_ERROR: + return 1; + case FLVPROCESSOR_WRITE_WAIT: + needSleep=true; + break; + } + + datalen -= written; + + if (written) + { + while (1) + { + if (killswitch) + return 1; + + int ret = processor->Process(); + if (ret == FLV_OK) + continue; + if (ret == FLV_NEED_MORE_DATA) + break; + if (ret == FLV_ERROR) + return 1; + } + } + } + return !!killswitch; +} + +bool FLVReader::GetFrame(size_t frameIndex, FrameData &frameData) +{ + if (processor) + return processor->GetFrame(frameIndex, frameData); + else + return false; +} + +/* +Test URLs (valid of as oct 23 2007) +http://pdl.stream.aol.com/aol/us/aolmusic/artists/astralwerks/2220s/2220s_shootyourgun_hakjfh_700_dl.flv +http://pdl.stream.aol.com/aol/us/aolmusic/sessions/2007/amberpacific/suc_amberpacific_fallbackintomylife_700_dl.flv +http://pdl.stream.aol.com/aol/us/aolmusic/sessions/2007/amberpacific/suc_amberpacific_gonesoyoung_700_dl.flv +http://pdl.stream.aol.com/aol/us/aolmusic/sessions/2007/amberpacific/suc_amberpacific_soyesterday_700_dl.flv +http://pdl.stream.aol.com/aol/us/aolmusic/sessions/2007/amberpacific/suc_amberpacific_watchingoverme_700_dl.flv + +a potential crasher: +http://pdl.stream.aol.com/aol/us/aolcomvideo/TVGuide/johncmcginley_6346/johncmcginley_6346_460_700_dl.flv + +FLV streams: +http://208.80.52.96/CBS_R20_452P_F128?ext=.flv +http://208.80.54.37/KROQFM?ext=.flv +http://208.80.52.96/CBS_R20_568P_F128?ext=.flv +*/ +DWORD FLVReader::ParserThread() +{ + if (PathIsURL(url)) + { + Downloader downloader; + downloader.Download(AutoChar(url), this); + } + else + { + processor = new FileProcessor(url); + ProcessFile(); + } + end_of_stream=true; + return 0; +} + +void FLVReader::Kill() +{ + killswitch=true; + WaitForSingleObject(flvThread, INFINITE); + CloseHandle(flvThread); +} + +void FLVReader::SignalKill() +{ + killswitch=true; +} + +bool FLVReader::IsEOF() +{ + return end_of_stream; +} + +bool FLVReader::GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame) +{ + if (processor) + return processor->GetPosition(time_in_ms, frameIndex, needVideoKeyFrame); + else + return false; +} + +uint32_t FLVReader::GetMaxTimestamp() +{ + if (processor) + return processor->GetMaxTimestamp(); + else + return 0; +} + +bool FLVReader::IsStreaming() +{ + if (processor) + return processor->IsStreaming(); + else + return true; +} + +FLVHeader *FLVReader::GetHeader() +{ + if (processor) + return processor->GetHeader(); + else + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVReader.h b/Src/Plugins/Input/in_flv/FLVReader.h new file mode 100644 index 00000000..27e68183 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVReader.h @@ -0,0 +1,49 @@ +#ifndef NULLSOFT_IN_FLV_FLVREADER_H +#define NULLSOFT_IN_FLV_FLVREADER_H + +#include <windows.h> +#include <bfc/platform/types.h> +#include "FLVStreamheader.h" +#include "../nu/AutoLock.h" +#include "BackgroundDownloader.h" +#include "FLVProcessor.h" + +class FLVReader : private Downloader::DownloadCallback +{ +public: + FLVReader(const wchar_t *_url); + ~FLVReader(); + + bool GetFrame(size_t frameIndex, FrameData &frameData); + bool GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame); + uint64_t Seek(uint64_t position); + size_t Read(void *data, size_t bytes); + void Kill(); + void SignalKill(); + bool IsEOF(); + uint32_t GetMaxTimestamp(); + uint64_t GetProcessedPosition(); + bool IsStreaming(); + FLVHeader *GetHeader(); +private: + void ProcessFile(); + int OnConnect(api_httpreceiver *http); + int OnData(void *data, size_t datalen); + int Process(); + DWORD CALLBACK ParserThread(); + static DWORD CALLBACK ParserThreadStub(LPVOID param) { return ((FLVReader *)param)->ParserThread(); } + +private: + bool killswitch; + + HANDLE flvThread; + bool end_of_stream; + wchar_t *url; + + /* because we won't know until after opening the stream whether it's progressive download + or a real-time stream, we need have a pointer to a virtual base class to do the processing + we'll create a different one depending on what kind of stream */ + FLVProcessor *processor; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVStreamHeader.cpp b/Src/Plugins/Input/in_flv/FLVStreamHeader.cpp new file mode 100644 index 00000000..8f1ef49e --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVStreamHeader.cpp @@ -0,0 +1,36 @@ +#include "FLVStreamHeader.h" +#include "FLVUtil.h" +/* +(c) 2006 Nullsoft, Inc. +Author: Ben Allison benski@nullsoft.com +*/ + +/* +PreviousTagSize - uint32 - total size of previous tag, presumably for stream continuity checking. big endian +Type - uint8 - what does this frame contain? 0x12=meta, 0x8=audio, 0x9=video +BodyLength - uint24 - size of the data following this header. big endian +Timestamp - uint24 - timestamp (milliseconds). big endian +Timestamp High - uint8 - high 8 bits of timestamp (to form 32bit timestamp) +Stream ID - uint24 - always zero +*/ + +bool FLVStreamHeader::Read(uint8_t *data, size_t size) +{ + if (size < 15) + return false; // header size too small + + previousSize = FLV::Read32(&data[0]); + type = data[4]; + dataSize = FLV::Read24(&data[5]); + timestamp = FLV::Read24(&data[8]); + uint8_t timestampHigh = FLV::Read8(&data[11]); + timestamp |= (timestampHigh << 24); + streamID = FLV::Read24(&data[12]); + + if (streamID != 0) + return false; + + return true; + + +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVStreamHeader.h b/Src/Plugins/Input/in_flv/FLVStreamHeader.h new file mode 100644 index 00000000..855d4159 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVStreamHeader.h @@ -0,0 +1,27 @@ +#ifndef NULLSOFT_FLVSTREAMHEADER_H +#define NULLSOFT_FLVSTREAMHEADER_H + +#include <bfc/platform/types.h> +namespace FLV +{ + enum + { + FRAME_TYPE_AUDIO = 0x8, + FRAME_TYPE_VIDEO = 0x9, + FRAME_TYPE_METADATA = 0x12, + }; +} +class FLVStreamHeader +{ +public: + bool Read(unsigned __int8 *data, size_t size); // size must be >=15, returns "true" if this was a valid header + + // attributes, consider these read-only + uint32_t previousSize; + uint8_t type; + uint32_t dataSize; + uint32_t timestamp; + uint32_t streamID; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVUtil.h b/Src/Plugins/Input/in_flv/FLVUtil.h new file mode 100644 index 00000000..9b9da776 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVUtil.h @@ -0,0 +1,90 @@ +#ifndef NULLSOFT_FLVUTIL_H +#define NULLSOFT_FLVUTIL_H + +#include <memory.h> +#include <bfc/platform/types.h> +namespace FLV +{ + // reads 32 bits from data and converts from big endian +inline uint32_t Read32(uint8_t *data) +{ + uint32_t returnVal; +#ifdef __BIG_ENDIAN__ + returnVal = *(uint32_t *)(&data[0]); +#else + // need to swap endianness + uint8_t *swap = (uint8_t *)&returnVal; + swap[0]=data[3]; + swap[1]=data[2]; + swap[2]=data[1]; + swap[3]=data[0]; +#endif + return returnVal; + +} +// reads 24 bits from data, converts from big endian, and returns as a 32bit int +inline uint32_t Read24(uint8_t *data) +{ + uint32_t returnVal=0; + uint8_t *swap = (uint8_t *)&returnVal; +#ifdef __BIG_ENDIAN__ + swap[1]=data[0]; + swap[2]=data[1]; + swap[3]=data[2]; + returnVal = *(uint32_t *)(&data[0]); +#else + // need to swap endianness + swap[0]=data[2]; + swap[1]=data[1]; + swap[2]=data[0]; +#endif + return returnVal; + +} + +// reads 16 bits from data and converts from big endian +inline uint16_t Read16(uint8_t *data) +{ + uint16_t returnVal; +#ifdef __BIG_ENDIAN__ + returnVal = *(uint16_t *)(&data[0]); +#else + // need to swap endianness + uint8_t *swap = (uint8_t *)&returnVal; + swap[0]=data[1]; + swap[1]=data[0]; +#endif + return returnVal; +} + +// reads 16 bits from data and converts from big endian +inline uint8_t Read8(uint8_t *data) +{ + uint8_t returnVal; + + returnVal = *(uint8_t *)(&data[0]); + return returnVal; +} + +// reads a double from data +inline double ReadDouble(uint8_t *data) +{ + double returnVal; +#ifdef __BIG_ENDIAN__ + memcpy(&returnVal, data, 8); + #else + uint8_t *swap = (uint8_t *)&returnVal; + swap[0]=data[7]; + swap[1]=data[6]; + swap[2]=data[5]; + swap[3]=data[4]; + swap[4]=data[3]; + swap[5]=data[2]; + swap[6]=data[1]; + swap[7]=data[0]; + #endif + return returnVal; +} + +} +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVVideoHeader.cpp b/Src/Plugins/Input/in_flv/FLVVideoHeader.cpp new file mode 100644 index 00000000..44c2cf69 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVVideoHeader.cpp @@ -0,0 +1,24 @@ +#include "FLVVideoHeader.h" + +/* +(c) 2006 Nullsoft, Inc. +Author: Ben Allison benski@nullsoft.com +*/ + +/* +codecID - 4 bits - 2: Sorensen H.263, 3: Screen video, 4: On2 VP6 +frameType - 4 bits - 1: keyframe, 2: inter frame, 3: disposable inter frame +*/ + +bool FLVVideoHeader::Read(unsigned __int8 *data, size_t size) +{ + if (size < 1) + return false; // header size too small + + unsigned __int8 byte = data[0]; + + format = (byte & 0x0f) >> 0; + frameType = (byte & 0xf0) >> 4; + + return true; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVVideoHeader.h b/Src/Plugins/Input/in_flv/FLVVideoHeader.h new file mode 100644 index 00000000..6a40d886 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVVideoHeader.h @@ -0,0 +1,32 @@ +#ifndef NULLSOFT_FLVVIDEOHEADER_H +#define NULLSOFT_FLVVIDEOHEADER_H + +namespace FLV +{ + const int VIDEO_FORMAT_JPEG = 1; + const int VIDEO_FORMAT_SORENSON = 2; // H.263 + const int VIDEO_FORMAT_SCREEN = 3; + const int VIDEO_FORMAT_VP6 = 4; + const int VIDEO_FORMAT_VP62 = 5; + const int VIDEO_FORMAT_SCREEN_V2 = 6; + const int VIDEO_FORMAT_AVC = 7; // MPEG-4 Part 10 + + const int VIDEO_FRAMETYPE_KEYFRAME = 1; + const int VIDEO_FRAMETYPE_IFRAME = 2; + const int VIDEO_FRAMETYPE_IFRAME_DISPOSABLE = 3; + const int VIDEO_FRAMETYPE_GENERATED = 4; + const int VIDEO_FRAMETYPE_INFO = 5; +}; + + +class FLVVideoHeader +{ +public: + bool Read(unsigned __int8 *data, size_t size); // size must be >=1, returns "true" if this was a valid header + + // attributes, consider these read-only + int format; + int frameType; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FileProcessor.cpp b/Src/Plugins/Input/in_flv/FileProcessor.cpp new file mode 100644 index 00000000..f2351ee0 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FileProcessor.cpp @@ -0,0 +1,201 @@ +#include "FileProcessor.h" +#include "FLVHeader.h" +#include "FLVVideoHeader.h" + +static int64_t Seek64(HANDLE hf, int64_t 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; +} + +int64_t FileSize64(HANDLE file) +{ + LARGE_INTEGER position; + position.QuadPart=0; + position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart); + + if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) + return INVALID_FILE_SIZE; + else + return position.QuadPart; +} + +FileProcessor::FileProcessor() +{ + Init(); +} + +FileProcessor::FileProcessor(const wchar_t *filename) +{ + Init(); + processedCursor=CreateFile(filename, GENERIC_READ, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); + readCursor=CreateFile(filename, GENERIC_READ, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); + writePosition=FileSize64(readCursor); +} + +FileProcessor::~FileProcessor() +{ + if (processedCursor != INVALID_HANDLE_VALUE) + CloseHandle(processedCursor); + if (readCursor != INVALID_HANDLE_VALUE) + CloseHandle(readCursor); +} + +void FileProcessor::Init() +{ + processedPosition=0; + processedCursor=INVALID_HANDLE_VALUE; + writePosition=0; + readCursor=INVALID_HANDLE_VALUE; + flen=0; + maxTimeStamp=0; + headerOK=false; +} + +int FileProcessor::Process() +{ + int64_t oldPosition = Seek64(processedCursor, 0, FILE_CURRENT); + + // read file header if we're at the beginning + if (processedPosition == 0) + { + if (writePosition-processedPosition >= 9) // need at least nine bytes for the FLV file header + { + uint8_t data[9] = {0}; + DWORD bytesRead=0; + ReadFile(processedCursor, data, 9, &bytesRead, NULL); + if (header.Read(data, bytesRead)) + { + headerOK=true; + Nullsoft::Utility::AutoLock lock(frameGuard); + processedPosition += bytesRead; + return FLV_OK; // we'll make our logic just a little bit more sane by only processing one frame per pass. + } + else + return FLV_ERROR; + } + + Seek64(processedCursor, oldPosition, FILE_BEGIN); // rollback + return FLV_NEED_MORE_DATA; + } + + if (writePosition-processedPosition >= 15) // need at least fifteen bytes for the FLV frame header + { + uint8_t data[15] = {0}; + DWORD bytesRead=0; + ReadFile(processedCursor, data, 15, &bytesRead, NULL); + FrameData frameData; + if (frameData.header.Read(data, bytesRead)) + { + if (frameData.header.dataSize + processedPosition + 15 <= writePosition) + { + frameData.keyFrame = false; + if (frameData.header.type == FLV::FRAME_TYPE_VIDEO) + { + FLVVideoHeader videoHeader; + DWORD videoHeaderRead=0; + ReadFile(processedCursor, data, 1, &videoHeaderRead, NULL); + if (videoHeader.Read(data, bytesRead)) + { + if (videoHeader.frameType == FLV::VIDEO_FRAMETYPE_KEYFRAME) + { + frameData.keyFrame = true; + } + } + Seek64(processedCursor, -(int64_t)videoHeaderRead, FILE_CURRENT); + // roll back file cursor from ReadFile + } + + { // critsec enter + Nullsoft::Utility::AutoLock lock(frameGuard); + // record the offset where we found it + frameData.location = processedPosition; + maxTimeStamp=max(maxTimeStamp, frameData.header.timestamp); + frames.push_back(frameData); + } // critsec leave + Seek64(processedCursor, frameData.header.dataSize, FILE_CURRENT); + + Nullsoft::Utility::AutoLock lock(frameGuard); + processedPosition+=bytesRead+frameData.header.dataSize; + return FLV_OK; + } + Seek64(processedCursor, oldPosition, FILE_BEGIN); // rollback + return FLV_NEED_MORE_DATA; + } + else + { + return FLV_ERROR; + } + } + Seek64(processedCursor, oldPosition, FILE_BEGIN); // rollback + return FLV_NEED_MORE_DATA; +} + +uint32_t FileProcessor::GetMaxTimestamp() +{ + return maxTimeStamp; +} + +bool FileProcessor::GetFrame(size_t frameIndex, FrameData &frameData) +{ + Nullsoft::Utility::AutoLock lock(frameGuard); + if (frameIndex < frames.size()) + { + frameData = frames[frameIndex]; + return true; + } + return false; +} + +uint64_t FileProcessor::GetProcessedPosition() +{ + Nullsoft::Utility::AutoLock lock(frameGuard); + return processedPosition; +} + +size_t FileProcessor::Read(void *data, size_t bytes) +{ + DWORD bytesRead = 0; + ReadFile(readCursor, data, (DWORD)bytes, &bytesRead, NULL); + return bytesRead; +} + +uint64_t FileProcessor::Seek(uint64_t position) +{ + return Seek64(readCursor, position, FILE_BEGIN); +} + +bool FileProcessor::GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame) +{ + Nullsoft::Utility::AutoLock lock(frameGuard); + // TODO: binary search + for (size_t f=0;f!=frames.size();f++) + { + if (frames[f].header.timestamp >= (uint32_t)time_in_ms) + { + if (needVideoKeyFrame) + { + while (f != 0 && !frames[f].keyFrame) + f--; + } + *frameIndex = f; + return true; + } + } + return false; +} + +FLVHeader *FileProcessor::GetHeader() +{ + if (headerOK) + return &header; + else + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FileProcessor.h b/Src/Plugins/Input/in_flv/FileProcessor.h new file mode 100644 index 00000000..d0fb6bd3 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FileProcessor.h @@ -0,0 +1,43 @@ +#pragma once +#include "FLVProcessor.h" +#include <windows.h> +#include <vector> +#include "../nu/AutoLock.h" + +class FileProcessor : public FLVProcessor +{ +public: + FileProcessor(const wchar_t *filename); + ~FileProcessor(); +private: + /* FLVProcessor virtual method overrides */ + int Write(void *data, size_t datalen, size_t *written) { return 1; } + uint64_t GetProcessedPosition(); + uint32_t GetMaxTimestamp(); + bool GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame); + bool IsStreaming() { return false; } + FLVHeader *GetHeader(); +public: + /* FLVProcessor virtual method overrides, continued + (these are public so FileProcessor can be used standalone */ + int Process(); + uint64_t Seek(uint64_t position); + size_t Read(void *data, size_t bytes); + bool GetFrame(size_t frameIndex, FrameData &frameData); + +protected: + FileProcessor(); + uint64_t processedPosition; + uint64_t writePosition; + HANDLE processedCursor, readCursor; + // file positions of samples + std::vector<FrameData> frames; + Nullsoft::Utility::LockGuard frameGuard; + uint64_t flen; + +private: + void Init(); + uint32_t maxTimeStamp; + FLVHeader header; + bool headerOK; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/PlayThread.cpp b/Src/Plugins/Input/in_flv/PlayThread.cpp new file mode 100644 index 00000000..33fa735e --- /dev/null +++ b/Src/Plugins/Input/in_flv/PlayThread.cpp @@ -0,0 +1,703 @@ +#include "main.h" +#include <windows.h> +#include "api__in_flv.h" +#include "../Winamp/wa_ipc.h" +#include "FLVHeader.h" +#include "FLVStreamHeader.h" +#include "FLVAudioHeader.h" +#include "FLVVideoHeader.h" +#include "FLVMetadata.h" +#include <malloc.h> +#include <stdio.h> +#include <shlwapi.h> +#include "VideoThread.h" +#include "FLVReader.h" +#include "resource.h" +#include "FLVCOM.h" +#include <api/service/waservicefactory.h> +#include "ifc_flvaudiodecoder.h" +#include "svc_flvdecoder.h" +#include "../nu/AudioOutput.h" + +#define PRE_BUFFER_MS 5000.0 +#define PRE_BUFFER_MAX (1024*1024) + +uint32_t last_timestamp=0; +static bool audioOpened; +int bufferCount; +static int bits, channels, sampleRate; +static double dataRate_audio, dataRate_video; +static double dataRate; +static uint64_t prebuffer; +bool mute=false; +uint32_t first_timestamp; +static size_t audio_buffered=0; +void VideoStop(); +static ifc_flvaudiodecoder *audioDecoder=0; +static bool checked_in_swf=false; +extern bool video_only; + +class FLVWait +{ +public: + int WaitOrAbort(int len) + { + if (WaitForSingleObject(killswitch, len) == WAIT_OBJECT_0) + return 1; + + return 0; + } +}; + +static nu::AudioOutput<FLVWait> outputter(&plugin); + +static void Buffering(int bufStatus, const wchar_t *displayString) +{ + if (bufStatus < 0 || bufStatus > 100) + return; + + char tempdata[75*2] = {0, }; + + int csa = plugin.SAGetMode(); + if (csa & 1) + { + for (int x = 0; x < bufStatus*75 / 100; x ++) + tempdata[x] = x * 16 / 75; + } + else if (csa&2) + { + int offs = (csa & 1) ? 75 : 0; + int x = 0; + while (x < bufStatus*75 / 100) + { + tempdata[offs + x++] = -6 + x * 14 / 75; + } + while (x < 75) + { + tempdata[offs + x++] = 0; + } + } + else if (csa == 4) + { + tempdata[0] = tempdata[1] = (bufStatus * 127 / 100); + } + if (csa) plugin.SAAdd(tempdata, ++bufferCount, (csa == 3) ? 0x80000003 : csa); + + /* + TODO + wchar_t temp[64] = {0}; + StringCchPrintf(temp, 64, L"%s: %d%%",displayString, bufStatus); + SetStatus(temp); + */ + //SetVideoStatusText(temp); // TODO: find a way to set the old status back + videoOutput->notifyBufferState(static_cast<int>(bufStatus*2.55f)); +} + +static bool Audio_IsSupported(int type) +{ + size_t n = 0; + waServiceFactory *factory = NULL; + while (factory = plugin.service->service_enumService(WaSvc::FLVDECODER, n++)) + { + svc_flvdecoder *creator = (svc_flvdecoder *)factory->getInterface(); + if (creator) + { + int supported = creator->HandlesAudio(type); + factory->releaseInterface(creator); + if (supported == svc_flvdecoder::CREATEDECODER_SUCCESS) + return true; + } + } + return false; +} + +static ifc_flvaudiodecoder *CreateAudioDecoder(const FLVAudioHeader &header) +{ + ifc_flvaudiodecoder *audio_decoder=0; + size_t n=0; + waServiceFactory *factory = NULL; + while (factory = plugin.service->service_enumService(WaSvc::FLVDECODER, n++)) + { + svc_flvdecoder *creator = (svc_flvdecoder *)factory->getInterface(); + if (creator) + { + if (creator->CreateAudioDecoder(header.stereo, header.bits, header.sampleRate, header.format, &audio_decoder) == FLV_AUDIO_SUCCESS) + return audio_decoder; + + factory->releaseInterface(creator); + } + } + return 0; +} + +void OnStart() +{ + Video_Init(); + + audioOpened = false; + audioDecoder = 0; + + bufferCount=0; + mute = false; + audio_buffered=0; + + if (!videoOutput) + videoOutput = (IVideoOutput *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GET_IVIDEOOUTPUT); +} + +void Audio_Close() +{ + if (audioDecoder) + audioDecoder->Close(); + audioDecoder=0; +} + +static bool OpenAudio(unsigned int sampleRate, unsigned int channels, unsigned int bits) +{ + if (!outputter.Open(0, channels, sampleRate, bits, -666)) + return false; + audioOpened = true; + return true; +} + +static void DoBuffer(FLVReader &reader) +{ + // TODO: we should pre-buffer after getting the first audio + video frames + // so we can estimate bitrate + for (;;) + { + uint64_t processedPosition = reader.GetProcessedPosition(); + if (processedPosition < prebuffer && !reader.IsEOF()) + { + Buffering((int)((100ULL * processedPosition) / prebuffer), WASABI_API_LNGSTRINGW(IDS_BUFFERING)); + if (WaitForSingleObject(killswitch, 100) == WAIT_OBJECT_0) + break; + else + continue; + } + else + break; + } +} + + +char pcmdata[65536] = {0}; +size_t outlen = 32768; + +static uint32_t audio_type; +static void OnAudio(FLVReader &reader, void *data, size_t length, const FLVAudioHeader &header) +{ + if (!audioDecoder) + { + audioDecoder = CreateAudioDecoder(header); + audio_type = header.format; + video_only=false; + } + + if (audioDecoder) + { + + if (!audioDecoder->Ready()) + { + //first_timestamp = -1; + } + + outlen = sizeof(pcmdata)/2 - audio_buffered; + double bitrate = 0; + int ret = audioDecoder->DecodeSample(data, length, pcmdata+audio_buffered, &outlen, &bitrate); + if (ret == FLV_AUDIO_SUCCESS) + { + outlen+=audio_buffered; + audio_buffered=0; + if (bitrate && dataRate_audio != bitrate) + { + dataRate_audio = bitrate; + dataRate = dataRate_audio + dataRate_video; + plugin.SetInfo((int)dataRate, -1, -1, 1); + } + + if (!audioOpened) + { + // pre-populate values for decoders that use the header info (e.g. ADPCM) + sampleRate=header.sampleRate; + channels = header.stereo?2:1; + bits = header.bits; + + if (audioDecoder->GetOutputFormat((unsigned int *)&sampleRate, (unsigned int *)&channels, (unsigned int *)&bits) == FLV_AUDIO_SUCCESS) + { + + // buffer (if needed) + prebuffer = (uint64_t)(dataRate * PRE_BUFFER_MS / 8.0); + if (prebuffer > PRE_BUFFER_MAX) + prebuffer=PRE_BUFFER_MAX; + DoBuffer(reader); // benski> admittedly a crappy place to call this + if (WaitForSingleObject(killswitch, 0) == WAIT_OBJECT_0) + return; + + OpenAudio(sampleRate, channels, bits); + } + } + + if (mute) + { + if (bits == 8) // 8 bit is signed so 128 is zero voltage + memset(pcmdata, 0x80, outlen); + else + memset(pcmdata, 0, outlen); + } + + if (audioOpened && outlen) + { + outputter.Write(pcmdata, outlen); + } + else + { + audio_buffered=outlen; + } + + } + else if (ret == FLV_AUDIO_NEEDS_MORE_INPUT) + { + plugin.SetInfo(-1, -1, -1, 0); + } + + } +} + + +#define PREBUFFER_BYTES 2048ULL + + +enum +{ + CODEC_CHECK_NONE=0, + CODEC_CHECK_AUDIO=1, + CODEC_CHECK_VIDEO=2, + CODEC_CHECK_UNSURE = -1, +}; + +static bool CheckSWF() +{ + if (!checked_in_swf) + { + const wchar_t *pluginsDir = (const wchar_t *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GETPLUGINDIRECTORYW); + wchar_t in_swf_path[MAX_PATH] = {0}; + PathCombineW(in_swf_path, pluginsDir, L"in_swf.dll"); + in_swf = LoadLibraryW(in_swf_path); + checked_in_swf = true; + } + return !!in_swf; +} + +static void CALLBACK SWFAPC(ULONG_PTR param) +{ + if (in_swf) + { + typedef In_Module *(*MODULEGETTER)(); + MODULEGETTER moduleGetter=0; + moduleGetter = (MODULEGETTER)GetProcAddress(in_swf, "winampGetInModule2"); + if (moduleGetter) + swf_mod = moduleGetter(); + } + + if (swf_mod) + { + if (swf_mod->Play(playFile)) + swf_mod=0; + } + + if (!swf_mod) + { + if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + } +} + +static bool Audio_DecoderReady() +{ + if (!audioDecoder) + return true; + + return !!audioDecoder->Ready(); +} + +static bool DecodersReady() +{ + return Audio_DecoderReady() && Video_DecoderReady(); +} + +DWORD CALLBACK PlayProcedure(LPVOID param) +{ + int needCodecCheck=CODEC_CHECK_UNSURE; + int missingCodecs=CODEC_CHECK_NONE; + size_t codecCheckFrame=0; + + dataRate_audio=0; + dataRate_video=0; + dataRate=0; + first_timestamp=-1; + + outputter.Init(plugin.outMod); + + FLVReader reader(playFile); + size_t i=0; + bool hasDuration=false; + + OnStart(); + plugin.is_seekable=0; + + prebuffer = PREBUFFER_BYTES; + bool first_frame_parsed=false; + + for (;;) + { + DoBuffer(reader); + + if (WaitForSingleObject(killswitch, paused?100:0) == WAIT_OBJECT_0) + break; + + if (paused) + continue; + + if (m_need_seek != -1 && DecodersReady() && first_frame_parsed) + { + if (reader.GetPosition(m_need_seek, &i, video_opened)) + { + VideoFlush(); + if (audioDecoder) + audioDecoder->Flush(); + FrameData frameData; + reader.GetFrame(i, frameData); + outputter.Flush(frameData.header.timestamp); + + if (video_only) + { + video_clock.Seek(frameData.header.timestamp); + } + } + uint32_t first_timestamp = 0; + m_need_seek=-1; + } + + // update the movie length + if (!hasDuration) + { + if (reader.IsStreaming()) + { + hasDuration=true; + g_length = -1000; + plugin.is_seekable=0; + } + else + { + g_length=reader.GetMaxTimestamp(); + if (g_length != -1000) + plugin.is_seekable=1; + } + PostMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE); + } + + /** if it's a local file or an HTTP asset with content-length + ** we verify that the FLV has codecs we can decode + ** depending on settings, we will do one of the following with unsupport assets + ** 1) Play anyway (e.g. an H.264 video might play audio only) + ** 2) Use in_swf to play back + ** 3) skip + **/ + + if (needCodecCheck == CODEC_CHECK_UNSURE) + { + FLVHeader *header = reader.GetHeader(); + if (header) + { + needCodecCheck=CODEC_CHECK_NONE; + if (!reader.IsStreaming()) + { + if (header->hasVideo) + needCodecCheck |= CODEC_CHECK_VIDEO; + if (header->hasAudio) + needCodecCheck |= CODEC_CHECK_AUDIO; + + } + if (!header->hasAudio) + { + video_only=true; + video_clock.Start(); + } + } + } + + if (needCodecCheck) + { + FrameData frameData; + if (reader.GetFrame(codecCheckFrame, frameData)) + { + FLVStreamHeader &frameHeader = frameData.header; + if ((needCodecCheck & CODEC_CHECK_AUDIO) && frameHeader.type == FLV::FRAME_TYPE_AUDIO) + { + reader.Seek(frameData.location+15); // TODO: check for -1 return value + + uint8_t data[1] = {0}; + FLVAudioHeader audioHeader; + size_t bytesRead = reader.Read(data, 1); + if (audioHeader.Read(data, bytesRead)) + { + if (Audio_IsSupported(audioHeader.format)) + { + needCodecCheck &= ~CODEC_CHECK_AUDIO; + } + else + { + needCodecCheck &= ~CODEC_CHECK_AUDIO; + missingCodecs|=CODEC_CHECK_AUDIO; + video_only=true; + } + } + } + if ((needCodecCheck & CODEC_CHECK_VIDEO) && frameHeader.type == FLV::FRAME_TYPE_VIDEO) + { + reader.Seek(frameData.location+15); // TODO: check for -1 return value + + uint8_t data[1] = {0}; + FLVVideoHeader videoHeader; + size_t bytesRead = reader.Read(data, 1); + if (videoHeader.Read(data, bytesRead)) + { + if (Video_IsSupported(videoHeader.format)) + { + needCodecCheck &= ~CODEC_CHECK_VIDEO; + } + else + { + needCodecCheck &= ~CODEC_CHECK_VIDEO; + missingCodecs|=CODEC_CHECK_VIDEO; + } + } + } + + codecCheckFrame++; + } + else if (reader.IsEOF()) + break; + else if (WaitForSingleObject(killswitch, 55) == WAIT_OBJECT_0) + break; + + } + + if (needCodecCheck) + continue; // don't start decoding until we've done our codec check + + if (missingCodecs) + { + // use in_swf to play this one + if (CheckSWF()) + { + HANDLE mainThread = WASABI_API_APP->main_getMainThreadHandle(); + if (mainThread) + { + reader.Kill(); + Audio_Close(); + Video_Stop(); + Video_Close(); + QueueUserAPC(SWFAPC, mainThread,0); + CloseHandle(mainThread); + return 0; + } + } + else + { + FLVHeader *header = reader.GetHeader(); + if (header) + { + bool can_play_something = false; + + if (header->hasVideo && !(missingCodecs & CODEC_CHECK_VIDEO)) + can_play_something = true; // we can play video + else if (header->hasAudio && !(missingCodecs & CODEC_CHECK_AUDIO)) + can_play_something = true; // we can play audio + + if (can_play_something) + { + missingCodecs=false; + continue; + } + } + break; // no header or no codecs at all, bail out + } + } + + /* --- End Codec Check --- */ + FrameData frameData; + if (reader.GetFrame(i, frameData)) + { + i++; + uint8_t data[2] = {0}; + FLVStreamHeader &frameHeader = frameData.header; + reader.Seek(frameData.location+15); // TODO: check for -1 return value + switch (frameHeader.type) + { + default: +#ifdef _DEBUG + DebugBreak(); +#endif + break; + case FLV::FRAME_TYPE_AUDIO: // audio + first_frame_parsed=true; + if (m_need_seek == -1 || !Audio_DecoderReady()) + { + FLVAudioHeader audioHeader; + size_t bytesRead = reader.Read(data, 1); + if (audioHeader.Read(data, bytesRead)) + { + size_t dataSize = frameHeader.dataSize - 1; + + uint8_t *audiodata = (uint8_t *)calloc(dataSize, sizeof(uint8_t)); + if (audiodata) + { + bytesRead = reader.Read(audiodata, dataSize); + if (bytesRead != dataSize) + break; + if (!reader.IsStreaming()) + { + if (first_timestamp == -1) + first_timestamp = frameHeader.timestamp; + last_timestamp = frameHeader.timestamp; + last_timestamp = plugin.outMod->GetWrittenTime(); + } + + OnAudio(reader, audiodata, dataSize, audioHeader); + free(audiodata); + } + } + } + break; + case FLV::FRAME_TYPE_VIDEO: // video + first_frame_parsed=true; + if (m_need_seek == -1 || !Video_DecoderReady()) + { + + FLVVideoHeader videoHeader; + size_t bytesRead = reader.Read(data, 1); + if (videoHeader.Read(data, bytesRead)) + { + size_t dataSize = frameHeader.dataSize - 1; + + uint8_t *videodata = (uint8_t *)calloc(dataSize, sizeof(uint8_t)); + if (videodata) + { + bytesRead = reader.Read(videodata, dataSize); + if (bytesRead != dataSize) + { + free(videodata); + break; + } + if (!OnVideo(videodata, dataSize, videoHeader.format, frameHeader.timestamp)) + free(videodata); + } + } + } + break; + case FLV::FRAME_TYPE_METADATA: // metadata + { + first_frame_parsed=true; + size_t dataSize = frameHeader.dataSize; + uint8_t *metadatadata= (uint8_t *)calloc(dataSize, sizeof(uint8_t)); + if (metadatadata) + { + size_t bytesRead = reader.Read(metadatadata, dataSize); + if (bytesRead != dataSize) + { + free(metadatadata); + break; + } + FLVMetadata metadata; + metadata.Read(metadatadata, dataSize); + for ( FLVMetadata::Tag *tag : metadata.tags ) + { + if (!_wcsicmp(tag->name.str, L"onMetaData")) + { + AMFType *amf_stream_title; + + amf_stream_title = tag->parameters->array[L"streamTitle"]; + if (amf_stream_title && amf_stream_title->type == AMFType::TYPE_STRING) + { + AMFString *stream_title_string = (AMFString *)amf_stream_title; + Nullsoft::Utility::AutoLock stream_lock(stream_title_guard); + free(stream_title); + stream_title = _wcsdup(stream_title_string->str); + PostMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE); + } + + AMFType *w, *h; + w = tag->parameters->array[L"width"]; + h = tag->parameters->array[L"height"]; + if (w && h) + { + width = (int)AMFGetDouble(w); + height = (int)AMFGetDouble(h); + } + + AMFType *duration; + duration=tag->parameters->array[L"duration"]; + if (duration) + { + hasDuration=true; + plugin.is_seekable=1; + g_length = (int)(AMFGetDouble(duration)*1000.0); + } + + // grab the data rate. we'll need this to determine a good pre-buffer. + AMFType *videoDataRate, *audioDataRate; + videoDataRate=tag->parameters->array[L"videodatarate"]; + audioDataRate=tag->parameters->array[L"audiodatarate"]; + if (videoDataRate || audioDataRate) + { + dataRate_audio = audioDataRate?AMFGetDouble(audioDataRate):0.0; + dataRate_video = videoDataRate?AMFGetDouble(videoDataRate):0.0; + + dataRate = dataRate_audio + dataRate_video; + + if (dataRate < 1.0f) + dataRate = 720.0f; + plugin.SetInfo((int)dataRate, -1, -1, 1); + prebuffer = (uint64_t)(dataRate * PRE_BUFFER_MS / 8.0); + if (prebuffer > PRE_BUFFER_MAX) + prebuffer=PRE_BUFFER_MAX; + + PostMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE); + } + } + flvCOM.MetadataCallback(tag); + } + free(metadatadata); + } + } + break; + } + } + else if (reader.IsEOF()) + break; + else if (WaitForSingleObject(killswitch, 55) == WAIT_OBJECT_0) + break; + } + + reader.SignalKill(); + + if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0) + { + outputter.Write(0,0); + outputter.WaitWhilePlaying(); + + if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + } + + SetEvent(killswitch); + + Video_Stop(); + Video_Close(); + + reader.Kill(); + Audio_Close(); + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/ProgressiveProcessor.cpp b/Src/Plugins/Input/in_flv/ProgressiveProcessor.cpp new file mode 100644 index 00000000..c4e3c5d7 --- /dev/null +++ b/Src/Plugins/Input/in_flv/ProgressiveProcessor.cpp @@ -0,0 +1,34 @@ +#include "ProgressiveProcessor.h" + +ProgressiveProcessor::ProgressiveProcessor() +{ + tempFile[0]=0; + writeCursor=INVALID_HANDLE_VALUE; + + wchar_t tempPath[MAX_PATH-14] = {0}; + GetTempPath(MAX_PATH-14, tempPath); + GetTempFileName(tempPath, L"wfv", 0, tempFile); + + writeCursor=CreateFile(tempFile, GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0); + processedCursor=CreateFile(tempFile, GENERIC_READ, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); + readCursor=CreateFile(tempFile, GENERIC_READ, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); +} + +ProgressiveProcessor::~ProgressiveProcessor() +{ + if (writeCursor != INVALID_HANDLE_VALUE) + CloseHandle(writeCursor); + + if (tempFile[0]) + DeleteFile(tempFile); +} + +int ProgressiveProcessor::Write(void *data, size_t datalen, size_t *written) +{ + DWORD dw_written=0; + WriteFile(writeCursor, data, (DWORD)datalen, &dw_written, NULL); + *written=dw_written; + writePosition+=dw_written; + + return 0; +} diff --git a/Src/Plugins/Input/in_flv/ProgressiveProcessor.h b/Src/Plugins/Input/in_flv/ProgressiveProcessor.h new file mode 100644 index 00000000..92102771 --- /dev/null +++ b/Src/Plugins/Input/in_flv/ProgressiveProcessor.h @@ -0,0 +1,16 @@ +#pragma once +#include "FileProcessor.h" + +class ProgressiveProcessor : public FileProcessor +{ +public: + ProgressiveProcessor(); + ~ProgressiveProcessor(); + +private: + /* FLVProcessor virtual method overrides */ + int Write(void *data, size_t datalen, size_t *written); +private: + HANDLE writeCursor; + wchar_t tempFile[MAX_PATH]; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/StreamProcessor.cpp b/Src/Plugins/Input/in_flv/StreamProcessor.cpp new file mode 100644 index 00000000..d5331cdc --- /dev/null +++ b/Src/Plugins/Input/in_flv/StreamProcessor.cpp @@ -0,0 +1,112 @@ +#include "StreamProcessor.h" +#include "FLVHeader.h" + +StreamProcessor::StreamProcessor() +{ + buffer.reserve(1024*1024); + bytesWritten=0; + readHeader=false; +} + +int StreamProcessor::Write(void *data, size_t datalen, size_t *written) +{ + *written = buffer.write(data, datalen); + bytesWritten += *written; + if (*written != datalen) + return FLVPROCESSOR_WRITE_WAIT; // tell the FLVReader we need a break :) + return FLVPROCESSOR_WRITE_OK; +} + +int StreamProcessor::Process() +{ + // we're actually not going to parse anything until the GetFrame() call. + // since we can't seek anyway + // but we do need to validate that this is an FLV stream + + if (!readHeader) + { + if (buffer.size() >= 9) // see if we have enough data + { + uint8_t data[9] = {0}; + buffer.read(data, 9); + + if (header.Read(data, 9)) + { + readHeader=true; + return FLV_OK; + } + else // not an FLV header + { + return FLV_ERROR; + } + } + } + + return FLV_NEED_MORE_DATA; +} + +uint64_t StreamProcessor::GetProcessedPosition() +{ + // since we parse the bitstream on-demand, we'll just return how many bytes we've buffered so far + if (readHeader) // make sure we've at least found the main FLV header + return bytesWritten; + else + return 0; +} + +uint32_t StreamProcessor::GetMaxTimestamp() +{ + return -1000; // it's a stream! no length! +} + +uint64_t StreamProcessor::Seek(uint64_t position) +{ + // we can't really seek in a traditional sense, but since Seek gets called to simply advance the read pointer, + // we'll advance within our buffer + // we always set FrameData::location to 0, so can just call like this + return buffer.advance((size_t)position); +} + +size_t StreamProcessor::Read(void *data, size_t bytes) +{ + // easy :) + return buffer.read(data, bytes); +} + +// the fun happens here +bool StreamProcessor::GetFrame(size_t frameIndex, FrameData &frameData) +{ + // since this is a stream, we're going to ignore frameIndex and just give them the next frame + + if (buffer.size() >= 15) + { + uint8_t data[15] = {0}; + buffer.peek(data, 15); + if (frameData.header.Read(data, 15)) + { + // first, make sure we have enough data buffered to read the whole thing + // because the next thing to get called after this function is Read() + if (frameData.header.dataSize + 15 <= buffer.size()) + { + // since we're streaming and only processing one frame at a time + // we're going to set the returned frame's location to 0 (start of the ring buffer) + frameData.location = 0; + return true; + } + } + } + return false; // not ready for a frame yet +} + +bool StreamProcessor::GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame) +{ + return false; // can't seek! +} + +FLVHeader *StreamProcessor::GetHeader() +{ +if (readHeader) +return &header; +else +return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/StreamProcessor.h b/Src/Plugins/Input/in_flv/StreamProcessor.h new file mode 100644 index 00000000..cd66ccdd --- /dev/null +++ b/Src/Plugins/Input/in_flv/StreamProcessor.h @@ -0,0 +1,28 @@ +#pragma once + +#include "FLVProcessor.h" +#include "../nu/RingBuffer.h" + +class StreamProcessor : public FLVProcessor +{ +public: + StreamProcessor(); + +private: + int Write(void *data, size_t datalen, size_t *written); + int Process(); + uint64_t Seek(uint64_t position); + size_t Read(void *data, size_t bytes); + uint64_t GetProcessedPosition(); + bool GetFrame(size_t frameIndex, FrameData &frameData); + uint32_t GetMaxTimestamp(); + bool GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame); + bool IsStreaming() { return true; } + FLVHeader *GetHeader(); +private: + RingBuffer buffer; + uint64_t bytesWritten; + bool readHeader; + FLVHeader header; +}; + diff --git a/Src/Plugins/Input/in_flv/VideoThread.cpp b/Src/Plugins/Input/in_flv/VideoThread.cpp new file mode 100644 index 00000000..e07d58d0 --- /dev/null +++ b/Src/Plugins/Input/in_flv/VideoThread.cpp @@ -0,0 +1,291 @@ +#include "main.h" +#include "VideoThread.h" +#include "api__in_flv.h" +#include "FLVVideoHeader.h" +#include <shlwapi.h> +#include <windows.h> +#include "../nu/threadname.h" +#include <api/service/waservicefactory.h> +#include "../nu/AutoLock.h" +#include "../nu/SampleQueue.h" + +int width, height; +IVideoOutput *videoOutput=0; +static HANDLE videoThread=0; +static volatile LONG video_flush=0; +static ifc_flvvideodecoder *videoDecoder=0; +bool video_opened=false; +static HANDLE coded_frames_event=0; +static HANDLE video_flush_event=0; +static Nullsoft::Utility::LockGuard coded_frames_guard; +extern bool video_only; + +void Video_Init() +{ + video_opened=false; + videoDecoder=0; + videoThread=0; + width=0; + height=0; + + if (coded_frames_event == 0) + coded_frames_event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (video_flush_event == 0) + video_flush_event = CreateEvent(NULL, TRUE, FALSE, NULL); + video_flush=0; +} + +struct FRAMEDATA +{ + FRAMEDATA() + { + data=0; + length=0; + timestamp=0; + } + + ~FRAMEDATA() + { + free(data); + } + void Reset() + { + free(data); + data=0; + length=0; + timestamp=0; + } + void Set(void *_data, size_t _length, uint32_t ts) + { + data = _data; + length = _length; + timestamp = ts; + } + void *data; + size_t length; + uint32_t timestamp; +}; + +static SampleQueue<FRAMEDATA> coded_frames; + +extern int GetOutputTime(); +static void DecodeVideo(FRAMEDATA *framedata) +{ + if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0) + { + int decodeResult = videoDecoder->DecodeSample(framedata->data, framedata->length, framedata->timestamp); + + if (decodeResult == FLV_VIDEO_SUCCESS) + { + void *data, *decoder_data; + uint64_t timestamp=framedata->timestamp; + while (videoDecoder->GetPicture(&data, &decoder_data, ×tamp) == FLV_VIDEO_SUCCESS) + { + if (!video_opened) + { + int color_format; + if (videoDecoder->GetOutputFormat(&width, &height, &color_format) == FLV_VIDEO_SUCCESS) + { + videoOutput->extended(VIDUSER_SET_THREAD_SAFE, 1, 0); + videoOutput->open(width, height, 0, 1.0, color_format); + video_opened=true; + } + } + if (video_opened) + { +again: + int realTime =(int)GetOutputTime(); + if (timestamp > (realTime+5)) + { + HANDLE handles[] = {killswitch, video_flush_event}; + int ret=WaitForMultipleObjects(2, handles, FALSE, (DWORD)(timestamp-realTime)); + if (ret != WAIT_TIMEOUT) + { + videoDecoder->FreePicture(data, decoder_data); + framedata->Reset(); + return ; + } + goto again; // TODO: handle paused state a little better than this + } + videoOutput->draw(data); + } + videoDecoder->FreePicture(data, decoder_data); + } + } + } + + framedata->Reset(); +} + +DWORD CALLBACK VideoProcedure(LPVOID param) +{ + SetThreadName(-1,"FLV_VideoProcedure"); + HANDLE wait_handles[] = { killswitch, video_flush_event, coded_frames_event}; + int ret; + do + { + ret = WaitForMultipleObjects(3, wait_handles, FALSE, INFINITE); + if (ret == WAIT_OBJECT_0 + 1) + { + if (video_flush) + { + InterlockedDecrement(&video_flush); + if (videoDecoder) + videoDecoder->Flush(); + } + ResetEvent(video_flush_event); + } + else if (ret == WAIT_OBJECT_0 + 2) + { + FRAMEDATA *frame_data = 0; + while (frame_data = coded_frames.PopProcessed()) + { + DecodeVideo(frame_data); + frame_data->Reset(); + coded_frames.PushFree(frame_data); + } + } + } while (ret != WAIT_OBJECT_0); + + if (video_opened && videoOutput) + videoOutput->close(); + video_opened=false; + return 0; +} + +bool Video_IsSupported(int type) +{ + size_t n = 0; + waServiceFactory *factory = NULL; + while (factory = plugin.service->service_enumService(WaSvc::FLVDECODER, n++)) + { + svc_flvdecoder *creator = (svc_flvdecoder *)factory->getInterface(); + if (creator) + { + int supported = creator->HandlesVideo(type); + factory->releaseInterface(creator); + if (supported == svc_flvdecoder::CREATEDECODER_SUCCESS) + return true; + } + } + return false; +} + +static ifc_flvvideodecoder *CreateVideoDecoder(int type) +{ + ifc_flvvideodecoder *video_decoder = 0; + size_t n = 0; + waServiceFactory *factory = NULL; + while (factory = plugin.service->service_enumService(WaSvc::FLVDECODER, n++)) + { + svc_flvdecoder *creator = (svc_flvdecoder *)factory->getInterface(); + if (creator) + { + if (creator->CreateVideoDecoder(type, width, height, &video_decoder) == FLV_VIDEO_SUCCESS) + return video_decoder; + + factory->releaseInterface(creator); + } + } + return 0; +} +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = +{ 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } }; +bool OnVideo(void *data, size_t length, int type, unsigned __int32 timestamp) +{ + if (!videoDecoder) + { + videoDecoder = CreateVideoDecoder(type); + } + + if (videoDecoder) + { + if (!video_only && !videoThread) + { + DWORD threadId; + videoThread = CreateThread(0, 0, VideoProcedure, 0, 0, &threadId); + SetThreadPriority(videoThread, (INT)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + } + + FRAMEDATA *new_frame = coded_frames.PopFree(); + if (new_frame) + { + new_frame->Set(data, length, timestamp); + if (video_only) + { + DecodeVideo(new_frame); + new_frame->Reset(); + coded_frames.PushFree(new_frame); + } + else + { + coded_frames.PushProcessed(new_frame); + SetEvent(coded_frames_event); + } + } + + return true; + } + + return false; +} + +void Video_Stop() +{ + if (video_only) + { + ResetEvent(coded_frames_event); + Nullsoft::Utility::AutoLock l(coded_frames_guard); + coded_frames.Trim(); + if (video_opened && videoOutput) + videoOutput->close(); + video_opened=false; + } + else + { + if (videoThread) + WaitForSingleObject(videoThread, INFINITE); + videoThread=0; + + InterlockedIncrement(&video_flush); + ResetEvent(coded_frames_event); + Nullsoft::Utility::AutoLock l(coded_frames_guard); + coded_frames.Trim(); + } +} + +void Video_Close() +{ + video_opened=false; + + if (videoDecoder) + { + videoDecoder->Close(); + videoDecoder=0; + } +} + +void VideoFlush() +{ + if (video_only) + { + if (videoDecoder) + videoDecoder->Flush(); + } + else if (videoThread) + { + InterlockedIncrement(&video_flush); + ResetEvent(coded_frames_event); + coded_frames.Trim(); + SetEvent(video_flush_event); + } +} + +bool Video_DecoderReady() +{ + if (!videoDecoder) + return true; + + return !!videoDecoder->Ready(); +} diff --git a/Src/Plugins/Input/in_flv/VideoThread.h b/Src/Plugins/Input/in_flv/VideoThread.h new file mode 100644 index 00000000..3d2d7487 --- /dev/null +++ b/Src/Plugins/Input/in_flv/VideoThread.h @@ -0,0 +1,21 @@ +#ifndef NULLSOFT_IN_FLV_VIDEOTHREAD_H +#define NULLSOFT_IN_FLV_VIDEOTHREAD_H + +#include "../nsv/dec_if.h" +#include "../Winamp/wa_ipc.h" +#include "svc_flvdecoder.h" +#include "ifc_flvvideodecoder.h" + +bool OnVideo(void *data, size_t length, int type, unsigned __int32 timestamp); +void VideoFlush(); +extern int width, height; +extern IVideoOutput *videoOutput; +extern bool video_opened; + +void Video_Init(); +void Video_Stop(); +void Video_Close(); +bool Video_IsSupported(int type); +bool Video_DecoderReady(); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/api__in_flv.h b/Src/Plugins/Input/in_flv/api__in_flv.h new file mode 100644 index 00000000..911b9eb2 --- /dev/null +++ b/Src/Plugins/Input/in_flv/api__in_flv.h @@ -0,0 +1,12 @@ +#ifndef NULLSOFT_IN_FLV_API_H +#define NULLSOFT_IN_FLV_API_H + +#include "../Agave/Config/api_config.h" +extern api_config *AGAVE_API_CONFIG; + +#include <api/application/api_application.h> +#define WASABI_API_APP applicationApi + +#include "../Agave/Language/api_language.h" + +#endif // !NULLSOFT_IN_FLV_API_H diff --git a/Src/Plugins/Input/in_flv/ifc_flvaudiodecoder.h b/Src/Plugins/Input/in_flv/ifc_flvaudiodecoder.h new file mode 100644 index 00000000..66ae264b --- /dev/null +++ b/Src/Plugins/Input/in_flv/ifc_flvaudiodecoder.h @@ -0,0 +1,62 @@ +#pragma once + +enum +{ + FLV_AUDIO_SUCCESS = 0, + FLV_AUDIO_FAILURE = 1, + FLV_AUDIO_NEEDS_MORE_INPUT = 2, +}; + +class ifc_flvaudiodecoder : public Dispatchable +{ +protected: + ifc_flvaudiodecoder() {} + ~ifc_flvaudiodecoder() {} +public: + int GetOutputFormat(unsigned int *sample_rate, unsigned int *channels, unsigned int *bits); + int DecodeSample(const void *input_buffer, size_t input_buffer_bytes, void *samples, size_t *samples_size_bytes, double *bitrate); + void Flush(); + void Close(); + int Ready(); // returns 1 for ready [default], 0 for not ready. Some codecs in FLV use the first packet for decoder config data. return 1 from this once you've gotten it + void SetPreferences(unsigned int max_channels, unsigned int preferred_bits); + DISPATCH_CODES + { + FLV_AUDIO_GETOUTPUTFORMAT = 0, + FLV_AUDIO_DECODE = 1, + FLV_AUDIO_FLUSH = 2, + FLV_AUDIO_CLOSE = 3, + FLV_AUDIO_READY = 4, + FLV_AUDIO_SETPREFERENCES=5, + }; +}; + +inline int ifc_flvaudiodecoder::GetOutputFormat(unsigned int *sample_rate, unsigned int *channels, unsigned int *bits) +{ + return _call(FLV_AUDIO_GETOUTPUTFORMAT, (int)FLV_AUDIO_FAILURE, sample_rate, channels, bits); +} + +inline int ifc_flvaudiodecoder::DecodeSample(const void *input_buffer, size_t input_buffer_bytes, void *samples, size_t *samples_size_bytes, double *bitrate) +{ + return _call(FLV_AUDIO_DECODE, (int)FLV_AUDIO_FAILURE, input_buffer, input_buffer_bytes, samples, samples_size_bytes, bitrate); +} + +inline void ifc_flvaudiodecoder::Flush() +{ + _voidcall(FLV_AUDIO_FLUSH); +} + +inline void ifc_flvaudiodecoder::Close() +{ + _voidcall(FLV_AUDIO_CLOSE); +} + +inline int ifc_flvaudiodecoder::Ready() +{ + return _call(FLV_AUDIO_READY, (int)1); // default to true so that decoders that don't implement won't block in_flv from seeking +} + +inline void ifc_flvaudiodecoder::SetPreferences(unsigned int max_channels, unsigned int preferred_bits) +{ + _voidcall(FLV_AUDIO_SETPREFERENCES, max_channels, preferred_bits); +} + diff --git a/Src/Plugins/Input/in_flv/ifc_flvvideodecoder.h b/Src/Plugins/Input/in_flv/ifc_flvvideodecoder.h new file mode 100644 index 00000000..1ee09833 --- /dev/null +++ b/Src/Plugins/Input/in_flv/ifc_flvvideodecoder.h @@ -0,0 +1,67 @@ +#pragma once + +enum +{ + FLV_VIDEO_SUCCESS = 0, + FLV_VIDEO_FAILURE = 1, +}; + +class ifc_flvvideodecoder : public Dispatchable +{ +protected: + ifc_flvvideodecoder() {} + ~ifc_flvvideodecoder() {} +public: + int GetOutputFormat(int *x, int *y, int *color_format); + int DecodeSample(const void *inputBuffer, size_t inputBufferBytes, int32_t timestamp); + void Flush(); + void Close(); + int GetPicture(void **data, void **decoder_data, uint64_t *timestamp); + void FreePicture(void *data, void *decoder_data); + int Ready(); // returns 1 for ready [default], 0 for not ready. Some codecs in FLV use the first packet for decoder config data. return 1 from this once you've gotten it + DISPATCH_CODES + { + FLV_VIDEO_GETOUTPUTFORMAT = 0, + FLV_VIDEO_DECODE = 1, + FLV_VIDEO_FLUSH = 2, + FLV_VIDEO_CLOSE = 3, + FLV_VIDEO_GET_PICTURE = 4, + FLV_VIDEO_FREE_PICTURE = 5, + FLV_VIDEO_READY = 6, + }; +}; + +inline int ifc_flvvideodecoder::GetOutputFormat(int *x, int *y, int *color_format) +{ + return _call(FLV_VIDEO_GETOUTPUTFORMAT, (int)FLV_VIDEO_FAILURE, x, y, color_format); +} + +inline int ifc_flvvideodecoder::DecodeSample(const void *inputBuffer, size_t inputBufferBytes, int32_t timestamp) +{ + return _call(FLV_VIDEO_DECODE, (int)FLV_VIDEO_FAILURE, inputBuffer, inputBufferBytes, timestamp); +} + +inline void ifc_flvvideodecoder::Flush() +{ + _voidcall(FLV_VIDEO_FLUSH); +} + +inline void ifc_flvvideodecoder::Close() +{ + _voidcall(FLV_VIDEO_CLOSE); +} + +inline int ifc_flvvideodecoder::GetPicture(void **data, void **decoder_data, uint64_t *timestamp) +{ + return _call(FLV_VIDEO_GET_PICTURE, (int)FLV_VIDEO_FAILURE, data, decoder_data, timestamp); +} + +inline void ifc_flvvideodecoder::FreePicture(void *data, void *decoder_data) +{ + _voidcall(FLV_VIDEO_FREE_PICTURE, data, decoder_data); +} + +inline int ifc_flvvideodecoder::Ready() +{ + return _call(FLV_VIDEO_READY, (int)1); // default to true so that decoders that don't implement won't block in_flv from seeking +} diff --git a/Src/Plugins/Input/in_flv/in_flv.rc b/Src/Plugins/Input/in_flv/in_flv.rc new file mode 100644 index 00000000..61854c33 --- /dev/null +++ b/Src/Plugins/Input/in_flv/in_flv.rc @@ -0,0 +1,85 @@ +// 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 + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_FLV "Nullsoft Flash Video Decoder v%s" + 65535 "{EC959D43-9122-4807-B928-7B46207AFA49}" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_FLV_OLD "Nullsoft Flash Video Decoder" + IDS_BUFFERING "Buffering" + IDS_FLASH_VIDEO "Flash Video" + IDS_VP6_FLASH_VIDEO "VP6 Flash Video" + IDS_FAMILY_STRING "Flash Video" + IDS_ABOUT_TEXT "%s\n© 2006-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_flv/in_flv.sln b/Src/Plugins/Input/in_flv/in_flv.sln new file mode 100644 index 00000000..7bdd0028 --- /dev/null +++ b/Src/Plugins/Input/in_flv/in_flv.sln @@ -0,0 +1,30 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29609.76 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_flv", "in_flv.vcxproj", "{405DF558-062D-43AB-B7FB-02C4CF8B28CE}" +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 + {405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Debug|Win32.ActiveCfg = Debug|Win32 + {405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Debug|Win32.Build.0 = Debug|Win32 + {405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Debug|x64.ActiveCfg = Debug|x64 + {405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Debug|x64.Build.0 = Debug|x64 + {405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Release|Win32.ActiveCfg = Release|Win32 + {405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Release|Win32.Build.0 = Release|Win32 + {405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Release|x64.ActiveCfg = Release|x64 + {405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B6180127-9081-4796-819C-0E19EBFB2BC4} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_flv/in_flv.vcxproj b/Src/Plugins/Input/in_flv/in_flv.vcxproj new file mode 100644 index 00000000..cd373c26 --- /dev/null +++ b/Src/Plugins/Input/in_flv/in_flv.vcxproj @@ -0,0 +1,295 @@ +<?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>{405DF558-062D-43AB-B7FB-02C4CF8B28CE}</ProjectGuid> + <RootNamespace>in_flv</RootNamespace> + <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" 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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|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> + <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_FLV_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <SmallerTypeCheck>false</SmallerTypeCheck> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <FunctionLevelLinking>true</FunctionLevelLinking> + <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> + <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\ +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_FLV_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <SmallerTypeCheck>false</SmallerTypeCheck> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <FunctionLevelLinking>true</FunctionLevelLinking> + <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> + <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\ +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> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;IN_FLV_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4244;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <SmallerTypeCheck>false</SmallerTypeCheck> + <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> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;IN_FLV_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4244;%(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="..\..\..\nu\RingBuffer.cpp" /> + <ClCompile Include="..\..\..\nu\SpillBuffer.cpp" /> + <ClCompile Include="AMFDispatch.cpp" /> + <ClCompile Include="AMFObject.cpp" /> + <ClCompile Include="BackgroundDownloader.cpp" /> + <ClCompile Include="ExtendedInfo.cpp" /> + <ClCompile Include="FileProcessor.cpp" /> + <ClCompile Include="FLVAudioHeader.cpp" /> + <ClCompile Include="FLVCOM.cpp" /> + <ClCompile Include="FLVHeader.cpp" /> + <ClCompile Include="FLVMetadata.cpp" /> + <ClCompile Include="FLVProcessor.cpp" /> + <ClCompile Include="FLVReader.cpp" /> + <ClCompile Include="FLVStreamHeader.cpp" /> + <ClCompile Include="FLVVideoHeader.cpp" /> + <ClCompile Include="main.cpp" /> + <ClCompile Include="PlayThread.cpp" /> + <ClCompile Include="ProgressiveProcessor.cpp" /> + <ClCompile Include="StreamProcessor.cpp" /> + <ClCompile Include="VideoThread.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\nu\RingBuffer.h" /> + <ClInclude Include="..\..\..\nu\SpillBuffer.h" /> + <ClInclude Include="..\..\..\nu\VideoClock.h" /> + <ClInclude Include="AMFDispatch.h" /> + <ClInclude Include="AMFObject.h" /> + <ClInclude Include="api__in_flv.h" /> + <ClInclude Include="BackgroundDownloader.h" /> + <ClInclude Include="FileProcessor.h" /> + <ClInclude Include="FLVAudioHeader.h" /> + <ClInclude Include="FLVCOM.h" /> + <ClInclude Include="FLVHeader.h" /> + <ClInclude Include="FLVMetadata.h" /> + <ClInclude Include="FLVProcessor.h" /> + <ClInclude Include="FLVReader.h" /> + <ClInclude Include="FLVStreamHeader.h" /> + <ClInclude Include="FLVUtil.h" /> + <ClInclude Include="FLVVideoHeader.h" /> + <ClInclude Include="ifc_flvaudiodecoder.h" /> + <ClInclude Include="ifc_flvvideodecoder.h" /> + <ClInclude Include="main.h" /> + <ClInclude Include="ProgressiveProcessor.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="StreamProcessor.h" /> + <ClInclude Include="svc_flvdecoder.h" /> + <ClInclude Include="VideoThread.h" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_flv.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_flv/in_flv.vcxproj.filters b/Src/Plugins/Input/in_flv/in_flv.vcxproj.filters new file mode 100644 index 00000000..8e199126 --- /dev/null +++ b/Src/Plugins/Input/in_flv/in_flv.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="AMFDispatch.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="AMFObject.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="BackgroundDownloader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="FileProcessor.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="FLVAudioHeader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="FLVCOM.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="FLVHeader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="FLVMetadata.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="FLVProcessor.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="FLVReader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="FLVStreamHeader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="FLVVideoHeader.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="ProgressiveProcessor.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="StreamProcessor.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="VideoThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\RingBuffer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\SpillBuffer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="VideoThread.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="svc_flvdecoder.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="StreamProcessor.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ProgressiveProcessor.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="main.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ifc_flvvideodecoder.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ifc_flvaudiodecoder.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FLVVideoHeader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FLVUtil.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FLVStreamHeader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FLVReader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FLVProcessor.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FLVMetadata.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FLVAudioHeader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FLVHeader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FLVCOM.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FileProcessor.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="BackgroundDownloader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="api__in_flv.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="AMFObject.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="AMFDispatch.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\RingBuffer.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>{9916d7e8-2b6f-493b-8618-234dec0cefeb}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{888ab1e4-8d93-40b5-92f8-35339ac6179a}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{0f72d524-22df-4752-8f22-5c1db90c62b0}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_flv.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/main.cpp b/Src/Plugins/Input/in_flv/main.cpp new file mode 100644 index 00000000..d51d0b2b --- /dev/null +++ b/Src/Plugins/Input/in_flv/main.cpp @@ -0,0 +1,432 @@ +#include "main.h" +#include <shlwapi.h> +#include "api__in_flv.h" +#include <stdio.h> +#include <api/service/waservicefactory.h> +#include "../Winamp/wa_ipc.h" +#include "resource.h" +#include "FileProcessor.h" +#include "FLVCOM.h" +#include "VideoThread.h" +#include "../f263/flv_f263_decoder.h" +#include <strsafe.h> + +#define FLV_PLUGIN_VERSION L"1.47" + +template <class api_T> +static 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> +static 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; +} + +/* Wasabi services */ +api_application *WASABI_API_APP=0; +api_config *AGAVE_API_CONFIG=0; +api_language *WASABI_API_LNG = 0; + +In_Module *swf_mod = 0; +HMODULE in_swf = 0; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; +wchar_t pluginName[256] = {0}; +wchar_t *playFile=0; +HANDLE killswitch, playthread=0; +int g_length=-1000; +bool video_only=false; +int paused = 0; +nu::VideoClock video_clock; +int m_need_seek=-1; +extern uint32_t last_timestamp; +int pan = 0, volume = -666; +wchar_t *stream_title=0; +Nullsoft::Utility::LockGuard stream_title_guard; + +// {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}; // "FLV\0Flash Video\0" + char* end = 0; + size_t remaining; + StringCchCopyExA(fileExtensionsString, 256, "FLV", &end, &remaining, 0); + StringCchCopyExA(end+1, remaining-1, WASABI_API_LNGSTRING(IDS_FLASH_VIDEO), 0, 0, 0); + plugin.FileExtensions = fileExtensionsString; +} + +int Init() +{ + if (!IsWindow(plugin.hMainWindow)) + return IN_INIT_FAILURE; + + ServiceBuild(AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceBuild(WASABI_API_APP, applicationApiServiceGuid); + 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,InFlvLangGUID); + + if (plugin.service->service_getServiceByGuid(flv_h263_guid)) + StringCchPrintfW(pluginName,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_FLV),FLV_PLUGIN_VERSION L" (h)"); + else + StringCchPrintfW(pluginName,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_FLV),FLV_PLUGIN_VERSION); + plugin.description = (char*)pluginName; + SetFileExtensions(); + + DispatchInfo flvDisp = { L"FLV", &flvCOM }; + SendMessage(plugin.hMainWindow, WM_WA_IPC, (WPARAM)&flvDisp, IPC_ADD_DISPATCH_OBJECT); + + killswitch = CreateEvent(NULL, TRUE, FALSE, NULL); + return IN_INIT_SUCCESS; +} + +void Quit() +{ + CloseHandle(killswitch); + ServiceRelease(AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceRelease(WASABI_API_APP, applicationApiServiceGuid); + ServiceRelease(WASABI_API_LNG, languageApiGUID); + if (in_swf) + FreeLibrary(in_swf); +} + +void GetFileInfo(const wchar_t *file, wchar_t *title, int *length_in_ms) +{ + if (!file || !*file) + { + if (swf_mod) + { + swf_mod->GetFileInfo(file, title, length_in_ms); + return; + } + + file = playFile; + } + + // no title support as it's not always stored in a standard way in FLV + if (title) + { + Nullsoft::Utility::AutoLock stream_lock(stream_title_guard); + if (stream_title && (!file || !file[0])) + { + lstrcpyn(title, stream_title, GETFILEINFO_TITLE_LENGTH); + } + else + { + lstrcpyn(title, file ? file : L"", GETFILEINFO_TITLE_LENGTH); + PathStripPath(title); + } + } + + // calculate length + if (file == playFile) // currently playing song? + *length_in_ms = g_length; // easy! + else if (PathIsURLW(file)) // don't calculate lengths for URLs since we'd have to connect + { + *length_in_ms = -1; + } + else + { + // open the file and find the "duration" metadata + FileProcessor processor(file); + + size_t frameIndex=0; + FrameData frameData; + int length=-1; + bool found=false; + do + { + // enumerate the frames as we process + // this function will fail the first few times + // because Process() hasn't been called enough + if (processor.GetFrame(frameIndex, frameData)) + { + if (frameData.header.type == FLV::FRAME_TYPE_METADATA) + { + if (processor.Seek(frameData.location + 15) == -1) + break; + + size_t dataSize = frameData.header.dataSize; + uint8_t *metadatadata= (uint8_t *)calloc(dataSize, sizeof(uint8_t)); + if (metadatadata) + { + size_t bytesRead = processor.Read(metadatadata, dataSize); + if (bytesRead != dataSize) + { + free(metadatadata); + break; + } + FLVMetadata metadata; + metadata.Read(metadatadata, dataSize); + + for ( FLVMetadata::Tag *tag : metadata.tags ) + { + if ( !_wcsicmp( tag->name.str, L"onMetaData" ) ) + { + AMFType *duration = tag->parameters->array[ L"duration" ]; + if ( duration ) + { + length = (int)( AMFGetDouble( duration ) * 1000.0 ); + found = true; + } + } + } + free(metadatadata); + } + else + break; + } + frameIndex++; + } + } + while (!found && processor.Process() == FLV_OK); // for local files, any return value other than FLV_OK is a failure + *length_in_ms = length; + } +} + +int InfoBox(const wchar_t *file, HWND hwndParent) +{ + return INFOBOX_UNCHANGED; +} + +int IsOurFile(const wchar_t *file) +{ + return 0; +} + +int Play(const wchar_t *file) +{ + { + Nullsoft::Utility::AutoLock stream_lock(stream_title_guard); + free(stream_title); + stream_title=0; + } + if (!videoOutput) + videoOutput = (IVideoOutput *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GET_IVIDEOOUTPUT); + + video_only=false; + m_need_seek = -1; + video_clock.Reset(); + paused=0; + ResetEvent(killswitch); + free(playFile); + playFile = _wcsdup(file); + playthread=CreateThread(0, 0, PlayProcedure, 0, 0, 0); + SetThreadPriority(playthread, (INT)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + return 0; // success +} + + +void Pause() +{ + paused = 1; + if (swf_mod) + { + swf_mod->Pause(); + } + else if (video_only) + { + video_clock.Pause(); + } + else + { + plugin.outMod->Pause(1); + } +} + +void UnPause() +{ + paused = 0; + if (swf_mod) + { + swf_mod->UnPause(); + } + else if (video_only) + { + video_clock.Unpause(); + } + else + { + plugin.outMod->Pause(0); + } + +} + +int IsPaused() +{ + if (swf_mod) + return swf_mod->IsPaused(); + + return paused; +} + +void Stop() +{ + if (swf_mod) + { + swf_mod->Stop(); + swf_mod=0; + return; + } + SetEvent(killswitch); + WaitForSingleObject(playthread, INFINITE); + playthread=0; + plugin.outMod->Close(); + plugin.SAVSADeInit(); + paused=0; +} + +int GetLength() +{ + if (swf_mod) + { + return swf_mod->GetLength(); + } + return g_length; +} + +int GetOutputTime() +{ + if (swf_mod) + { + return swf_mod->GetOutputTime(); + } + else if (video_only) + { + return video_clock.GetOutputTime(); + } + else if (plugin.outMod) + { + return plugin.outMod->GetOutputTime(); + } + else + return 0; +} + +void SetOutputTime(int time_in_ms) +{ + if (swf_mod) + { + swf_mod->SetOutputTime(time_in_ms); + return ; + } + m_need_seek=time_in_ms; +} + + +void SetVolume(int _volume) +{ + if (swf_mod) + { + swf_mod->SetVolume(_volume); + return ; + } + volume = _volume; + if (plugin.outMod) + plugin.outMod->SetVolume(volume); +} + +void SetPan(int _pan) +{ + if (swf_mod) + { + swf_mod->SetPan(_pan); + return ; + } + pan = _pan; + if (plugin.outMod) + plugin.outMod->SetPan(pan); +} + +void EQSet(int on, char data[10], int preamp) +{ + if (swf_mod) + { + swf_mod->EQSet(on, data, preamp); + return; + } +} + +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_FLV_OLD,text,1024); + StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), + plugin.description, TEXT(__DATE__)); + DoAboutMessageBox(hwndParent,text,message); +} + +In_Module plugin = +{ + IN_VER_RET, + "nullsoft(in_flv.dll)", + 0, + 0, + 0 /*"FLV\0Flash Video\0"*/, + 1, // not seekable, for now + 1, + 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_flv/main.h b/Src/Plugins/Input/in_flv/main.h new file mode 100644 index 00000000..ba3f4376 --- /dev/null +++ b/Src/Plugins/Input/in_flv/main.h @@ -0,0 +1,27 @@ +#ifndef NULLSOFT_IN_FLV_MAIN_H +#define NULLSOFT_IN_FLV_MAIN_H + +#include <windows.h> +#include "../Winamp/in2.h" +#include "../nu/VideoClock.h" +#include "../nu/AutoLock.h" + +/* main.cpp */ +extern In_Module plugin; +extern In_Module *swf_mod; +extern HMODULE in_swf; +extern wchar_t *playFile; +extern HANDLE killswitch; +extern int m_need_seek; +extern int paused; +extern int g_length; +extern nu::VideoClock video_clock; +extern wchar_t *stream_title; +extern Nullsoft::Utility::LockGuard stream_title_guard; + +/* PlayThread.cpp */ + +DWORD CALLBACK PlayProcedure(LPVOID param); + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/resource.h b/Src/Plugins/Input/in_flv/resource.h new file mode 100644 index 00000000..22a49a42 --- /dev/null +++ b/Src/Plugins/Input/in_flv/resource.h @@ -0,0 +1,23 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by in_flv.rc +// +#define IDS_NULLSOFT_FLV_OLD 0 +#define IDS_BUFFERING 2 +#define IDS_FLASH_VIDEO 3 +#define IDS_STRING4 4 +#define IDS_VP6_FLASH_VIDEO 4 +#define IDS_FAMILY_STRING 5 +#define IDS_ABOUT_TEXT 6 +#define IDS_NULLSOFT_FLV 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 105 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_flv/svc_flvdecoder.h b/Src/Plugins/Input/in_flv/svc_flvdecoder.h new file mode 100644 index 00000000..1093d18a --- /dev/null +++ b/Src/Plugins/Input/in_flv/svc_flvdecoder.h @@ -0,0 +1,52 @@ +#pragma once + +#include <bfc/dispatch.h> +#include <api/service/services.h> + +class ifc_flvaudiodecoder; +class ifc_flvvideodecoder; +class svc_flvdecoder : public Dispatchable +{ +protected: + svc_flvdecoder() {} + ~svc_flvdecoder() {} +public: + static FOURCC getServiceType() { return WaSvc::FLVDECODER; } + enum + { + CREATEDECODER_SUCCESS = 0, + CREATEDECODER_NOT_MINE = -1, // graceful failure + CREATEDECODER_FAILURE = 1, // generic failure - format_type is ours but we weren't able to create the decoder + }; + int CreateAudioDecoder(int stereo, int bits, int sample_rate, int format, ifc_flvaudiodecoder **decoder); + int CreateVideoDecoder(int format_type, int width, int height, ifc_flvvideodecoder **decoder); + int HandlesAudio(int format_type); + int HandlesVideo(int format_type); + DISPATCH_CODES + { + CREATE_AUDIO_DECODER = 0, + CREATE_VIDEO_DECODER = 1, + HANDLES_AUDIO=2, + HANDLES_VIDEO=3, + }; +}; + +inline int svc_flvdecoder::CreateAudioDecoder(int stereo, int bits, int sample_rate, int format, ifc_flvaudiodecoder **decoder) +{ + return _call(CREATE_AUDIO_DECODER, (int)CREATEDECODER_NOT_MINE, stereo, bits, sample_rate, format, decoder); +} + +inline int svc_flvdecoder::CreateVideoDecoder(int format_type, int width, int height, ifc_flvvideodecoder **decoder) +{ + return _call(CREATE_VIDEO_DECODER, (int)CREATEDECODER_NOT_MINE, format_type,width, height, decoder); +} + +inline int svc_flvdecoder::HandlesAudio(int format_type) +{ + return _call(HANDLES_AUDIO, (int)CREATEDECODER_NOT_MINE, format_type); +} + +inline int svc_flvdecoder::HandlesVideo(int format_type) +{ + return _call(HANDLES_VIDEO, (int)CREATEDECODER_NOT_MINE, format_type); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/version.rc2 b/Src/Plugins/Input/in_flv/version.rc2 new file mode 100644 index 00000000..871c3650 --- /dev/null +++ b/Src/Plugins/Input/in_flv/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,47,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", "1,47,0,0" + VALUE "InternalName", "Nullsoft Flash Video Decoder" + VALUE "LegalCopyright", "Copyright © 2006-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_flv.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_linein/LineIn.cpp b/Src/Plugins/Input/in_linein/LineIn.cpp new file mode 100644 index 00000000..073761af --- /dev/null +++ b/Src/Plugins/Input/in_linein/LineIn.cpp @@ -0,0 +1,50 @@ +#include "LineIn.h" +#include "main.h" +#include "audio.h" +int LineIn::Play() +{ + paused = false; + posinms = 0; + line.is_seekable = 0; + line.SetInfo(44*4*8, 44, 2, 1); + line.SAVSAInit(0, 44100); + line.VSASetInfo(2, 44100); + { + short dta[576*2] = {0, }; + line.VSAAddPCMData(dta, 2, 16, 0); + line.SAAddPCMData(dta, 2, 16, 0); + } + if (audioInit(1)) + {} + return 0; +} + +void LineIn::Stop() +{ + audioQuit(); +} + +void LineIn::Pause() +{ + posinms = audioGetPos(); + audioPause(1); + paused = true; +} + +void LineIn::Unpause() +{ + audioPause(0); + paused = false; +} + +int LineIn::GetLength() +{ + return -1000; +} + +int LineIn::GetOutputTime() +{ + if (paused) + return posinms; + return audioGetPos(); +} diff --git a/Src/Plugins/Input/in_linein/LineIn.h b/Src/Plugins/Input/in_linein/LineIn.h new file mode 100644 index 00000000..febf7934 --- /dev/null +++ b/Src/Plugins/Input/in_linein/LineIn.h @@ -0,0 +1,25 @@ +#ifndef NULLSOFT_LINEINH +#define NULLSOFT_LINEINH + +class LineIn +{ +public: + LineIn() : posinms(0), paused(false) + {} + int Play(); + void Stop(); + void Pause(); + void Unpause(); + int GetLength(); + int GetOutputTime(); + bool IsPaused() { return paused; } + void SetOutputTime(int time_in_ms) +{} + void SetVolume(int a_v, int a_p) + {} +private: + int posinms; + bool paused; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_linein/audio.cpp b/Src/Plugins/Input/in_linein/audio.cpp new file mode 100644 index 00000000..4ab2472c --- /dev/null +++ b/Src/Plugins/Input/in_linein/audio.cpp @@ -0,0 +1,113 @@ +#include <windows.h> +#include "Audio.h" +#include "main.h" +#pragma intrinsic(memcpy, memset) +static int start_clock,paused; +static HWAVEIN hWaveIn; +static unsigned short data_latest[576*2]; +static unsigned short data_buffers[2][576*2]; +static WAVEHDR wave_hdrs[2]; +static HANDLE hThread; +static int done; +static int Thread(int *a); +static int a_v=128, a_p=0; +int initted; + +int audioInit(int sample) +{ + int x; + WAVEFORMATEX wft; + int threadid; + start_clock=GetTickCount(); + paused=0; + if (!sample) return initted=0; + wft.wFormatTag = WAVE_FORMAT_PCM; + wft.nChannels = 2; + wft.nSamplesPerSec = 44100; + wft.nBlockAlign = 2*2; + wft.nAvgBytesPerSec = wft.nSamplesPerSec*wft.nBlockAlign; + wft.wBitsPerSample = 16; + wft.cbSize = 0; + if (waveInOpen(&hWaveIn,WAVE_MAPPER,&wft,0,0,0) != MMSYSERR_NOERROR) + { + return 1; + } + for (x = 0; x < 2; x ++) + { + memset(&wave_hdrs[x],0,sizeof(wave_hdrs[x])); + wave_hdrs[x].lpData = (char *) data_buffers[x]; + wave_hdrs[x].dwBufferLength = 576*2*sizeof(short); + waveInPrepareHeader(hWaveIn,&wave_hdrs[x],sizeof(wave_hdrs[0])); + waveInAddBuffer(hWaveIn,&wave_hdrs[x],sizeof(wave_hdrs[0])); + } + initted=1; + done=0; + waveInStart(hWaveIn); + hThread = CreateThread(NULL,0,(unsigned long (__stdcall *)(void*)) Thread,&done,0,(unsigned long *)&threadid); + //SetThreadPriority(hThread,THREAD_PRIORITY_HIGHEST); + return 0; +} + +void audioPause(int s) +{ + if (s) + { + if (!paused) + { + paused=1; + if (initted) waveInStop(hWaveIn); + start_clock = GetTickCount()-start_clock; + } + } + else + { + if (paused) + { + if (initted) waveInStart(hWaveIn); + start_clock=GetTickCount()-start_clock; + paused=0; + } + } +} + +void audioQuit() +{ + if (!initted) return; + done=1; + WaitForSingleObject(hThread,INFINITE); + waveInStop(hWaveIn); + waveInReset(hWaveIn); + while (waveInClose(hWaveIn) == WAVERR_STILLPLAYING) Sleep(10); + CloseHandle(hThread); +} + +int audioGetPos() +{ + if (paused) return start_clock; + return GetTickCount()-start_clock; +} + + +void audioSetPos(int ms) +{ + start_clock = GetTickCount()-ms; +} + +static int Thread(int *a) +{ + int w; + while (!*a) + { + Sleep(10); + if (wave_hdrs[0].dwFlags & WHDR_DONE) w=0; + else if (wave_hdrs[1].dwFlags & WHDR_DONE) w=1; + else continue; + memcpy(data_latest,wave_hdrs[w].lpData,576*2*sizeof(short)); + wave_hdrs[w].dwFlags=WHDR_PREPARED; + waveInAddBuffer(hWaveIn,&wave_hdrs[w],sizeof(wave_hdrs[0])); +// memset(data_latest,0,576*2*sizeof(short)); + line.VSAAddPCMData(data_latest,2,16,0); + line.SAAddPCMData(data_latest,2,16,0); + } + return 0; +} diff --git a/Src/Plugins/Input/in_linein/audio.h b/Src/Plugins/Input/in_linein/audio.h new file mode 100644 index 00000000..7c7213e6 --- /dev/null +++ b/Src/Plugins/Input/in_linein/audio.h @@ -0,0 +1,11 @@ +#ifndef NULLSOFT_AUDIOH +#define NULLSOFT_AUDIOH + +int audioInit(int); +int audioGetPos(); +void audioSetPos(int ms); +void audioGetWaveform(unsigned short data[576*2]); +void audioQuit(); +void audioPause(int s); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_linein/in_linein.cpp b/Src/Plugins/Input/in_linein/in_linein.cpp new file mode 100644 index 00000000..1b2eda2b --- /dev/null +++ b/Src/Plugins/Input/in_linein/in_linein.cpp @@ -0,0 +1,191 @@ +#define PLUGIN_NAME "Nullsoft LineIn Plug-in" +#define PLUGIN_VERSION L"3.16" + +#include "main.h" +#include <windows.h> +#include "LineIn.h" +#include <api/service/waServiceFactory.h> +#include "../Agave/Language/api_language.h" +#include "../winamp/wa_ipc.h" +#include "resource.h" + +// wasabi based services for localisation support +api_language *WASABI_API_LNG = 0; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; + +int lstrcmpni(const char *a, const char *b, int count) +{ + if (!a || !b) return -1; + + switch (CompareStringA(LOCALE_USER_DEFAULT, NORM_IGNORECASE, a, min(lstrlenA(a),count), b, min(lstrlenA(b),count))) + { + case CSTR_LESS_THAN: return -1; + case CSTR_GREATER_THAN: return 1; + case CSTR_EQUAL: return 0; + } + return -1; +} +LineIn lineIn; + +int DoAboutMessageBox(HWND parent, wchar_t* title, wchar_t* message) +{ + MSGBOXPARAMSW msgbx = {sizeof(MSGBOXPARAMSW),0}; + msgbx.lpszText = message; + msgbx.lpszCaption = title; + msgbx.lpszIcon = MAKEINTRESOURCEW(102); + msgbx.hInstance = GetModuleHandle(0); + msgbx.dwStyle = MB_USERICON; + msgbx.hwndOwner = parent; + return MessageBoxIndirectW(&msgbx); +} + +void about(HWND hwndParent) +{ + wchar_t message[1024] = {0}, text[1024] = {0}; + WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_LINEIN_PLUGIN_OLD,text,1024); + wsprintfW(message, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), + line.description, __DATE__); + DoAboutMessageBox(hwndParent,text,message); +} + +int init() +{ + if (!IsWindow(line.hMainWindow)) + return IN_INIT_FAILURE; + + waServiceFactory *sf = line.service->service_getServiceByGuid(languageApiGUID); + if (sf) WASABI_API_LNG = reinterpret_cast<api_language*>(sf->getInterface()); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(line.hDllInstance,InLineInLangGUID); + + static wchar_t szDescription[256]; + swprintf(szDescription,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_LINEIN_PLUGIN),PLUGIN_VERSION); + line.description = (char*)szDescription; + return IN_INIT_SUCCESS; +} + +void quit() {} + +int play(const char *fn) +{ + line.is_seekable=0; + if (!lstrcmpni(fn, "linein://", 9)) + { + lineIn.Play(); + return 0; + } + return 1; +} + +int isourfile(const char *fn) +{ + if (!lstrcmpni(fn, "linein://", 9)) return 1; + return 0; +} + +void pause() +{ + lineIn.Pause(); +} + +void unpause() +{ + lineIn.Unpause(); +} + +int ispaused() +{ + return lineIn.IsPaused(); +} + +void stop() +{ + lineIn.Stop(); + line.SAVSADeInit(); +} + +int getlength() +{ + return lineIn.GetLength(); +} + +int getoutputtime() +{ + return lineIn.GetOutputTime(); +} +void setoutputtime(int time_in_ms) +{} + +void setvolume(int volume) +{ + line.outMod->SetVolume(volume); +} + +void setpan(int pan) +{ + line.outMod->SetPan(pan); +} + +int infoDlg(const char *fn, HWND hwnd) +{ + return 0; +} + +void getfileinfo(const char *filename, char *title, int *length_in_ms) +{ + if (length_in_ms) + *length_in_ms = -1000; + if (title) + WASABI_API_LNGSTRING_BUF(IDS_LINE_INPUT,title,GETFILEINFO_TITLE_LENGTH); +} + +void eq_set(int on, char data[10], int preamp) +{} + +In_Module line = + { + IN_VER_RET, + "nullsoft(in_line.dll)", + 0, // hMainWindow + 0, // hDllInstance + "", + 0, // is_seekable + //0, // uses output plugins + 1, // uses output plugins + about, + about, + init, + quit, + getfileinfo, + infoDlg, + isourfile, + play, + pause, + unpause, + ispaused, + stop, + getlength, + getoutputtime, + setoutputtime, + setvolume, + setpan, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, // dsp shit + eq_set, + NULL, // setinfo + NULL // out_mod + }; + +BOOL APIENTRY DllMain(HANDLE hMod, DWORD r, void*) +{ + if (r == DLL_PROCESS_ATTACH) + DisableThreadLibraryCalls((HMODULE)hMod); + + return TRUE; +} + +extern "C" __declspec( dllexport ) In_Module * winampGetInModule2() +{ + return &line; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_linein/in_linein.rc b/Src/Plugins/Input/in_linein/in_linein.rc new file mode 100644 index 00000000..c9d5b3b1 --- /dev/null +++ b/Src/Plugins/Input/in_linein/in_linein.rc @@ -0,0 +1,82 @@ +// 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 + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_LINEIN_PLUGIN "Nullsoft LineIn Plug-in v%s" + 65535 "{EA1C197A-D227-474c-A9FD-1C79DE722BDD}" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_LINEIN_PLUGIN_OLD "Nullsoft LineIn Plug-in" + IDS_LINE_INPUT "Line Input" + IDS_ABOUT_TEXT "%s\nCopyright © 1997-2023 Winamp SA\n\nBuild date: %hs\n\nHow to use:\n For line input, open 'linein://'\n in the open location box." +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_linein/in_linein.sln b/Src/Plugins/Input/in_linein/in_linein.sln new file mode 100644 index 00000000..dd89a609 --- /dev/null +++ b/Src/Plugins/Input/in_linein/in_linein.sln @@ -0,0 +1,30 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29609.76 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_linein", "in_linein.vcxproj", "{0B5E2B59-BD34-4675-987E-6752A6F9D77D}" +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 + {0B5E2B59-BD34-4675-987E-6752A6F9D77D}.Debug|Win32.ActiveCfg = Debug|Win32 + {0B5E2B59-BD34-4675-987E-6752A6F9D77D}.Debug|Win32.Build.0 = Debug|Win32 + {0B5E2B59-BD34-4675-987E-6752A6F9D77D}.Debug|x64.ActiveCfg = Debug|x64 + {0B5E2B59-BD34-4675-987E-6752A6F9D77D}.Debug|x64.Build.0 = Debug|x64 + {0B5E2B59-BD34-4675-987E-6752A6F9D77D}.Release|Win32.ActiveCfg = Release|Win32 + {0B5E2B59-BD34-4675-987E-6752A6F9D77D}.Release|Win32.Build.0 = Release|Win32 + {0B5E2B59-BD34-4675-987E-6752A6F9D77D}.Release|x64.ActiveCfg = Release|x64 + {0B5E2B59-BD34-4675-987E-6752A6F9D77D}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {88B3FE0C-E6C6-43C1-9A78-25F6398E17D5} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_linein/in_linein.vcxproj b/Src/Plugins/Input/in_linein/in_linein.vcxproj new file mode 100644 index 00000000..c5df7304 --- /dev/null +++ b/Src/Plugins/Input/in_linein/in_linein.vcxproj @@ -0,0 +1,248 @@ +<?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>{0B5E2B59-BD34-4675-987E-6752A6F9D77D}</ProjectGuid> + <RootNamespace>in_linein</RootNamespace> + <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" 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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|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> + <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_LINEIN_EXPORTS;%(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>winmm.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <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\ +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_LINEIN_EXPORTS;%(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>winmm.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(ProjectName).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_LINEIN_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>winmm.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>winmm.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>true</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>WIN64;NDEBUG;_WINDOWS;_USRDLL;IN_LINEIN_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>winmm.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>winmm.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>true</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="audio.cpp" /> + <ClCompile Include="in_linein.cpp" /> + <ClCompile Include="LineIn.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="audio.h" /> + <ClInclude Include="LineIn.h" /> + <ClInclude Include="main.h" /> + <ClInclude Include="resource.h" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_linein.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_linein/in_linein.vcxproj.filters b/Src/Plugins/Input/in_linein/in_linein.vcxproj.filters new file mode 100644 index 00000000..04181cd8 --- /dev/null +++ b/Src/Plugins/Input/in_linein/in_linein.vcxproj.filters @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="audio.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="in_linein.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="LineIn.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="audio.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="LineIn.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="main.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{0d321d64-ec0a-44a6-a09d-35003a2ea058}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{7da33fde-69de-427a-8046-46e6f4846f14}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{012164b1-deee-45fb-a608-b2353dd37d5b}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_linein.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_linein/main.h b/Src/Plugins/Input/in_linein/main.h new file mode 100644 index 00000000..8e30eeec --- /dev/null +++ b/Src/Plugins/Input/in_linein/main.h @@ -0,0 +1,7 @@ +#ifndef NULLSOFT_MAINH +#define NULLSOFT_MAINH + +#include "../winamp/in2.h" +extern In_Module line; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_linein/resource.h b/Src/Plugins/Input/in_linein/resource.h new file mode 100644 index 00000000..c163119a --- /dev/null +++ b/Src/Plugins/Input/in_linein/resource.h @@ -0,0 +1,22 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by in_linein.rc +// +#define IDS_NULLSOFT_LINEIN_PLUGIN_OLD 0 +#define IDS_ABOUT_STR 1 +#define IDS_ABOUT 2 +#define IDS_STRING3 3 +#define IDS_LINE_INPUT 3 +#define IDS_ABOUT_TEXT 4 +#define IDS_NULLSOFT_LINEIN_PLUGIN 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_linein/version.rc2 b/Src/Plugins/Input/in_linein/version.rc2 new file mode 100644 index 00000000..0626529d --- /dev/null +++ b/Src/Plugins/Input/in_linein/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 3,16,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", "3,16,0,0" + VALUE "InternalName", "Nullsoft LineIn Plug-in" + VALUE "LegalCopyright", "Copyright © 1997-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_linein.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_midi/CompressionUtility.cpp b/Src/Plugins/Input/in_midi/CompressionUtility.cpp new file mode 100644 index 00000000..2da502d3 --- /dev/null +++ b/Src/Plugins/Input/in_midi/CompressionUtility.cpp @@ -0,0 +1,270 @@ +#include "CompressionUtility.h" +#include "malloc.h" +#include <cstring> +#include "zlib.h" +#include "minizip/unzip.h" + + +#define dir_delimter '/' +#define MAX_FILENAME 512 +#define READ_SIZE 8192 + +/// <summary> +/// Compress given buffer as GZIP. +/// Dont forget to free out buffer!!!! +/// </summary> +/// <param name="input"></param> +/// <param name="input_size"></param> +/// <param name="ppvOut"></param> +/// <param name="out_size"></param> +/// <returns></returns> +int CompressionUtility::CompressAsGZip(const void* input, size_t input_size, void** ppvOut, size_t& out_size) +{ + z_stream zlib_stream; + zlib_stream.next_in = (Bytef*)input; + zlib_stream.avail_in = (uInt)input_size; + zlib_stream.next_out = Z_NULL; + zlib_stream.avail_out = Z_NULL; + zlib_stream.zalloc = (alloc_func)0; + zlib_stream.zfree = (free_func)0; + zlib_stream.opaque = 0; + int ret = deflateInit2(&zlib_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (16 + MAX_WBITS), 8, Z_DEFAULT_STRATEGY); + + unsigned full_length = 2000; + unsigned half_length = input_size / 2; + + unsigned compLength = full_length; + unsigned char* comp = (unsigned char*)malloc(compLength); + + bool done = false; + + while (!done) + { + if (zlib_stream.total_out >= compLength) + { + // Increase size of output buffer + unsigned char* uncomp2 = (unsigned char*)malloc(compLength + half_length); + memcpy(uncomp2, comp, compLength); + compLength += half_length; + free(comp); + comp = uncomp2; + } + + zlib_stream.next_out = (Bytef*)(comp + zlib_stream.total_out); + zlib_stream.avail_out = compLength - zlib_stream.total_out; + + // Deflate another chunk. + ret = deflate(&zlib_stream, Z_FINISH); + if (Z_STREAM_END == ret) + { + done = true; + } + else if (Z_OK != ret) + { + break; + } + } + if (Z_OK != deflateEnd(&zlib_stream)) + { + free(comp); + return ret; + } + + *ppvOut = (void*)comp; + out_size = zlib_stream.total_out; + + return ret; +} +/// <summary> +/// Decompress given buffer. +/// Dont forget to free out buffer!!!! +/// </summary> +/// <param name="input"></param> +/// <param name="input_size"></param> +/// <param name="ppvOut"></param> +/// <param name="out_size"></param> +/// <returns></returns> +int CompressionUtility::DecompressGZip(const void* input, size_t input_size, void** ppvOut, size_t& out_size) +{ + z_stream zlib_stream; + zlib_stream.next_in = (Bytef*)input; + zlib_stream.avail_in = (uInt)input_size; + zlib_stream.next_out = Z_NULL; + zlib_stream.avail_out = Z_NULL; + zlib_stream.zalloc = (alloc_func)0; + zlib_stream.zfree = (free_func)0; + zlib_stream.opaque = Z_NULL; + + int ret = inflateInit2(&zlib_stream, (16 + MAX_WBITS)); + if (Z_OK != ret) + { + return ret; + } + unsigned full_length = input_size; + unsigned half_length = input_size / 2; + + unsigned uncompLength = full_length; + unsigned char* uncomp = (unsigned char*)malloc(uncompLength); + + bool done = false; + + while (!done) + { + if (zlib_stream.total_out >= uncompLength) + { + // Increase size of output buffer + unsigned char* uncomp2 = (unsigned char*)malloc(uncompLength + half_length); + memcpy(uncomp2, uncomp, uncompLength); + uncompLength += half_length; + free(uncomp); + uncomp = uncomp2; + } + + zlib_stream.next_out = (Bytef*)(uncomp + zlib_stream.total_out); + zlib_stream.avail_out = uncompLength - zlib_stream.total_out; + + // Inflate another chunk. + ret = inflate(&zlib_stream, Z_SYNC_FLUSH); + if (Z_STREAM_END == ret) + { + done = true; + } + else if (Z_OK != ret) + { + break; + } + } + if (Z_OK != inflateEnd(&zlib_stream)) + { + free(uncomp); + return ret; + } + + *ppvOut = (void*)uncomp; + out_size = zlib_stream.total_out; + return ret; +} + +/// <summary> +/// Returns inflated first file inside the ZIP container, +/// rest are ignored!!!!! +/// </summary> +/// <param name="input"></param> +/// <param name="input_size"></param> +/// <param name="ppvOut"></param> +/// <param name="out_size"></param> +/// <returns></returns> +int CompressionUtility::DecompressPKZip(const char* fn, void** ppvOut, size_t& out_size) +{ + // Open the zip file + unzFile zipfile = unzOpen(fn); + if (nullptr == zipfile) + { + // file not found + return -1; + } + + // Get info about the zip file + unz_global_info global_info; + if (UNZ_OK != unzGetGlobalInfo(zipfile, &global_info)) + { + // could not read file global info + unzClose(zipfile); + return -1; + } + + // Buffer to hold data read from the zip file. + //char read_buffer[READ_SIZE]; + + // Loop to extract all files + for (uLong i = 0; i < global_info.number_entry; ++i) + { + // Get info about current file. + unz_file_info file_info; + char filename[MAX_FILENAME]; + if (unzGetCurrentFileInfo( + zipfile, + &file_info, + filename, + MAX_FILENAME, + NULL, 0, NULL, 0) != UNZ_OK) + { + // could not read file info + unzClose(zipfile); + return -1; + } + + // Check if this entry is a directory or file. + const size_t filename_length = strlen(filename); + if (filename[filename_length - 1] == dir_delimter) + { + // Entry is a directory, skip it + } + else + { + // Entry is a file, so extract it. + if (unzOpenCurrentFile(zipfile) != UNZ_OK) + { + // could not open file + unzClose(zipfile); + return -1; + } + + unsigned full_length = READ_SIZE * 2; + unsigned half_length = READ_SIZE; + + unsigned uncompLength = full_length; + unsigned char* uncomp = (unsigned char*)malloc(uncompLength); + + size_t total_out = 0; + int error = UNZ_OK; + do + { + if (total_out >= uncompLength) + { + // Increase size of output buffer + unsigned char* uncomp2 = (unsigned char*)malloc(uncompLength + half_length); + memcpy(uncomp2, uncomp, uncompLength); + uncompLength += half_length; + free(uncomp); + uncomp = uncomp2; + } + + error = unzReadCurrentFile(zipfile, uncomp + total_out, uncompLength - total_out); + if (error < 0) + { + // something happened + unzCloseCurrentFile(zipfile); + unzClose(zipfile); + return -1; + } + + // Write data to buffer. + if (error > 0) + { + total_out += error; + } + } while (error > 0); + + *ppvOut = (void*)uncomp; + out_size = total_out; + } + + unzCloseCurrentFile(zipfile); + + // Go the the next entry listed in the zip file. + //if ((i + 1) < global_info.number_entry) + //{ + // if (unzGoToNextFile(zipfile) != UNZ_OK) + // { + // printf("cound not read next file\n"); + // unzClose(zipfile); + // return -1; + // } + //} + } + + unzClose(zipfile); + + return UNZ_OK; +} diff --git a/Src/Plugins/Input/in_midi/CompressionUtility.h b/Src/Plugins/Input/in_midi/CompressionUtility.h new file mode 100644 index 00000000..904dc25b --- /dev/null +++ b/Src/Plugins/Input/in_midi/CompressionUtility.h @@ -0,0 +1,13 @@ +#pragma once + +/// <summary> +/// +/// </summary> +class CompressionUtility +{ +public: + static int CompressAsGZip(const void* input, size_t input_size, void** ppvOut, size_t& out_size); + static int DecompressGZip(const void* input, size_t input_size, void** ppvOut, size_t& out_size); + static int DecompressPKZip(const char* fn, void** ppvOut, size_t& out_size); +}; + diff --git a/Src/Plugins/Input/in_midi/In2.h b/Src/Plugins/Input/in_midi/In2.h new file mode 100644 index 00000000..a667545f --- /dev/null +++ b/Src/Plugins/Input/in_midi/In2.h @@ -0,0 +1,2 @@ + +#include "../Winamp/in2.h"
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/cleaner.cpp b/Src/Plugins/Input/in_midi/cleaner.cpp new file mode 100644 index 00000000..1c72c668 --- /dev/null +++ b/Src/Plugins/Input/in_midi/cleaner.cpp @@ -0,0 +1,587 @@ +#include "main.h" + +#pragma warning(disable:4200) + +extern BYTE ff7loopstart[12]; +extern BYTE ff7loopend[10]; + +extern cfg_int cfg_hack_xg_drums, cfg_hack_dls_instruments, cfg_hack_dls_drums, cfg_ff7loopz; + +typedef union +{ + BYTE b[4]; + DWORD dw; +} b_dw; + +typedef struct +{ + DWORD pos, tm, sz; + BYTE le; + BYTE data[]; +} +TRACK; + +DWORD _fastcall rev32(DWORD); +//WORD _fastcall rev16(WORD); + + + +int test_drum_kit(DWORD no, IDirectMusicCollection* dls); +void do_dls_check(DWORD * i, IDirectMusicCollection * dls); + + +class CCleaner +{ +public: + INSTRUMENT_DESC* instr, **instr_ptr; + BYTE ctab[16][128]; + // UINT dm_vol; + grow_buf outbuf; + UINT ntrax, ntrax1; + UINT maxvol; + TRACK** in_trax; + TRACK* out_trax[16]; + DWORD ct; + UINT tf; + MIDI_file* mf; + DWORD vol_set; + + bool drumfix, insfix; + b_dw ins[16], ins_set[16]; + + bool f2, tr1, dm, only_ins, ins_no_lsb; + bool hasnotes[16]; + void check_ins(UINT msb, UINT lsb, UINT patch, UINT note, BOOL drum, UINT ch) //called on note + { + if (ins_no_lsb) lsb = 0; + INSTRUMENT_DESC * d = instr; + while (d) + { + if (d->bank_hi == msb && d->bank_lo == lsb && d->patch == patch && d->drum == drum) break; + d = d->next; + } + if (d) + { + d->count++; + if (d->note_max < note) d->note_max = note; + if (d->note_min > note) d->note_min = note; + d->channels |= 1 << ch; + } + else + { + d = new INSTRUMENT_DESC; + *instr_ptr = d; + instr_ptr = &d->next; + d->next = 0; + d->note_min = d->note_max = note; + d->bank_hi = msb; + d->bank_lo = lsb; + d->patch = patch; + d->count = 1; + d->drum = drum; + d->user = 0; + d->channels = 1 << ch; + } + } + void AdvanceTime(TRACK* t); + void AddEvent(BYTE ev, BYTE* data); + void WriteTrack(TRACK* t); + int Run(MIDI_file* mf, DWORD, void ** out_data, int * out_size); + + void do_shit(UINT n); + + UINT get_next_time() + { + UINT t = -1; + UINT n; + for (n = 0;n < ntrax;n++) + { + UINT t1 = in_trax[n]->tm; + if (t1 < t) t = t1; + } + return t; + } + + BOOL event_ok(BYTE e, BYTE* p) + { + BYTE _c = e & 0xF0; + BYTE ch = e & 0xF; + if (_c == 0xB0) + { + if (cfg_hack_xg_drums && ch == 9 && p[1] == 0 && (p[0] == 0 || p[0] == 0x20)) + return 0; + + if (p[0] > 127) + return 0; + + ctab[ch][p[0]] = p[1]; + + + if (p[0] == 0) + { + ins[ch].b[2] = p[1]; + if (insfix) return 0; + } + if (p[0] == 0x20) + { + ins[ch].b[1] = p[1]; + if (insfix) return 0; + } + + if (dm) //keep dm drivers happy... + { + if (p[0] >= 0x20 && p[0] < 0x40) //lsb values + { + return 0; + } + else if (p[0] < 0x20) + { + BYTE data[2] = {(BYTE)(p[0] + 0x20),ctab[ch][p[0] + 0x20]}; + AddEvent(e, data); + } + } + + return 1; + + } + + else if (_c == 0xC0) + { + if (ch == 9) + { + if (drumfix && !test_drum_kit(p[0], mf->pDLS)) return 0; + ins[ch].b[0] = p[0]; + } + else + { + ins[ch].b[0] = p[0]; + if (insfix) return 0; + } + } + else if (_c == 0x90 && p[1]) + { + if (only_ins) + check_ins(ins[ch].b[2], ins[ch].b[1], ins[ch].b[0], p[0], ch == 9, ch); + if (ch != 9 && insfix) + { + if (ins_set[ch].dw != ins[ch].dw) + { + do_dls_check(&ins[ch].dw, mf->pDLS); + + + if (ins_set[ch].b[1] != ins[ch].b[1]) + { + BYTE t[2] = {0x20, ins[ch].b[1]}; + AddEvent(0xB0 | ch, t); + } + + if (ins_set[ch].b[2] != ins[ch].b[2]) + { + BYTE t[2] = {0, ins[ch].b[2]}; + AddEvent(0xB0 | ch, t); + } + AddEvent(0xC0 | ch, ins[ch].b); + + ins_set[ch].dw = ins[ch].dw; + } + } + } + + return 1; + } + + CCleaner() + { + memset(ins, 0, sizeof(ins)); + memset(ins_set, -1, sizeof(ins_set)); + memset(hasnotes, 0, sizeof(hasnotes)); + memset(out_trax, 0, sizeof(out_trax)); + in_trax = 0; + } + ~CCleaner() + { + UINT n; + if (in_trax) + { + for (n = 0;n < ntrax;n++) + if (in_trax[n]) {free(in_trax[n]);in_trax[n] = 0;} + free(in_trax); + } + for (n = 0;n < 16;n++) + { + if (out_trax[n]) + { + free(out_trax[n]); + out_trax[n] = 0; + } + } + } +}; + +void CCleaner::do_shit(UINT n) +{ + BYTE ce = 0; + TRACK* t = in_trax[n]; + if (!t) return ; + while (t->tm == ct) + { + + if (t->pos >= t->sz) + { + t->pos = -1; + t->tm = -1; + tf++; + break; + } + BYTE c0 = t->data[t->pos]; + if (c0 == 0xFF) //Meta-events + { + + if (cfg_ff7loopz + && (t->sz - t->pos) >= sizeof(ff7loopend) // bounds check + && !memcmp(t->data + t->pos, ff7loopend, sizeof(ff7loopend))) + { + // MessageBox(GetActiveWindow(),"blah",0,0); + // AdvanceTime(t); + tf = ntrax; + // return; + } + BYTE c1 = t->data[t->pos + 1]; + if (c1 == 0x2F) + { + t->pos += 3; + t->tm = -1; + tf++; + } + { + t->pos += 2; + if (t->pos < t->sz) + { + + unsigned int _d; + t->pos += DecodeDelta(t->data + t->pos, &_d, t->sz - t->pos); + t->pos += _d; + } + } + } else if (c0 == 0xF0) + { + t->pos += ReadSysex(&t->data[t->pos], t->sz - t->pos); + } + else if (c0 == 0xF7) t->pos++; + else if ((c0&0xF0) == 0xF0) //WTF? + { + t->pos = -1; + t->tm = -1; + tf++; + break; + } + else + { + if (c0&0x80) + { + ce = c0; + t->pos++; + } + else ce = t->le; + + if (event_ok(ce, &t->data[t->pos])) AddEvent(ce, &t->data[t->pos]); + + if ((ce&0xF0) == 0xC0 || (ce&0xF0) == 0xD0) t->pos++; + else t->pos += 2; + t->le = ce; + } + + if (t->tm != -1 && t->pos >= t->sz) + { + t->pos = -1; + t->tm = -1; + tf++; + break; + } + AdvanceTime(t); + } +} + +#define WriteBuf(A,B) outbuf.write(A,B) + +#pragma pack(push) +#pragma pack(1) +typedef struct +{ + WORD t, n, d; +} +MHD; +typedef struct +{ + DWORD c, s; +} +CHD; +#pragma pack(pop) + + +void CCleaner::AdvanceTime(TRACK* t) +{ + if (t->tm != -1) + { + unsigned int d; + UINT _n = DecodeDelta(t->data + t->pos, &d, t->sz - t->pos); + if (_n < 4) t->tm += d; + t->pos += _n; + } +} + +void CCleaner::AddEvent(BYTE ev, BYTE* data) +{ + if (only_ins) return ; + BYTE nt = ev & 0xF; + BYTE ec = ev & 0xF0; + if (tr1) nt = 0; + TRACK *t = out_trax[nt]; + + ZeroMemory(ctab, sizeof(ctab)); + + + if (!t) + { + t = out_trax[nt] = (TRACK*)malloc(sizeof(TRACK) + 0x1000); + if (!t) return ; + ZeroMemory(t, 16); + t->sz = 0x1000; + t->tm = 0; + + } + else if (t->pos > t->sz - 0x10) + { + t->sz *= 2; + out_trax[nt] = (TRACK*)realloc(t, sizeof(TRACK) + t->sz); + if (!out_trax[nt]) + { + free(t); + return ; + } + t = out_trax[nt]; + } + + if (t->tm < ct) + { + t->pos += EncodeDelta(&t->data[t->pos], ct - t->tm); + t->tm = ct; + } + else + { + t->data[t->pos++] = 0; + } + if (ec == 0x90) + { + hasnotes[nt] = 1; + data[0] &= 0x7F; /* don't allow 8bit note numbers */ + } + else if (ec == 0x80) + { + data[0] &= 0x7F; /* don't allow 8bit note numbers */ + } + /*if (ev!=t->le) */{t->data[t->pos++] = ev;t->le = ev;} + t->data[t->pos++] = data[0]; + if (ec != 0xC0 && ec != 0xD0) t->data[t->pos++] = data[1]; +} + +void CCleaner::WriteTrack(TRACK* t) +{ + CHD chd; + chd.c = 'krTM'; + chd.s = rev32(t->pos); + WriteBuf(&chd, 8); + WriteBuf(&t->data, t->pos); + ntrax1++; +} + +int DoCleanUp(MIDI_file* mf, DWORD mode, void** out_data, int * out_size) +{ + CCleaner c; + c.only_ins = 0; + return c.Run(mf, mode, out_data, out_size); +} + +int CCleaner::Run(MIDI_file* _mf, DWORD _md, void ** out_data, int * out_size) +{ + f2 = *(WORD*)(_mf->data + 8) == 0x0200; + maxvol = 90; + vol_set = 0; + dm = (_md & CLEAN_DM) ? 1 : 0; + tr1 = (_md & CLEAN_1TRACK) ? 1 : 0; + + if (_md&CLEAN_DLS) + { + drumfix = dm && cfg_hack_dls_drums; + insfix = dm && cfg_hack_dls_instruments; + } + else + { + drumfix = insfix = 0; + } + + + + mf = _mf; + + instr_ptr = &instr; + instr = 0; + + UINT n; + + ct = 0; + tf = 0; + ntrax = ntrax1 = 0; + CHD chd; + MHD mhd; + DWORD ptr = 8; + + + + mhd = *(MHD*)(mf->data + 8); + + ptr += 6; + + mhd.t = rev16(mhd.t); + mhd.n = rev16(mhd.n); + + if (mhd.t > 2) + goto fail; + ntrax = mhd.n; + n = 0; + in_trax = (TRACK**)malloc(sizeof(void*) * ntrax); + for (;n < ntrax && ptr < (UINT)mf->size;n++) + { + chd = *(CHD*)(mf->data + ptr); + ptr += 8; + if (chd.c != 'krTM' || ptr > (UINT)mf->size) + { + ntrax = n; + break; + } + chd.s = rev32(chd.s); + //if (ptr+chd.s>(UINT)mf->size) + if (chd.s > ((UINT)mf->size - ptr)) + { + chd.s = mf->size - ptr; + } + //goto fail; + in_trax[n] = (TRACK*)malloc(16 + chd.s); + in_trax[n]->sz = chd.s; + in_trax[n]->tm = 0; + in_trax[n]->le = 0; + in_trax[n]->pos = 0; + memcpy(in_trax[n]->data, mf->data + ptr, chd.s); + ptr += chd.s; + AdvanceTime(in_trax[n]); + } + if (f2) + { + for (n = 0;n < ntrax;n++) + { + in_trax[n]->tm = ct; + while (tf <= n) + { + do_shit(n); + if (in_trax[n]->tm != -1) ct = in_trax[n]->tm; + } + } + } + else + { + while (tf < ntrax) + { + UINT nt = get_next_time(); //ct++; + if (nt == -1) break; + ct = nt; + for (n = 0;n < ntrax && tf < ntrax;n++) + { + do_shit(n); + } + } + } + + if (!only_ins) + { + + + mhd.t = 0x0100; + mhd.n = 0; //rev16(ntrax1); + chd.c = 'dhTM'; + chd.s = 0x06000000; + WriteBuf(&chd, 8); + WriteBuf(&mhd, 6); + if (!(_md&CLEAN_NOTEMPO) && mf->tmap) + { + /* BYTE *tt=mf->tmap->BuildTrack(); + if (tt) + { + WriteBuf(tt,rev32(*(DWORD*)(tt+4))+8); + ntrax1++; + free(tt); + }*/ + if (mf->tmap->BuildTrack(outbuf)) + { + ntrax1++; + } + } + if (!(_md&CLEAN_NOSYSEX) && mf->smap) + { + /* BYTE *st=mf->smap->BuildTrack(); + if (st) + { + WriteBuf(st,rev32(*(DWORD*)(st+4))+8); + ntrax1++; + free(st); + }*/ + if (mf->smap->BuildTrack(outbuf)) + { + ntrax1++; + } + } + + + + for (n = 0;n < 16;n++) if (out_trax[n] && hasnotes[n] && out_trax[n]->pos) + { + TRACK *t = out_trax[n]; + t->pos += EncodeDelta(t->data + t->pos, ct - t->tm); + t->data[t->pos++] = 0xFF; + t->data[t->pos++] = 0x2F; + t->data[t->pos++] = 0; + WriteTrack(t); + } + { + WORD t = rev16(ntrax1); + outbuf.write_ptr(&t, 2, 10); + } + if (out_size) *out_size = outbuf.get_size(); + if (out_data) *out_data = outbuf.finish(); +#if 0 + { + HANDLE f = CreateFile("c:\\dump.mid", GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); + DWORD bw = 0; + WriteFile(f, rv, bs, &bw, 0); + CloseHandle(f); + } +#endif + + } + return 1; +fail: + // ErrorBox("WARNING: cleaner messed up"); + + return 0; + + //TO DESTRUCTOR + +} + +INSTRUMENT_DESC* GetInstruments(MIDI_file* mf, BOOL do_lsb) +{ + CCleaner c; + c.only_ins = 1; + c.ins_no_lsb = !do_lsb; + c.Run(mf, 0, 0, 0); + return c.instr; +} diff --git a/Src/Plugins/Input/in_midi/cmf.cpp b/Src/Plugins/Input/in_midi/cmf.cpp new file mode 100644 index 00000000..0eb4f552 --- /dev/null +++ b/Src/Plugins/Input/in_midi/cmf.cpp @@ -0,0 +1,48 @@ +#include "main.h" +#include "cvt.h" + +bool is_cmf(const BYTE* buf,size_t s) +{ + return s>0x20 && *(DWORD*)buf == _rv('CTMF'); +} + +static BYTE tempodat[7]={0,0xFF,0x51,0x03,0,0,0}; + +bool load_cmf(MIDI_file * mf,const BYTE* ptr,size_t sz) +{ + if (sz < 14) + return 0; + + WORD music_offset = *(WORD*)(ptr+8); + if ((size_t)music_offset > sz) + return 0; + const BYTE* _t=ptr+music_offset; + size_t total_size=sz - music_offset; + mf->size=14+8+7+total_size; + BYTE* _pt=(BYTE*)malloc(mf->size); + if (!_pt) return 0; + + mf->data=_pt; + *(DWORD*)_pt=_rv('MThd'); + _pt+=4; + *(DWORD*)_pt=_rv(6); + _pt+=4; + *(WORD*)_pt=0; + _pt+=2; + *(WORD*)_pt=0x0100; + _pt+=2; + *(WORD*)_pt=rev16(*(WORD*)(ptr+10)); + _pt+=2; + *(DWORD*)_pt=_rv('MTrk'); + _pt+=4; + *(DWORD*)_pt=rev32(total_size+7); + _pt+=4; + DWORD tm=(DWORD)(48000000/(*(WORD*)(ptr+12))); + tempodat[4]=(BYTE)((tm>>16)&0xFF); + tempodat[5]=(BYTE)((tm>>8)&0xFF); + tempodat[6]=(BYTE)(tm&0xFF); + memcpy(_pt,tempodat,7); + _pt+=7; + memcpy(_pt,_t,total_size); + return 1; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/config.cpp b/Src/Plugins/Input/in_midi/config.cpp new file mode 100644 index 00000000..fa2fd8b1 --- /dev/null +++ b/Src/Plugins/Input/in_midi/config.cpp @@ -0,0 +1,1072 @@ +#include "main.h" +#include <shlobj.h> +#include <commctrl.h> +#include "resource.h" +#include "../winamp/wa_ipc.h" +#include "../pfc/string_unicode.h" + +static HWND g_theConfig; + +static struct +{ + HWND wnd; + int id; + UINT l_id; +} child_dlgs[]={ + {0,IDD_CONFIG1,IDS_PREFS_DEVICE}, + {0,IDD_CONFIG8,IDS_PREFS_DISPLAY}, + {0,IDD_CONFIG2,IDS_PREFS_SAMPLING}, + {0,IDD_CONFIG3,IDS_PREFS_DIRECTMUSIC}, + {0,IDD_CONFIG4,IDS_PREFS_MISC}, + {0,IDD_CONFIG7,IDS_PREFS_FILE_TYPES}, + {0,IDD_CONFIG5,IDS_PREFS_FILES}, + {0,IDD_CONFIG6,IDS_PREFS_HARDWARE_SETUP}, +}; + +enum +{ + IDC_CONFIG1,IDC_CONFIG8,IDC_CONFIG2,IDC_CONFIG3,IDC_CONFIG4,IDC_CONFIG7,IDC_CONFIG5,IDC_CONFIG6 +}; + +static const char CFG_NAME[]="IN_MIDI"; + +#define FILE_BLAH MessageBoxA(wnd,WASABI_API_LNGSTRING(IDS_UNABLE_TO_LOAD_FILE),0,MB_ICONERROR) + +int loop_txt[3]={STRING_LOOP1,STRING_LOOP2,STRING_LOOP3}; + +#define MAKEID(X,Y) ( ( (DWORD)(X)<<16) | (DWORD)(Y)) //MAKEID(DLG_ID,CTRL_ID) + +extern cfg_int cfg_seq_showpanel; +extern cfg_int cfg_lyrics; + + +cfg_int cfg_smp("smp",0),cfg_reverb("reverb",0),cfg_chorus("chorus",0),cfg_dls_active("dls_active",0); + +cfg_int cfg_sampout("sampout",0); +cfg_int cfg_hardware_reset("hardware_reset",0); +cfg_int cfg_gm_reset("gm_reset",0),cfg_gm_reset1("gm_reset1",0); +cfg_int cfg_gs_reset("gs_reset",0),cfg_gs_reset1("gs_reset1",0); +cfg_int cfg_nosysex("nosysex",0); +cfg_int cfg_dm_keep_port("dm_keep_port",0); +cfg_int cfg_recover_tracks("recover_tracks",1); +cfg_int cfg_rmi_def("rmi_def",0); +cfg_int cfg_logvol("logvol",1); + +cfg_int cfg_ff7loopz("ff7loopz",1); +cfg_int cfg_samp_revert("samp_revert",1); + +cfg_int cfg_hack_xg_drums("hack_xg_drums",0),cfg_hack_dls_instruments("hack_dls_instruments",1),cfg_hack_dls_drums("hack_dls_drums",1); + +static const WORD sr_tab[]={8000,11025,16000,22050,24000,32000,44100,48000}; + +cfg_int cfg_cur_tab("cur_tab",0); + +cfg_int cfg_volmode("volmode",2); +//0 - no volume, 1 - default, 2,3,4 ... - mixers +cfg_string cfg_extra_exts("extra_exts",""); + +cfg_int cfg_freq("freq",22050); +cfg_int cfg_eof_delay("eof_delay",0); + +cfg_string cfg_dls_file("dls_file",""); +cfg_int cfg_bugged("bugged",0); + +cfg_int cfg_loop_type("loop_type",0),cfg_loop_count("loop_count",1),cfg_loop_infinite("loop_infinite",0); + +cfg_int cfg_wavein_dev("wavein_dev",-1),cfg_wavein_sr("wavein_sr",44100),cfg_wavein_ch("wavein_ch",2),cfg_wavein_bps("wavein_bps",16),cfg_wavein_src("wavein_src",0); +cfg_int cfg_playback_mode("playback_mode",0); + + +static UINT vm_retval=-666; + +cfg_struct_t<GUID> cfg_driver("driver",0),cfg_device("device",0); + +UINT volmode_detect() +{ + if (vm_retval!=-666) return vm_retval; + UINT dev; + UINT id; + UINT n_devz=mixerGetNumDevs(); + UINT pos=0; + for(dev=0;dev<n_devz;dev++) + { + mixerGetID((HMIXEROBJ)dev,&id,MIXER_OBJECTF_MIXER); + + + MIXERLINE ml; + memset(&ml,0,sizeof(ml)); + ml.cbStruct=sizeof(ml); + ml.dwComponentType=MIXERLINE_COMPONENTTYPE_DST_SPEAKERS; + + mixerGetLineInfo((HMIXEROBJ)id,&ml,MIXER_GETLINEINFOF_COMPONENTTYPE|MIXER_OBJECTF_MIXER); + + UINT con; + for(con=0;con<ml.cConnections;con++) + { + MIXERLINE ml1; + memset(&ml1,0,sizeof(ml1)); + ml1.cbStruct=sizeof(ml); + ml1.dwSource=con; + mixerGetLineInfo((HMIXEROBJ)id,&ml1,MIXER_GETLINEINFOF_SOURCE|MIXER_OBJECTF_MIXER); + if (ml1.dwComponentType==MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER) + { + vm_retval=pos; + return vm_retval; + } + pos++; + } + } + vm_retval=-1; + return vm_retval; +} + +//avoid double id shit +#define _IDC_PORT MAKEID(IDC_CONFIG1,IDC_PORT) +#define _IDC_DEV_INFO MAKEID(IDC_CONFIG1,IDC_DEV_INFO) +#define _IDC_HARDWARE_RESET MAKEID(IDC_CONFIG1,IDC_HARDWARE_RESET) +#define _IDC_VOLMODE MAKEID(IDC_CONFIG1,IDC_VOLMODE) +#define _IDC_LOGVOL MAKEID(IDC_CONFIG1,IDC_LOGVOL) +#define _IDC_SAMPLING_ENABLED MAKEID(IDC_CONFIG2,IDC_SAMPLING_ENABLED) +#define _IDC_WAVEIN MAKEID(IDC_CONFIG2,IDC_WAVEIN) +#define _IDC_WAVEIN_SRC MAKEID(IDC_CONFIG2,IDC_WAVEIN_SRC) +#define _IDC_WAVEIN_SR MAKEID(IDC_CONFIG2,IDC_WAVEIN_SR) +#define _IDC_WAVEIN_S2 MAKEID(IDC_CONFIG2,IDC_WAVEIN_S2) +#define _IDC_WAVEIN_CH MAKEID(IDC_CONFIG2,IDC_WAVEIN_CH) +#define _IDC_WAVEIN_BPS MAKEID(IDC_CONFIG2,IDC_WAVEIN_BPS) +#define _IDC_SAMPLING_DSP MAKEID(IDC_CONFIG2,IDC_SAMPLING_DSP) +#define _IDC_SAMPLING_OUTPUT MAKEID(IDC_CONFIG2,IDC_SAMPLING_OUTPUT) +#define _IDC_SAMP_REVERT MAKEID(IDC_CONFIG2,IDC_SAMP_REVERT) +#define _IDC_REVERB MAKEID(IDC_CONFIG3,IDC_REVERB) +#define _IDC_CHORUS MAKEID(IDC_CONFIG3,IDC_CHORUS) +#define _IDC_DM_KEEP_PORT MAKEID(IDC_CONFIG3,IDC_DM_KEEP_PORT) +#define _IDC_FREQ MAKEID(IDC_CONFIG3,IDC_FREQ) +#define _IDC_DLS_CB MAKEID(IDC_CONFIG3,IDC_DLS_CB) +#define _IDC_DLS MAKEID(IDC_CONFIG3,IDC_DLS) +#define _IDC_DLS_B MAKEID(IDC_CONFIG3,IDC_DLS_B) +#define _IDC_HACK_DM_RESETS MAKEID(IDC_CONFIG3,IDC_HACK_DM_RESETS) +#define _IDC_SHOW_PANEL MAKEID(IDC_CONFIG4,IDC_SHOW_PANEL) +#define _IDC_LOOP_S MAKEID(IDC_CONFIG4,IDC_LOOP_S) +#define _IDC_LOOP MAKEID(IDC_CONFIG4,IDC_LOOP) +#define _IDC_LOOP_S1 MAKEID(IDC_CONFIG4,IDC_LOOP_S1) +#define _IDC_LOOP_S2 MAKEID(IDC_CONFIG4,IDC_LOOP_S2) +#define _IDC_LOOP_T MAKEID(IDC_CONFIG4,IDC_LOOP_T) +#define _IDC_LOOP_SP MAKEID(IDC_CONFIG4,IDC_LOOP_SP) +#define _IDC_LOOP_S3 MAKEID(IDC_CONFIG4,IDC_LOOP_S3) +#define _IDC_LOOP_S4 MAKEID(IDC_CONFIG4,IDC_LOOP_S4) +#define _IDC_INFINITE MAKEID(IDC_CONFIG4,IDC_INFINITE) +#define _IDC_PLAYBACK_METHOD MAKEID(IDC_CONFIG4,IDC_PLAYBACK_METHOD) +#define _IDC_RESET MAKEID(IDC_CONFIG4,IDC_RESET) +#define _IDC_RESET1 MAKEID(IDC_CONFIG4,IDC_RESET1) +#define _IDC_STEREO MAKEID(IDC_CONFIG5,IDC_STEREO) +#define _IDC_HACKTRACK MAKEID(IDC_CONFIG5,IDC_HACKTRACK) + +#define _IDC_HACK_NO_SYSEX MAKEID(IDC_CONFIG5,IDC_HACK_NO_SYSEX) +#define _IDC_HACK_XG_DRUMS MAKEID(IDC_CONFIG5,IDC_HACK_XG_DRUMS) +#define _IDC_HACK_DLS_DRUMS MAKEID(IDC_CONFIG5,IDC_HACK_DLS_DRUMS) +#define _IDC_HACK_DLS_INSTRUMENTS MAKEID(IDC_CONFIG5,IDC_HACK_DLS_INSTRUMENTS) + +#define _IDC_LYRICS_ENABLED MAKEID(IDC_CONFIG8,IDC_LYRICS_ENABLED) +#define _IDC_EOF_DELAY MAKEID(IDC_CONFIG5,IDC_EOF_DELAY) +#define _IDC_EOF_DELAY_SPIN MAKEID(IDC_CONFIG5,IDC_EOF_DELAY_SPIN) + +sysex_table cfg_sysex_table; +static sysex_table edit_sysex_table; + + +#define _IDC_SYSEX_LIST MAKEID(IDC_CONFIG6,IDC_SYSEX_LIST) +#define _IDC_IMP_F MAKEID(IDC_CONFIG6,IDC_IMP_F) +#define _IDC_EXP_F MAKEID(IDC_CONFIG6,IDC_EXP_F) +#define _IDC_IMP_PR MAKEID(IDC_CONFIG6,IDC_IMP_PR) +#define _IDC_EXP_PR MAKEID(IDC_CONFIG6,IDC_EXP_PR) +#define _IDC_SYSEX_ADD MAKEID(IDC_CONFIG6,IDC_SYSEX_ADD) +#define _IDC_SYSEX_DELETE MAKEID(IDC_CONFIG6,IDC_SYSEX_DELETE) +#define _IDC_SYSEX_EDIT MAKEID(IDC_CONFIG6,IDC_SYSEX_EDIT) +#define _IDC_SYSEX_UP MAKEID(IDC_CONFIG6,IDC_SYSEX_UP) +#define _IDC_SYSEX_DOWN MAKEID(IDC_CONFIG6,IDC_SYSEX_DOWN) + + +#define _IDC_EXTS_LIST MAKEID(IDC_CONFIG7,IDC_EXTS_LIST) +#define _IDC_EXTS_ED MAKEID(IDC_CONFIG7,IDC_EXTS_ED) +#define _IDC_RMI_FMT MAKEID(IDC_CONFIG8,IDC_RMI_FMT) +#define _IDC_RMI_DEF MAKEID(IDC_CONFIG8,IDC_RMI_DEF) + + +#define EnableDlgItem(X,Y,Z) EnableWindow(_GetDlgItem(X,Y),Z) + +static HWND cfgGetTab(UINT id) +{ + id-=IDC_CONFIG1; + if (id>=tabsize(child_dlgs)) return 0; + return child_dlgs[id].wnd; +} + +static HWND __getdlgitem(UINT id) +{ + + HWND w=cfgGetTab(id>>16); + if (!w) return 0; + return GetDlgItem(w,id&0xFFFF); +} + +#define _GetDlgItem(w,id) __getdlgitem(id) + +#define _GetDlgItemText(p,id,tx,l) GetWindowText(__getdlgitem(id),tx,l) +#define _SetDlgItemText(p,id,tx) SetWindowText(__getdlgitem(id),tx) +#define _SetDlgItemTextW(p,id,tx) SetWindowTextW(__getdlgitem(id),tx) +#define _GetDlgItemInt(a,b,c,d) __getdlgitemint(b) +#define _SetDlgItemInt(a,b,c,d) __setdlgitemint(b,c) +#define _SendDlgItemMessage(a,b,c,d,e) SendMessage(_GetDlgItem(a,b),c,d,e) + +static void __setdlgitemint(UINT id,UINT val) +{ + char buf[32] = {0}; + _itoa(val,buf,10); + SetWindowTextA(__getdlgitem(id),buf); +} + +static int __getdlgitemint(UINT id) +{ + char buf[32] = {0}; + GetWindowTextA(__getdlgitem(id),buf,32); + return atoi(buf); +} + + +static BOOL CALLBACK cfgVisStatusProc(HWND wnd,LPARAM param) +{ + if (GetWindowLong(wnd,GWL_ID)==IDC_SAMPLING_ENABLED) + { + EnableWindow(wnd,(param & 2) >> 1); + } + else + { + EnableWindow(wnd,param & 1); + } + return 1; +} + +static MIDI_device * get_device(HWND wnd) +{ + int idx = _SendDlgItemMessage(wnd,_IDC_PORT,CB_GETCURSEL,0,0); + if (idx<0) return 0; + return (MIDI_device*)_SendDlgItemMessage(wnd,_IDC_PORT,CB_GETITEMDATA,idx,0); +} + + +static void cfgVisStatus(HWND wnd) +{ + int samp =_SendDlgItemMessage(wnd,_IDC_SAMPLING_ENABLED,BM_GETCHECK,0,0); + int output = 0; + int flags = 0; + MIDI_device * dev = get_device(wnd); + if (dev && dev->has_output()) output = 1; + + if (output && samp) {samp = 0;_SendDlgItemMessage(wnd,_IDC_SAMPLING_ENABLED,BM_SETCHECK,0,0);} + if (!output) + { + flags|=2; + if (samp) flags|=1; + else { + _SendDlgItemMessage(wnd,_IDC_SAMPLING_DSP,BM_SETCHECK,0,0); + _SendDlgItemMessage(wnd,_IDC_SAMPLING_OUTPUT,BM_SETCHECK,0,0); + } + } + + EnumChildWindows(cfgGetTab(IDC_CONFIG2),cfgVisStatusProc,flags); +} + + +static void cfgVisMix(HWND wnd,int dev) +{ + HWND w=_GetDlgItem(wnd,_IDC_WAVEIN_SRC); + SendMessage(w,CB_RESETCONTENT,0,0); + SendMessageW(w,CB_ADDSTRING,0,(long)WASABI_API_LNGSTRINGW(STRING_SAMP_SRC_DEFAULT)); + SendMessage(w,CB_SETCURSEL,0,0); + UINT id=0; + + if (mixerGetID((HMIXEROBJ)dev,&id,MIXER_OBJECTF_WAVEOUT)) return; + + MIXERLINE ml; + memset(&ml,0,sizeof(ml)); + ml.cbStruct=sizeof(ml); + ml.dwComponentType=MIXERLINE_COMPONENTTYPE_DST_WAVEIN; + + if (mixerGetLineInfo((HMIXEROBJ)id,&ml,MIXER_GETLINEINFOF_COMPONENTTYPE|MIXER_OBJECTF_MIXER)) return; + + MIXERLINECONTROLS cs; + MIXERCONTROL c; + memset(&cs,0,sizeof(cs)); + cs.cbStruct=sizeof(cs); + cs.cControls=1; + cs.dwLineID=ml.dwLineID; + cs.dwControlType=MIXERCONTROL_CONTROLTYPE_MUX; + cs.cbmxctrl=sizeof(c); + cs.pamxctrl=&c; + memset(&c,0,sizeof(c)); + c.cbStruct=sizeof(c); + + if (!mixerGetLineControls((HMIXEROBJ)id,&cs,MIXER_OBJECTF_MIXER|MIXER_GETLINECONTROLSF_ONEBYTYPE)) + { + MIXERCONTROLDETAILS_LISTTEXT *t = (MIXERCONTROLDETAILS_LISTTEXT*)malloc(sizeof(MIXERCONTROLDETAILS_LISTTEXT)*c.cMultipleItems); + MIXERCONTROLDETAILS d; + d.cbStruct=sizeof(d); + d.dwControlID=c.dwControlID; + d.cbDetails=sizeof(*t); + d.cChannels=ml.cChannels; + d.cMultipleItems=c.cMultipleItems; + d.paDetails=t; + UINT n; + for(n=0;n<c.cMultipleItems;n++) + { + t[n].dwParam1=ml.dwLineID; + } + mixerGetControlDetails((HMIXEROBJ)id,&d,MIXER_GETCONTROLDETAILSF_LISTTEXT|MIXER_OBJECTF_MIXER); + for(n=0;n<c.cMultipleItems;n++) + { + SendMessage(w,CB_ADDSTRING,0,(long)t[n].szName); + } + free(t); + } +} + +static void ChangePort(HWND wnd,MIDI_device * dev) +{ + cfgVisStatus(wnd); + + { + string_w temp; + temp+=WASABI_API_LNGSTRINGW(IDS_TYPE); + temp+=dev->get_driver()->get_name(); + temp+=L"\x0d\x0a"; + temp+=dev->get_info(); + if (!wcscmp((const wchar_t*)temp + temp.length()-2,L"\x0d\x0a")) temp.truncate(temp.length()-2); + _SetDlgItemTextW(wnd,_IDC_DEV_INFO,temp); + } +} + +#undef DEV + +const static struct +{ + UINT id; + cfg_int * var; +} bWnds[]= +{ + {_IDC_SAMP_REVERT,&cfg_samp_revert}, + {_IDC_LOGVOL,&cfg_logvol}, + {_IDC_RMI_DEF,&cfg_rmi_def}, + {_IDC_HACK_DLS_DRUMS,&cfg_hack_dls_drums}, + {_IDC_HACK_DLS_INSTRUMENTS,&cfg_hack_dls_instruments}, + {_IDC_HACK_XG_DRUMS,&cfg_hack_xg_drums}, + {_IDC_HACKTRACK,&cfg_recover_tracks}, + {_IDC_DM_KEEP_PORT,&cfg_dm_keep_port}, + {_IDC_SAMPLING_ENABLED,&cfg_smp}, + {_IDC_REVERB,&cfg_reverb}, + {_IDC_CHORUS,&cfg_chorus}, + {_IDC_SAMPLING_OUTPUT,&cfg_sampout}, + {_IDC_HACK_NO_SYSEX,&cfg_nosysex}, + {_IDC_LYRICS_ENABLED,&cfg_lyrics}, + {_IDC_SHOW_PANEL,&cfg_seq_showpanel}, +}; + +#define NUM_BWNDS (sizeof(bWnds)/sizeof(*bWnds)) + +#define WM_CMDNOTIFY (WM_USER+6) + +static BOOL WINAPI CfgChildProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) +{ + if (msg==WM_COMMAND) + { +#if defined(_WIN64) + SendMessage(GetParent(wnd),WM_CMDNOTIFY,wp,MAKEID(GetWindowLong(wnd,DWLP_USER),LOWORD(wp))); +#else + SendMessage(GetParent(wnd), WM_CMDNOTIFY, wp, MAKEID(GetWindowLong(wnd, DWL_USER), LOWORD(wp))); +#endif + } + return 0; +} + + +static bool is_hex(char c) {return (c>='0' && c<='9') || (c>='a' && c<='f') || (c>='A' && c<='F');} + +static UINT read_hex(char* t) +{ + UINT r=0; + while(is_hex(*t)) + { + r<<=4; + if (*t>='0' && *t<='9') + r|=*t-'0'; + else if (*t>='a' && *t<='f') + r|=*t-'a'+10; + else r|=*t-'A'+10; + t++; + } + return r; +} + +BYTE* read_sysex_edit(HWND w,UINT *siz)//used in seq.cpp +{ + UINT tl=GetWindowTextLength(w)+2; + char* tx=(char*)malloc(tl); + if (!tx) return 0; + GetWindowTextA(w,tx,tl+2); + UINT sl=0; + BYTE* tmp=(BYTE*)malloc(tl/2+2); + char* t=tx; + if (!tmp) + { + free(tx); + return 0; + } + tmp[sl++]=0xF0; + while(t && *t) + { + while(!is_hex(*t) && *t) t++; + if (!*t) break; + tmp[sl++]=read_hex(t); + while(is_hex(*t)) t++; + } + + tmp[sl++]=0xF7; + free(tx); + *siz=sl; + return tmp; +} + +static BOOL WINAPI SysexProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) +{ + switch(msg) + { + case WM_INITDIALOG: +#if defined(_WIN64) + SetWindowLong(wnd,DWLP_USER,lp); +#else + SetWindowLong(wnd, DWL_USER, lp); +#endif + SendDlgItemMessage(wnd,IDC_SPIN1,UDM_SETRANGE,0,MAKELONG(9999,0)); + SetDlgItemInt(wnd,IDC_DELAY,edit_sysex_table.get_time(lp),0); + edit_sysex_table.print_edit(lp,GetDlgItem(wnd,IDC_EDIT1)); + return 1; + case WM_COMMAND: + switch(wp) + { + case IDOK: + { + UINT size; + BYTE* data=read_sysex_edit(GetDlgItem(wnd,IDC_EDIT1),&size); + if (!data) + { + EndDialog(wnd,0); + break; + } +#if defined(_WIN64) + edit_sysex_table.modify_entry(GetWindowLong(wnd, DWLP_USER), data, size, GetDlgItemInt(wnd, IDC_DELAY, 0, 0)); +#else + edit_sysex_table.modify_entry(GetWindowLong(wnd, DWL_USER), data, size, GetDlgItemInt(wnd, IDC_DELAY, 0, 0)); +#endif + free(data); + } + + EndDialog(wnd,1); + break; + case IDCANCEL: + EndDialog(wnd,0); + break; + } + } + return 0; +} + +extern int initDefaultDeviceShit(); + +static BOOL WINAPI CfgProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) +{ + switch(msg) + { + case WM_INITDIALOG: + if (g_theConfig && g_theConfig!=wnd) + { + ShowWindow(g_theConfig,SW_SHOW); + EndDialog(wnd,0); + return 0; + } + + initDefaultDeviceShit(); + + g_theConfig=wnd; + { + HWND hTab=GetDlgItem(wnd,IDC_TAB); +#if defined(_WIN64) + SetWindowLong(wnd, DWLP_USER, (long)hTab); +#else + SetWindowLong(wnd, DWL_USER, (long)hTab); +#endif + HWND w; + UINT n; + TC_ITEMW it= + { + TCIF_TEXT, + 0,0, + 0, //pszText + 0, + -1,0 + }; + for(n=0;n<tabsize(child_dlgs);n++) + { + it.pszText=WASABI_API_LNGSTRINGW(child_dlgs[n].l_id); + SendMessage(hTab,TCM_INSERTITEMW,n,(long)&it); + } + SendMessage(hTab,TCM_SETCURFOCUS,cfg_cur_tab,0); + RECT r; + GetClientRect(hTab,&r); + TabCtrl_AdjustRect(hTab,0,&r); + MapWindowPoints(hTab,wnd,(LPPOINT)&r,2); + + for(n=0;n<tabsize(child_dlgs);n++) + { + child_dlgs[n].wnd=w=WASABI_API_CREATEDIALOGW(child_dlgs[n].id,wnd,CfgChildProc); + SendMessage(MIDI_callback::GetMainWindow(),WM_WA_IPC,(WPARAM)w,IPC_USE_UXTHEME_FUNC); + SetWindowPos(w,0,r.left,r.top,r.right-r.left,r.bottom-r.top,SWP_NOZORDER); +#if defined(_WIN64) + SetWindowLong(w, DWLP_USER, IDC_CONFIG1 + n); +#else + SetWindowLong(w, DWL_USER, IDC_CONFIG1 + n); +#endif + ShowWindow(w,n==cfg_cur_tab ? SW_SHOW : SW_HIDE); + } + } + + { + UINT n; + for(n=0;n<NUM_BWNDS;n++) + { + _SendDlgItemMessage(wnd,bWnds[n].id,BM_SETCHECK,*bWnds[n].var,0); + } + } + + SetDlgItemTextA(wnd,_IDC_DLS,cfg_dls_file); + _SendDlgItemMessage(wnd,_IDC_DLS_CB,BM_SETCHECK,cfg_dls_active,0); + EnableWindow(_GetDlgItem(wnd,_IDC_DLS),cfg_dls_active); + EnableWindow(_GetDlgItem(wnd,_IDC_DLS_B),cfg_dls_active); + _SetDlgItemInt(wnd,_IDC_LOOP_T,cfg_loop_count,0); + _SendDlgItemMessage(wnd,_IDC_INFINITE,BM_SETCHECK,cfg_loop_infinite,0); + _SendDlgItemMessage(wnd,_IDC_LOOP_T,EM_LIMITTEXT,3,0); + _SendDlgItemMessage(wnd,_IDC_LOOP_SP,UDM_SETRANGE,0,MAKELONG(999,1)); + { + int n; + HWND w=_GetDlgItem(wnd,_IDC_LOOP); + for(n=0;n<3;n++) + { + SendMessageW(w,CB_ADDSTRING,0,(long)WASABI_API_LNGSTRINGW(loop_txt[n])); + } + SendMessage(w,CB_SETCURSEL,cfg_loop_type,0); + char tmp[10] = {0}; + w=_GetDlgItem(wnd,_IDC_FREQ); + for(n=0;n<3;n++) + { + int freq = 11025<<n; + _itoa(freq,tmp,10); + int idx = SendMessageA(w,CB_ADDSTRING,0,(long)tmp); // Must stay in ANSI + if (cfg_freq==freq) + SendMessageA(w,CB_SETCURSEL,idx,0); // Must stay in ANSI + } + + w=_GetDlgItem(wnd,_IDC_PORT); + { + int idx_driver=0; + MIDI_driver * driver; + while(driver = MIDI_driver::driver_enumerate(idx_driver++)) + { + int idx_device=0; + MIDI_device * device; + while(device = driver->device_enumerate(idx_device++)) + { + string_w temp; + temp+=driver->get_name(); + temp+=L" / "; + temp+=device->get_name(); + int idx_combo = SendMessageW(w,CB_ADDSTRING,0,(LPARAM)(const wchar_t*)temp); + SendMessage(w,CB_SETITEMDATA,idx_combo,(long)device); + + if (driver->get_guid() == cfg_driver && device->get_guid() == cfg_device) + { + SendMessage(w,CB_SETCURSEL,idx_combo,0); + ChangePort(wnd,device); + } + } + + } + } + + w=_GetDlgItem(wnd,_IDC_WAVEIN); + for(n=-1;n<(int)waveInGetNumDevs();n++) + { + WAVEINCAPS caps; + if (waveInGetDevCaps((UINT)n,&caps,sizeof(caps))==MMSYSERR_NOERROR && caps.dwFormats!=0) + { + int idx=SendMessage(w,CB_ADDSTRING,0,(long)caps.szPname); + SendMessage(w,CB_SETITEMDATA,idx,n); + if (n==cfg_wavein_dev) SendMessage(w,CB_SETCURSEL,idx,0); + } + } + + cfgVisMix(wnd,cfg_wavein_dev); + _SendDlgItemMessage(wnd,_IDC_WAVEIN_SRC,CB_SETCURSEL,cfg_wavein_src,0); + + w=_GetDlgItem(wnd,_IDC_PLAYBACK_METHOD); + SendMessageW(w,CB_ADDSTRING,0,(long)WASABI_API_LNGSTRINGW(IDS_STREAMED)); + SendMessageW(w,CB_ADDSTRING,0,(long)WASABI_API_LNGSTRINGW(IDS_IMMEDIATE)); + SendMessage(w,CB_SETCURSEL,cfg_playback_mode,0); + + w=_GetDlgItem(wnd,_IDC_SYSEX_LIST); + edit_sysex_table = cfg_sysex_table; + + { + char temp[128] = {0}; + n=0; + while(edit_sysex_table.print_preview(n++,temp)) SendMessage(w,LB_ADDSTRING,0,(long)temp); + } + + w=_GetDlgItem(wnd,_IDC_EXTS_LIST); + for(n=0;n<MIDI_core::FileTypes_GetNum();n++) + { + SendMessageA(w,LB_ADDSTRING,0,(long)MIDI_core::FileTypes_GetExtension(n)); // Must stay in ANSI + + if (cfg_ext_mask & (1<<n)) + SendMessage(w,LB_SETSEL,1,n); + } + SetDlgItemTextA(wnd,_IDC_EXTS_ED,cfg_extra_exts); + + + w=_GetDlgItem(wnd,_IDC_VOLMODE); + SendMessageW(w,CB_ADDSTRING,0,(long)WASABI_API_LNGSTRINGW(STRING_VOLUME_NONE)); + SendMessageW(w,CB_ADDSTRING,0,(long)WASABI_API_LNGSTRINGW(STRING_VOLUME_DRIVER_SPECIFIC)); + SendMessageW(w,CB_ADDSTRING,0,(long)WASABI_API_LNGSTRINGW(STRING_VOLUME_AUTO)); + + { + UINT id=0; + UINT n_devz=mixerGetNumDevs(); + UINT dev; + for(dev=0;dev<n_devz;dev++) + { + mixerGetID((HMIXEROBJ)dev,&id,MIXER_OBJECTF_MIXER); + + MIXERCAPSW capz; + mixerGetDevCapsW(id,&capz,sizeof(capz)); + + MIXERLINEW ml; + memset(&ml,0,sizeof(ml)); + ml.cbStruct=sizeof(ml); + ml.dwComponentType=MIXERLINE_COMPONENTTYPE_DST_SPEAKERS; + + mixerGetLineInfoW((HMIXEROBJ)id,&ml,MIXER_GETLINEINFOF_COMPONENTTYPE|MIXER_OBJECTF_MIXER); + + UINT con; + for(con=0;con<ml.cConnections;con++) + { + MIXERLINEW ml1; + memset(&ml1,0,sizeof(ml1)); + ml1.cbStruct=sizeof(ml); + ml1.dwSource=con; + mixerGetLineInfoW((HMIXEROBJ)id,&ml1,MIXER_GETLINEINFOF_SOURCE|MIXER_OBJECTF_MIXER); + if (n_devz==1) SendMessageW(w,CB_ADDSTRING,0,(long)ml1.szName); + else + { + string_w temp; + temp+=capz.szPname; + temp+=ml1.szShortName; + SendMessageW(w,CB_ADDSTRING,0,(long)(const wchar_t*)temp); + } + } + } + } + + SendMessage(w,CB_SETCURSEL,cfg_volmode,0); + + w=_GetDlgItem(wnd,_IDC_WAVEIN_CH); + SendMessageW(w,CB_ADDSTRING,0,(long)WASABI_API_LNGSTRINGW(STRING_MONO)); + SendMessageW(w,CB_ADDSTRING,0,(long)WASABI_API_LNGSTRINGW(STRING_STEREO)); + SendMessage(w,CB_SETCURSEL,cfg_wavein_ch-1,0); + w=_GetDlgItem(wnd,_IDC_WAVEIN_BPS); + + for(n=1;n<=4;n++) + { + wchar_t foo[32] = {0}; + wsprintfW(foo,WASABI_API_LNGSTRINGW(STRING_BIT_FMT),n<<3); + SendMessageW(w,CB_ADDSTRING,0,(long)foo); + } + SendMessage(w,CB_SETCURSEL,(cfg_wavein_bps>>3)-1,0); + + w=_GetDlgItem(wnd,_IDC_WAVEIN_SR); + for(n=0;n<sizeof(sr_tab)/sizeof(sr_tab[0]);n++) + { + char foo[8] = {0}; + _itoa(sr_tab[n],foo,10); + SendMessageA(w,CB_ADDSTRING,0,(long)foo); // Must stay in ANSI mode + } + + _SetDlgItemInt(wnd,_IDC_WAVEIN_SR,cfg_wavein_sr,0); + + w=_GetDlgItem(wnd,_IDC_HARDWARE_RESET); + SendMessageW(w,CB_ADDSTRING,0,(long)WASABI_API_LNGSTRINGW(IDS_NONE)); + SendMessageW(w,CB_ADDSTRING,0,(long)L"GM (General MIDI)"); + SendMessageW(w,CB_ADDSTRING,0,(long)L"GS (Roland)"); + SendMessageW(w,CB_ADDSTRING,0,(long)L"XG (Yamaha)"); + SendMessageW(w,CB_SETCURSEL,cfg_hardware_reset,0); + } +// f_num=0; + + _SendDlgItemMessage(wnd,_IDC_EOF_DELAY_SPIN,UDM_SETRANGE,0,MAKELONG(0x7FFF,0)); + _SetDlgItemInt(wnd,_IDC_EOF_DELAY,cfg_eof_delay,0); + + + return 1; + case WM_NOTIFY: + switch(wp) + { + case IDC_TAB: + if (((NMHDR*)lp)->code==TCN_SELCHANGE) + { + UINT n; + HWND hTab=((NMHDR*)lp)->hwndFrom; + cfg_cur_tab=SendMessage(hTab,TCM_GETCURSEL,0,0); + for(n=0;n<tabsize(child_dlgs);n++) + { + HWND w=cfgGetTab(IDC_CONFIG1+n); + ShowWindow(w,n==cfg_cur_tab ? SW_SHOW : SW_HIDE); + } + } + break; + } + break; + case WM_CMDNOTIFY://WM_COMMAND from one of child dialogs + if (wp>>16) + { + switch(lp) + { + case _IDC_SYSEX_LIST: + if (wp>>16==LBN_DBLCLK) + { + CfgProc(wnd,WM_CMDNOTIFY,0,_IDC_SYSEX_EDIT); + } + break; + case _IDC_WAVEIN: + if (wp>>16==CBN_SELCHANGE) + { + int d=_SendDlgItemMessage(wnd,_IDC_WAVEIN,CB_GETCURSEL,0,0); + if (d>=0) cfgVisMix(wnd,_SendDlgItemMessage(wnd,_IDC_WAVEIN,CB_GETITEMDATA,d,0)); + + } + break; + case _IDC_PORT: + if (wp>>16==CBN_SELCHANGE) + { + ChangePort(wnd,get_device(wnd)); + } + break; + } + } + else + { + switch(lp) + { + case _IDC_DLS_CB: + { + int checked = _SendDlgItemMessage(wnd,_IDC_DLS_CB,BM_GETCHECK,0,0); + EnableWindow(_GetDlgItem(wnd,_IDC_DLS),checked); + EnableWindow(_GetDlgItem(wnd,_IDC_DLS_B),checked); + } + break; + case _IDC_IMP_F: + if (edit_sysex_table.num_entries()>0) + { + char fn[MAX_PATH] = {0}; + if (DoOpenFile(wnd,fn,IDS_SYSEX_DATA,"SYX",0)) + { + HANDLE f=CreateFileA(fn,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,0,0); + if (f!=INVALID_HANDLE_VALUE) + { + DWORD sz=GetFileSize(f,0); + BYTE * temp=(BYTE*)malloc(sz); + if (temp) + { + DWORD br = 0; + ReadFile(f,temp,sz,&br,0); + edit_sysex_table.add_entry(temp,sz,0); + free(temp); + } + char tmp[128] = {0}; + edit_sysex_table.print_preview(edit_sysex_table.num_entries()-1,tmp); + _SendDlgItemMessage(wnd,_IDC_SYSEX_LIST,LB_ADDSTRING,0,(long)tmp); + CloseHandle(f); + } + else FILE_BLAH; + } + } + break; + case _IDC_IMP_PR: + // if (!edit_sysex_table.is_empty()) + { + char fn[MAX_PATH] = {0}; + fn[0]=0; + if (DoOpenFile(wnd,fn,IDS_MIDI_HARDWARE_PRESETS,"MHP",0)) + { + if (!edit_sysex_table.file_read(fn)) + { + FILE_BLAH; + } + } + } + break; + case _IDC_EXP_PR: + if (!edit_sysex_table.is_empty()) + { + char fn[MAX_PATH] = {0}; + fn[0]=0; + if (DoOpenFile(wnd,fn,IDS_MIDI_HARDWARE_PRESETS,"MHP",1)) + { + if (!edit_sysex_table.file_write(fn)) + { + FILE_BLAH; + } + } + } + break; + case _IDC_EXP_F: + if (!edit_sysex_table.is_empty()) + { + UINT ns=_SendDlgItemMessage(wnd,_IDC_SYSEX_LIST,LB_GETCURSEL,0,0); + if (ns!=-1) + { + char fn[MAX_PATH] = {0}; + fn[0]=0; + if (DoOpenFile(wnd,fn,IDS_SYSEX_DATA,"SYX",1)) + { + HANDLE f=CreateFileA(fn,GENERIC_WRITE,0,0,CREATE_ALWAYS,0,0); + if (f!=INVALID_HANDLE_VALUE) + { + BYTE * data; + int size = 0; + edit_sysex_table.get_entry(ns,&data,&size,0); + DWORD bw = 0; + WriteFile(f,data,size,&bw,0); + CloseHandle(f); + } + else FILE_BLAH; + } + } + } + + break; + case _IDC_SYSEX_EDIT: + if (!edit_sysex_table.is_empty()) + { + HWND w=_GetDlgItem(wnd,_IDC_SYSEX_LIST); + int n=SendMessage(w,LB_GETCURSEL,0,0); + if (n!=-1 && n<edit_sysex_table.num_entries()) + { + if (WASABI_API_DIALOGBOXPARAM(IDD_SYSEX,wnd,SysexProc,n)) + { + SendMessage(w,LB_DELETESTRING,n,0); + char tmp[128] = {0}; + edit_sysex_table.print_preview(n,tmp); + SendMessage(w,LB_INSERTSTRING,n,(long)tmp); + } + } + } + break; + case _IDC_SYSEX_ADD: +// if (!edit_sysex_table.is_empty()) + { + BYTE data[2]={0xF0,0xF7}; + edit_sysex_table.add_entry(data,2,0); + char tmp[128] = {0}; + edit_sysex_table.print_preview(edit_sysex_table.num_entries()-1,tmp); + _SendDlgItemMessage(wnd,_IDC_SYSEX_LIST,LB_ADDSTRING,0,(long)tmp); + } + break; + case _IDC_SYSEX_DELETE: + if (!edit_sysex_table.is_empty()) + { + HWND w=_GetDlgItem(wnd,_IDC_SYSEX_LIST); + int n=SendMessage(w,LB_GETCURSEL,0,0); + if (n != LB_ERR) + { + SendMessage(w,LB_DELETESTRING,n,0); + edit_sysex_table.remove_entry(n); + } + } + break; + case _IDC_SYSEX_UP: + { + HWND w=_GetDlgItem(wnd,_IDC_SYSEX_LIST); + int ns=SendMessage(w,LB_GETCURSEL,0,0); + if (ns==-1 || ns==0) break; + int ns1=ns-1; + BYTE * data; + int size,time; + edit_sysex_table.get_entry(ns,&data,&size,&time); + edit_sysex_table.insert_entry(ns1,data,size,time); + edit_sysex_table.remove_entry(ns+1); + + char tmp[128] = {0}; + SendMessage(w,LB_DELETESTRING,ns1,0); + edit_sysex_table.print_preview(ns,tmp); + SendMessage(w,LB_INSERTSTRING,ns,(long)tmp); + } + break; + case _IDC_SYSEX_DOWN: + { + HWND w=_GetDlgItem(wnd,_IDC_SYSEX_LIST); + int ns1=SendMessage(w,LB_GETCURSEL,0,0); + if (ns1==-1 || ns1==edit_sysex_table.num_entries()-1) break; + int ns=ns1+1; + BYTE * data; + int size,time; + edit_sysex_table.get_entry(ns,&data,&size,&time); + edit_sysex_table.insert_entry(ns1,data,size,time); + edit_sysex_table.remove_entry(ns+1); + + char tmp[128] = {0}; + SendMessage(w,LB_DELETESTRING,ns,0); + edit_sysex_table.print_preview(ns1,tmp); + SendMessage(w,LB_INSERTSTRING,ns1,(long)tmp); + } + break; + case _IDC_SAMPLING_ENABLED: + cfgVisStatus(wnd); + break; + case _IDC_DLS_B: + { + char tmp[MAX_PATH] = {0}; + GetDlgItemTextA(wnd,_IDC_DLS,tmp,MAX_PATH); + if (DoOpenFile(wnd,tmp,IDS_DLS_FILES,"DLS",0)) SetDlgItemTextA(wnd,_IDC_DLS,tmp); + } + break; + } + break; + + } + break; + case WM_COMMAND: + switch(wp) + { + case IDRESET: + if (MessageBoxW(wnd,WASABI_API_LNGSTRINGW(STRING_CONFIG_RESET),(wchar_t*)mod.description, + MB_ICONWARNING|MB_YESNO)==IDYES) + { + edit_sysex_table.reset(); + if (g_theConfig==wnd) g_theConfig=0; + EndDialog(wnd,666); + } + break; + case IDCANCEL: + edit_sysex_table.reset(); + if (g_theConfig==wnd) g_theConfig=0; + EndDialog(wnd,0); + break; + case IDOK: + { + UINT n; + for(n=0;n<NUM_BWNDS;n++) + { + *bWnds[n].var=!!_SendDlgItemMessage(wnd,bWnds[n].id,BM_GETCHECK,0,0); + } + } + cfg_wavein_src=_SendDlgItemMessage(wnd,_IDC_WAVEIN_SRC,CB_GETCURSEL,0,0); + cfg_playback_mode = _SendDlgItemMessage(wnd,_IDC_PLAYBACK_METHOD,CB_GETCURSEL,0,0); + { + MIDI_device * dev = get_device(wnd); + if (dev) + { + cfg_driver = dev->get_driver()->get_guid(); + cfg_device = dev->get_guid(); + } + } + + { + int t=_SendDlgItemMessage(wnd,_IDC_WAVEIN,CB_GETCURSEL,0,0); + if (t<0) cfg_wavein_dev=-1; + else cfg_wavein_dev = _SendDlgItemMessage(wnd,_IDC_WAVEIN,CB_GETITEMDATA,t,0); + } + + cfg_wavein_sr=_GetDlgItemInt(wnd,_IDC_WAVEIN_SR,0,0); + cfg_wavein_ch=_SendDlgItemMessage(wnd,_IDC_WAVEIN_CH,CB_GETCURSEL,0,0)+1; + cfg_wavein_bps=(_SendDlgItemMessage(wnd,_IDC_WAVEIN_BPS,CB_GETCURSEL,0,0)+1)<<3; + + cfg_loop_type=_SendDlgItemMessage(wnd,_IDC_LOOP,CB_GETCURSEL,0,0); + cfg_loop_count=_GetDlgItemInt(wnd,_IDC_LOOP_T,0,0); + cfg_loop_infinite=_SendDlgItemMessage(wnd,_IDC_INFINITE,BM_GETCHECK,0,0); + + { + int t=_SendDlgItemMessage(wnd,_IDC_FREQ,CB_GETCURSEL,0,0); + if (t<0) cfg_freq=22050; + else cfg_freq=11025<<t; + } + + cfg_dls_file.get_string().from_window(_GetDlgItem(wnd,_IDC_DLS)); + cfg_dls_active = _SendDlgItemMessage(wnd,_IDC_DLS_CB,BM_GETCHECK,0,0); + + cfg_sysex_table=edit_sysex_table; + edit_sysex_table.reset(); + + cfg_extra_exts.get_string().from_window(_GetDlgItem(wnd,_IDC_EXTS_ED)); + + { + int n; + HWND w=_GetDlgItem(wnd,_IDC_EXTS_LIST); + cfg_ext_mask=0; + for(n=0;n<MIDI_core::FileTypes_GetNum();n++) + if (SendMessage(w,LB_GETSEL,n,0)) cfg_ext_mask = (int)cfg_ext_mask | (1<<n); + } + + cfg_volmode=_SendDlgItemMessage(wnd,_IDC_VOLMODE,CB_GETCURSEL,0,0); + + cfg_eof_delay=_GetDlgItemInt(wnd,_IDC_EOF_DELAY,0,0); + + cfg_hardware_reset=_SendDlgItemMessage(wnd,_IDC_HARDWARE_RESET,CB_GETCURSEL,0,0); + + if (g_theConfig==wnd) g_theConfig=0; + EndDialog(wnd,1); + break; + } + break; + } + return 0; +} + + +int MIDI_core::Config(HWND p) +{ + int r; +db: + r=WASABI_API_DIALOGBOXW(IDD_CONFIG,p,CfgProc); + if (r==666) + { + cfg_var::config_reset(); + goto db; + } + return r; +} + +int DoAboutMessageBox(HWND parent, wchar_t* title, wchar_t* message) +{ + MSGBOXPARAMSW msgbx = {sizeof(MSGBOXPARAMSW),0}; + msgbx.lpszText = message; + msgbx.lpszCaption = title; + msgbx.lpszIcon = MAKEINTRESOURCEW(102); + msgbx.hInstance = GetModuleHandle(0); + msgbx.dwStyle = MB_USERICON; + msgbx.hwndOwner = parent; + return MessageBoxIndirectW(&msgbx); +} + +void About(HWND hwndParent) +{ + wchar_t message[1024] = {0}, text[1024] = {0}; + WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_MIDI_PLAYER_OLD,text,1024); + wsprintfW(message, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), + mod.description, __DATE__); + DoAboutMessageBox(hwndParent,text,message); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/core_api.h b/Src/Plugins/Input/in_midi/core_api.h new file mode 100644 index 00000000..e30979cf --- /dev/null +++ b/Src/Plugins/Input/in_midi/core_api.h @@ -0,0 +1,61 @@ +#ifndef __CORE_API_H +#define __CORE_API_H + +//ancient tempura header. yay. + +#ifndef ASSERT +#ifdef _DEBUG +#define ASSERT(x) if (!(x)) MessageBox(NULL,"ASSERT FAILED: " #x,"ASSERT FAILED in " __FILE__ ,MB_OK|MB_ICONSTOP); +#else +#define ASSERT(x) +#endif +#endif + +class WReader; + +class WPlayer_callback +{ + public: + virtual WReader *GetReader(char *url)=0; + virtual void Error(char *reason)=0; + virtual void Warning(char *warning)=0; + virtual void Status(char *status)=0; + virtual void TitleChange(char *new_title)=0; + virtual void InfoChange(char *new_info_str, int new_length)=0; + virtual void UrlChange(char *new_url)=0; +}; + +class WInfo_callback +{ + public: + virtual WReader *GetReader(char *url)=0; +}; + +class WReader +{ + protected: + WReader() : m_player(0) { } + public: + WPlayer_callback *m_player; + virtual char *GetDescription() { return 0; }; + virtual int Open(char *url, char *killswitch)=0; + virtual int Read(char *buffer, int length, char *killswitch)=0; + virtual int GetLength(void)=0; + virtual int CanSeek(void)=0; + virtual int Seek(int position, char *killswitch)=0; + virtual char *GetHeader(char *name) { return 0; } + virtual ~WReader() { } +}; + +#define READ_VER 0x100 +#define OF_VER 0x100 + +typedef struct +{ + int version; + char *description; + WReader *(*create)(); + int (*ismine)(char *url); +} reader_source; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/cvt.h b/Src/Plugins/Input/in_midi/cvt.h new file mode 100644 index 00000000..12d8f0c5 --- /dev/null +++ b/Src/Plugins/Input/in_midi/cvt.h @@ -0,0 +1,21 @@ +#ifndef STRICT +#define STRICT 1 +#endif +#include <windows.h> +#include <math.h> + +#include <malloc.h> + +#include <mmsystem.h> +#include <dsound.h> +#ifndef MF_NO_DMCRAP +#include <dmusici.h> +#include <dmusicf.h> +#endif + +class WReader; +struct CTempoMap; +struct CSysexMap; + +#include "utils.h" +#include "midifile.h" diff --git a/Src/Plugins/Input/in_midi/dmplugin.h b/Src/Plugins/Input/in_midi/dmplugin.h new file mode 100644 index 00000000..821ef7d6 --- /dev/null +++ b/Src/Plugins/Input/in_midi/dmplugin.h @@ -0,0 +1,281 @@ +/************************************************************************ +* * +* dmplugin.h -- This module contains the API for plugins for the * +* DirectMusic performance layer * +* * +* Copyright (c) 1998-1999 Microsoft Corporation * +* * +************************************************************************/ + +#ifndef _DMPLUGIN_ +#define _DMPLUGIN_ + +#include <windows.h> + +#define COM_NO_WINDOWS_H +#include <objbase.h> + +#include <mmsystem.h> +#include <dmusici.h> + +#include <pshpack8.h> + +#ifdef __cplusplus +extern "C" { +#endif + +interface IDirectMusicTrack; +interface IDirectMusicTool; +interface IDirectMusicTool8; +interface IDirectMusicTrack8; +interface IDirectMusicPerformance; +interface IDirectMusicPerformance8; +interface IDirectMusicSegment; +interface IDirectMusicSegment8; +interface IDirectMusicSegmentState; +interface IDirectMusicSegmentState8; +interface IDirectMusicGraph; +#ifndef __cplusplus +typedef interface IDirectMusicTrack IDirectMusicTrack; +typedef interface IDirectMusicTool IDirectMusicTool; +typedef interface IDirectMusicTool8 IDirectMusicTool8; +typedef interface IDirectMusicTrack8 IDirectMusicTrack8; +typedef interface IDirectMusicPerformance IDirectMusicPerformance; +typedef interface IDirectMusicPerformance8 IDirectMusicPerformance8; +typedef interface IDirectMusicSegment IDirectMusicSegment; +typedef interface IDirectMusicSegment8 IDirectMusicSegment8; +typedef interface IDirectMusicSegmentState IDirectMusicSegmentState; +typedef interface IDirectMusicSegmentState8 IDirectMusicSegmentState8; +typedef interface IDirectMusicGraph IDirectMusicGraph; +#endif + +typedef struct _DMUS_PMSG DMUS_PMSG; +typedef long MUSIC_TIME; + +/* Registry location for tools */ +#define DMUS_REGSTR_PATH_TOOLS "Software\\Microsoft\\DirectMusic\\Tools" + +/*//////////////////////////////////////////////////////////////////// +// IDirectMusicTool */ +#undef INTERFACE +#define INTERFACE IDirectMusicTool +DECLARE_INTERFACE_(IDirectMusicTool, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicTool */ + STDMETHOD(Init) (THIS_ IDirectMusicGraph* pGraph) PURE; + STDMETHOD(GetMsgDeliveryType) (THIS_ DWORD* pdwDeliveryType ) PURE; + STDMETHOD(GetMediaTypeArraySize)(THIS_ DWORD* pdwNumElements ) PURE; + STDMETHOD(GetMediaTypes) (THIS_ DWORD** padwMediaTypes, + DWORD dwNumElements) PURE; + STDMETHOD(ProcessPMsg) (THIS_ IDirectMusicPerformance* pPerf, + DMUS_PMSG* pPMSG) PURE; + STDMETHOD(Flush) (THIS_ IDirectMusicPerformance* pPerf, + DMUS_PMSG* pPMSG, + REFERENCE_TIME rtTime) PURE; +}; + +/*//////////////////////////////////////////////////////////////////// +// IDirectMusicTool8 */ +#undef INTERFACE +#define INTERFACE IDirectMusicTool8 +DECLARE_INTERFACE_(IDirectMusicTool8, IDirectMusicTool) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicTool */ + STDMETHOD(Init) (THIS_ IDirectMusicGraph* pGraph) PURE; + STDMETHOD(GetMsgDeliveryType) (THIS_ DWORD* pdwDeliveryType ) PURE; + STDMETHOD(GetMediaTypeArraySize)(THIS_ DWORD* pdwNumElements ) PURE; + STDMETHOD(GetMediaTypes) (THIS_ DWORD** padwMediaTypes, + DWORD dwNumElements) PURE; + STDMETHOD(ProcessPMsg) (THIS_ IDirectMusicPerformance* pPerf, + DMUS_PMSG* pPMSG) PURE; + STDMETHOD(Flush) (THIS_ IDirectMusicPerformance* pPerf, + DMUS_PMSG* pPMSG, + REFERENCE_TIME rtTime) PURE; + /* IDirectMusicTool8 */ + STDMETHOD(Clone) (THIS_ IDirectMusicTool ** ppTool) PURE; +}; + + +/* The following flags are sent in the IDirectMusicTrack::Play() method */ +/* inside the dwFlags parameter */ +typedef enum enumDMUS_TRACKF_FLAGS +{ + DMUS_TRACKF_SEEK = 1, /* set on a seek */ + DMUS_TRACKF_LOOP = 2, /* set on a loop (repeat) */ + DMUS_TRACKF_START = 4, /* set on first call to Play */ + DMUS_TRACKF_FLUSH = 8, /* set when this call is in response to a flush on the perfomance */ + DMUS_TRACKF_DIRTY = 0x10, /* set when the track should consider any cached values from a previous call to GetParam to be invalidated */ + /* The following flags are DX8 only. */ + DMUS_TRACKF_NOTIFY_OFF = 0x20, /* tells track not to send notifications. */ + DMUS_TRACKF_PLAY_OFF = 0x40, /* tells track not to play anything (but can still send notifications.) */ + DMUS_TRACKF_LOOPEND = 0x80, /* set when the end of range is also a loop end. */ + DMUS_TRACKF_STOP = 0x100, /* set when the end of range is also end of playing this segment. */ + DMUS_TRACKF_RECOMPOSE = 0x200, /* set to indicate the track should compose. */ + DMUS_TRACKF_CLOCK = 0x400, /* set when time parameters are in reference (clock) time. Only valid for PlayEx(). */ +} DMUS_TRACKF_FLAGS; + +/* The following flags are sent in the IDirectMusicTrack8::GetParamEx() and SetParamEx() methods */ +/* inside the dwFlags parameter */ +#define DMUS_TRACK_PARAMF_CLOCK 0x01 /* set when the time is measured is in reference (clock) time */ + +/*//////////////////////////////////////////////////////////////////// +// IDirectMusicTrack */ +#undef INTERFACE +#define INTERFACE IDirectMusicTrack +DECLARE_INTERFACE_(IDirectMusicTrack, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicTrack */ + STDMETHOD(Init) (THIS_ IDirectMusicSegment* pSegment) PURE; + STDMETHOD(InitPlay) (THIS_ IDirectMusicSegmentState* pSegmentState, + IDirectMusicPerformance* pPerformance, + void** ppStateData, + DWORD dwVirtualTrackID, + DWORD dwFlags) PURE; + STDMETHOD(EndPlay) (THIS_ void* pStateData) PURE; + STDMETHOD(Play) (THIS_ void* pStateData, + MUSIC_TIME mtStart, + MUSIC_TIME mtEnd, + MUSIC_TIME mtOffset, + DWORD dwFlags, + IDirectMusicPerformance* pPerf, + IDirectMusicSegmentState* pSegSt, + DWORD dwVirtualID) PURE; + STDMETHOD(GetParam) (THIS_ REFGUID rguidType, + MUSIC_TIME mtTime, + MUSIC_TIME* pmtNext, + void* pParam) PURE; + STDMETHOD(SetParam) (THIS_ REFGUID rguidType, + MUSIC_TIME mtTime, + void* pParam) PURE; + STDMETHOD(IsParamSupported) (THIS_ REFGUID rguidType) PURE; + STDMETHOD(AddNotificationType) (THIS_ REFGUID rguidNotificationType) PURE; + STDMETHOD(RemoveNotificationType) (THIS_ REFGUID rguidNotificationType) PURE; + STDMETHOD(Clone) (THIS_ MUSIC_TIME mtStart, + MUSIC_TIME mtEnd, + IDirectMusicTrack** ppTrack) PURE; +}; + +/*//////////////////////////////////////////////////////////////////// +// IDirectMusicTrack8 */ +#undef INTERFACE +#define INTERFACE IDirectMusicTrack8 +DECLARE_INTERFACE_(IDirectMusicTrack8, IDirectMusicTrack) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicTrack */ + STDMETHOD(Init) (THIS_ IDirectMusicSegment* pSegment) PURE; + STDMETHOD(InitPlay) (THIS_ IDirectMusicSegmentState* pSegmentState, + IDirectMusicPerformance* pPerformance, + void** ppStateData, + DWORD dwVirtualTrackID, + DWORD dwFlags) PURE; + STDMETHOD(EndPlay) (THIS_ void* pStateData) PURE; + STDMETHOD(Play) (THIS_ void* pStateData, + MUSIC_TIME mtStart, + MUSIC_TIME mtEnd, + MUSIC_TIME mtOffset, + DWORD dwFlags, + IDirectMusicPerformance* pPerf, + IDirectMusicSegmentState* pSegSt, + DWORD dwVirtualID) PURE; + STDMETHOD(GetParam) (THIS_ REFGUID rguidType, + MUSIC_TIME mtTime, + MUSIC_TIME* pmtNext, + void* pParam) PURE; + STDMETHOD(SetParam) (THIS_ REFGUID rguidType, + MUSIC_TIME mtTime, + void* pParam) PURE; + STDMETHOD(IsParamSupported) (THIS_ REFGUID rguidType) PURE; + STDMETHOD(AddNotificationType) (THIS_ REFGUID rguidNotificationType) PURE; + STDMETHOD(RemoveNotificationType) (THIS_ REFGUID rguidNotificationType) PURE; + STDMETHOD(Clone) (THIS_ MUSIC_TIME mtStart, + MUSIC_TIME mtEnd, + IDirectMusicTrack** ppTrack) PURE; + /* IDirectMusicTrack8 */ + STDMETHOD(PlayEx) (THIS_ void* pStateData, + REFERENCE_TIME rtStart, + REFERENCE_TIME rtEnd, + REFERENCE_TIME rtOffset, + DWORD dwFlags, + IDirectMusicPerformance* pPerf, + IDirectMusicSegmentState* pSegSt, + DWORD dwVirtualID) PURE; + STDMETHOD(GetParamEx) (THIS_ REFGUID rguidType, /* Command type. */ + REFERENCE_TIME rtTime, /* Time, in ref time if dwFlags == DMUS_TRACK_PARAMF_CLOCK. Otherwise, music time. */ + REFERENCE_TIME* prtNext, /* Time of next parameter, relative to rtTime, in music or clock time units. */ + void* pParam, /* Pointer to the parameter data. */ + void * pStateData, /* State data for track instance. */ + DWORD dwFlags) PURE; /* Control flags. */ + STDMETHOD(SetParamEx) (THIS_ REFGUID rguidType, + REFERENCE_TIME rtTime, + void* pParam, /* Pointer to the parameter data. */ + void * pStateData, /* State data for track instance. */ + DWORD dwFlags) PURE; /* Control flags. */ + STDMETHOD(Compose) (THIS_ IUnknown* pContext, /* Context for composition (song or segment) */ + DWORD dwTrackGroup, + IDirectMusicTrack** ppResultTrack) PURE; + STDMETHOD(Join) (THIS_ IDirectMusicTrack* pNewTrack, + MUSIC_TIME mtJoin, + IUnknown* pContext, /* Context for joining (song or segment) */ + DWORD dwTrackGroup, + IDirectMusicTrack** ppResultTrack) PURE; +}; + +/* CLSID's */ +DEFINE_GUID(CLSID_DirectMusicTempoTrack,0xd2ac2885, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicSeqTrack,0xd2ac2886, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicSysExTrack,0xd2ac2887, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicTimeSigTrack,0xd2ac2888, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicChordTrack,0xd2ac288b, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicCommandTrack,0xd2ac288c, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicStyleTrack,0xd2ac288d, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicMotifTrack,0xd2ac288e, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicSignPostTrack,0xf17e8672, 0xc3b4, 0x11d1, 0x87, 0xb, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicBandTrack,0xd2ac2894, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicChordMapTrack,0xd2ac2896, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicMuteTrack,0xd2ac2898, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* New CLSID's for DX8 */ +DEFINE_GUID(CLSID_DirectMusicScriptTrack,0x4108fa85, 0x3586, 0x11d3, 0x8b, 0xd7, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xb6); /* {4108FA85-3586-11d3-8BD7-00600893B1B6} */ +DEFINE_GUID(CLSID_DirectMusicMarkerTrack,0x55a8fd00, 0x4288, 0x11d3, 0x9b, 0xd1, 0x8a, 0xd, 0x61, 0xc8, 0x88, 0x35); +DEFINE_GUID(CLSID_DirectMusicSegmentTriggerTrack, 0xbae4d665, 0x4ea1, 0x11d3, 0x8b, 0xda, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xb6); /* {BAE4D665-4EA1-11d3-8BDA-00600893B1B6} */ +DEFINE_GUID(CLSID_DirectMusicLyricsTrack, 0x995c1cf5, 0x54ff, 0x11d3, 0x8b, 0xda, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xb6); /* {995C1CF5-54FF-11d3-8BDA-00600893B1B6} */ +DEFINE_GUID(CLSID_DirectMusicParamControlTrack, 0x4be0537b, 0x5c19, 0x11d3, 0x8b, 0xdc, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xb6); /* {4BE0537B-5C19-11d3-8BDC-00600893B1B6} */ +DEFINE_GUID(CLSID_DirectMusicMelodyFormulationTrack, 0xb0684266, 0xb57f, 0x11d2, 0x97, 0xf9, 0x0, 0xc0, 0x4f, 0xa3, 0x6e, 0x58); +DEFINE_GUID(CLSID_DirectMusicWaveTrack,0xeed36461, 0x9ea5, 0x11d3, 0x9b, 0xd1, 0x0, 0x80, 0xc7, 0x15, 0xa, 0x74); + +/* IID's */ +DEFINE_GUID(IID_IDirectMusicTrack, 0xf96029a1, 0x4282, 0x11d2, 0x87, 0x17, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(IID_IDirectMusicTool,0xd2ac28ba, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* Interface IDs for DX8 */ +/* changed interfaces (GUID only) */ +DEFINE_GUID(IID_IDirectMusicTool8, 0xe674303, 0x3b05, 0x11d3, 0x9b, 0xd1, 0xf9, 0xe7, 0xf0, 0xa0, 0x15, 0x36); +DEFINE_GUID(IID_IDirectMusicTrack8, 0xe674304, 0x3b05, 0x11d3, 0x9b, 0xd1, 0xf9, 0xe7, 0xf0, 0xa0, 0x15, 0x36); + +#ifdef __cplusplus +}; /* extern "C" */ +#endif + +#include <poppack.h> + +#endif /* #ifndef _DMPLUGIN_ */ diff --git a/Src/Plugins/Input/in_midi/dmusicc.h b/Src/Plugins/Input/in_midi/dmusicc.h new file mode 100644 index 00000000..3608babb --- /dev/null +++ b/Src/Plugins/Input/in_midi/dmusicc.h @@ -0,0 +1,784 @@ +/************************************************************************ +* * +* dmusicc.h -- This module defines the DirectMusic core API's * +* * +* Copyright (c) 1998-1999 Microsoft Corporation +* * +************************************************************************/ + +#ifndef _DMUSICC_ +#define _DMUSICC_ + +#include <windows.h> + +#define COM_NO_WINDOWS_H +#include <objbase.h> + +#include <mmsystem.h> + +#include "dls1.h" +#include "dmerror.h" +#include "dmdls.h" +#include "dsound.h" +#include "dmusbuff.h" + +#include <pshpack8.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef ULONGLONG SAMPLE_TIME; +typedef ULONGLONG SAMPLE_POSITION; +typedef SAMPLE_TIME *LPSAMPLE_TIME; + +#define DMUS_MAX_DESCRIPTION 128 +#define DMUS_MAX_DRIVER 128 + +typedef struct _DMUS_BUFFERDESC *LPDMUS_BUFFERDESC; +typedef struct _DMUS_BUFFERDESC +{ + DWORD dwSize; + DWORD dwFlags; + GUID guidBufferFormat; + DWORD cbBuffer; +} DMUS_BUFFERDESC; + +/* DMUS_EFFECT_ flags are used in the dwEffectFlags fields of both DMUS_PORTCAPS + * and DMUS_PORTPARAMS. + */ +#define DMUS_EFFECT_NONE 0x00000000 +#define DMUS_EFFECT_REVERB 0x00000001 +#define DMUS_EFFECT_CHORUS 0x00000002 +#define DMUS_EFFECT_DELAY 0x00000004 + +/* For DMUS_PORTCAPS dwClass + */ +#define DMUS_PC_INPUTCLASS (0) +#define DMUS_PC_OUTPUTCLASS (1) + +/* For DMUS_PORTCAPS dwFlags + */ +#define DMUS_PC_DLS (0x00000001) // Supports DLS downloading and DLS level 1. +#define DMUS_PC_EXTERNAL (0x00000002) // External MIDI module. +#define DMUS_PC_SOFTWARESYNTH (0x00000004) // Software synthesizer. +#define DMUS_PC_MEMORYSIZEFIXED (0x00000008) // Memory size is fixed. +#define DMUS_PC_GMINHARDWARE (0x00000010) // GM sound set is built in, no need to download. +#define DMUS_PC_GSINHARDWARE (0x00000020) // GS sound set is built in. +#define DMUS_PC_XGINHARDWARE (0x00000040) // XG sound set is built in. +#define DMUS_PC_DIRECTSOUND (0x00000080) // Connects to DirectSound via a DSound buffer. +#define DMUS_PC_SHAREABLE (0x00000100) // Synth can be actively shared by multiple apps at once. +#define DMUS_PC_DLS2 (0x00000200) // Supports DLS2 instruments. +#define DMUS_PC_AUDIOPATH (0x00000400) // Multiple outputs can be connected to DirectSound for audiopaths. +#define DMUS_PC_WAVE (0x00000800) // Supports streaming and one shot waves. + +#define DMUS_PC_SYSTEMMEMORY (0x7FFFFFFF) // Sample memory is system memory. + + +typedef struct _DMUS_PORTCAPS +{ + DWORD dwSize; + DWORD dwFlags; + GUID guidPort; + DWORD dwClass; + DWORD dwType; + DWORD dwMemorySize; + DWORD dwMaxChannelGroups; + DWORD dwMaxVoices; + DWORD dwMaxAudioChannels; + DWORD dwEffectFlags; + WCHAR wszDescription[DMUS_MAX_DESCRIPTION]; +} DMUS_PORTCAPS; + +typedef DMUS_PORTCAPS *LPDMUS_PORTCAPS; + +/* Values for DMUS_PORTCAPS dwType. This field indicates the underlying + * driver type of the port. + */ +#define DMUS_PORT_WINMM_DRIVER (0) +#define DMUS_PORT_USER_MODE_SYNTH (1) +#define DMUS_PORT_KERNEL_MODE (2) + +/* These flags (set in dwValidParams) indicate which other members of the */ +/* DMUS_PORTPARAMS are valid. */ +/* */ +#define DMUS_PORTPARAMS_VOICES 0x00000001 +#define DMUS_PORTPARAMS_CHANNELGROUPS 0x00000002 +#define DMUS_PORTPARAMS_AUDIOCHANNELS 0x00000004 +#define DMUS_PORTPARAMS_SAMPLERATE 0x00000008 +#define DMUS_PORTPARAMS_EFFECTS 0x00000020 +#define DMUS_PORTPARAMS_SHARE 0x00000040 +#define DMUS_PORTPARAMS_FEATURES 0x00000080 /* DirectX 8.0 and above */ + +typedef struct _DMUS_PORTPARAMS +{ + DWORD dwSize; + DWORD dwValidParams; + DWORD dwVoices; + DWORD dwChannelGroups; + DWORD dwAudioChannels; + DWORD dwSampleRate; + DWORD dwEffectFlags; + BOOL fShare; +} DMUS_PORTPARAMS7; + +typedef struct _DMUS_PORTPARAMS8 +{ + DWORD dwSize; + DWORD dwValidParams; + DWORD dwVoices; + DWORD dwChannelGroups; + DWORD dwAudioChannels; + DWORD dwSampleRate; + DWORD dwEffectFlags; + BOOL fShare; + DWORD dwFeatures; +} DMUS_PORTPARAMS8; + +#define DMUS_PORT_FEATURE_AUDIOPATH 0x00000001 /* Supports audiopath connection to DSound buffers. */ +#define DMUS_PORT_FEATURE_STREAMING 0x00000002 /* Supports streaming waves through the synth. */ + + +typedef DMUS_PORTPARAMS8 DMUS_PORTPARAMS; +typedef DMUS_PORTPARAMS *LPDMUS_PORTPARAMS; + +typedef struct _DMUS_SYNTHSTATS *LPDMUS_SYNTHSTATS; +typedef struct _DMUS_SYNTHSTATS8 *LPDMUS_SYNTHSTATS8; +typedef struct _DMUS_SYNTHSTATS +{ + DWORD dwSize; /* Size in bytes of the structure */ + DWORD dwValidStats; /* Flags indicating which fields below are valid. */ + DWORD dwVoices; /* Average number of voices playing. */ + DWORD dwTotalCPU; /* Total CPU usage as percent * 100. */ + DWORD dwCPUPerVoice; /* CPU per voice as percent * 100. */ + DWORD dwLostNotes; /* Number of notes lost in 1 second. */ + DWORD dwFreeMemory; /* Free memory in bytes */ + long lPeakVolume; /* Decibel level * 100. */ +} DMUS_SYNTHSTATS; + +typedef struct _DMUS_SYNTHSTATS8 +{ + DWORD dwSize; /* Size in bytes of the structure */ + DWORD dwValidStats; /* Flags indicating which fields below are valid. */ + DWORD dwVoices; /* Average number of voices playing. */ + DWORD dwTotalCPU; /* Total CPU usage as percent * 100. */ + DWORD dwCPUPerVoice; /* CPU per voice as percent * 100. */ + DWORD dwLostNotes; /* Number of notes lost in 1 second. */ + DWORD dwFreeMemory; /* Free memory in bytes */ + long lPeakVolume; /* Decibel level * 100. */ + DWORD dwSynthMemUse; /* Memory used by synth wave data */ +} DMUS_SYNTHSTATS8; + +#define DMUS_SYNTHSTATS_VOICES (1 << 0) +#define DMUS_SYNTHSTATS_TOTAL_CPU (1 << 1) +#define DMUS_SYNTHSTATS_CPU_PER_VOICE (1 << 2) +#define DMUS_SYNTHSTATS_LOST_NOTES (1 << 3) +#define DMUS_SYNTHSTATS_PEAK_VOLUME (1 << 4) +#define DMUS_SYNTHSTATS_FREE_MEMORY (1 << 5) + +#define DMUS_SYNTHSTATS_SYSTEMMEMORY DMUS_PC_SYSTEMMEMORY + +typedef struct _DMUS_WAVES_REVERB_PARAMS +{ + float fInGain; /* Input gain in dB (to avoid output overflows) */ + float fReverbMix; /* Reverb mix in dB. 0dB means 100% wet reverb (no direct signal) + Negative values gives less wet signal. + The coeficients are calculated so that the overall output level stays + (approximately) constant regardless of the ammount of reverb mix. */ + float fReverbTime; /* The reverb decay time, in milliseconds. */ + float fHighFreqRTRatio; /* The ratio of the high frequencies to the global reverb time. + Unless very 'splashy-bright' reverbs are wanted, this should be set to + a value < 1.0. + For example if dRevTime==1000ms and dHighFreqRTRatio=0.1 than the + decay time for high frequencies will be 100ms.*/ + +} DMUS_WAVES_REVERB_PARAMS; + +/* Note: Default values for Reverb are: + fInGain = 0.0dB (no change in level) + fReverbMix = -10.0dB (a reasonable reverb mix) + fReverbTime = 1000.0ms (one second global reverb time) + fHighFreqRTRatio = 0.001 (the ratio of the high frequencies to the global reverb time) +*/ + +typedef enum +{ + DMUS_CLOCK_SYSTEM = 0, + DMUS_CLOCK_WAVE = 1 +} DMUS_CLOCKTYPE; + +#define DMUS_CLOCKF_GLOBAL 0x00000001 + +typedef struct _DMUS_CLOCKINFO7 *LPDMUS_CLOCKINFO7; +typedef struct _DMUS_CLOCKINFO7 +{ + DWORD dwSize; + DMUS_CLOCKTYPE ctType; + GUID guidClock; /* Identifies this time source */ + WCHAR wszDescription[DMUS_MAX_DESCRIPTION]; +} DMUS_CLOCKINFO7; + +typedef struct _DMUS_CLOCKINFO8 *LPDMUS_CLOCKINFO8; +typedef struct _DMUS_CLOCKINFO8 +{ + DWORD dwSize; + DMUS_CLOCKTYPE ctType; + GUID guidClock; /* Identifies this time source */ + WCHAR wszDescription[DMUS_MAX_DESCRIPTION]; + DWORD dwFlags; +} DMUS_CLOCKINFO8; + +typedef DMUS_CLOCKINFO8 DMUS_CLOCKINFO; +typedef DMUS_CLOCKINFO *LPDMUS_CLOCKINFO; + +/* Default bus identifiers + * + * The first 17 are direct mappings to the destinations defined in both + * the MMA DLS Level 2 specification and the Microsoft Multi-Channel audio + * specification. + */ +#define DSBUSID_FIRST_SPKR_LOC 0 +#define DSBUSID_FRONT_LEFT 0 +#define DSBUSID_LEFT 0 /* Front left is also just left */ +#define DSBUSID_FRONT_RIGHT 1 +#define DSBUSID_RIGHT 1 /* Ditto front right */ +#define DSBUSID_FRONT_CENTER 2 +#define DSBUSID_LOW_FREQUENCY 3 +#define DSBUSID_BACK_LEFT 4 +#define DSBUSID_BACK_RIGHT 5 +#define DSBUSID_FRONT_LEFT_OF_CENTER 6 +#define DSBUSID_FRONT_RIGHT_OF_CENTER 7 +#define DSBUSID_BACK_CENTER 8 +#define DSBUSID_SIDE_LEFT 9 +#define DSBUSID_SIDE_RIGHT 10 +#define DSBUSID_TOP_CENTER 11 +#define DSBUSID_TOP_FRONT_LEFT 12 +#define DSBUSID_TOP_FRONT_CENTER 13 +#define DSBUSID_TOP_FRONT_RIGHT 14 +#define DSBUSID_TOP_BACK_LEFT 15 +#define DSBUSID_TOP_BACK_CENTER 16 +#define DSBUSID_TOP_BACK_RIGHT 17 +#define DSBUSID_LAST_SPKR_LOC 17 + +#define DSBUSID_IS_SPKR_LOC(id) ( ((id) >= DSBUSID_FIRST_SPKR_LOC) && ((id) <= DSBUSID_LAST_SPKR_LOC) ) + +/* These bus identifiers are for the standard DLS effect sends + */ +#define DSBUSID_REVERB_SEND 64 +#define DSBUSID_CHORUS_SEND 65 + +/* Dynamic bus identifiers start here. See the documentation for how + * synthesizers map the output of voices to static and dynamic + * bus identifiers. + */ +#define DSBUSID_DYNAMIC_0 512 + +/* Null bus, used to identify busses that have no function mapping. +*/ +#define DSBUSID_NULL 0xFFFFFFFF + +interface IDirectMusic; +interface IDirectMusic8; +interface IDirectMusicBuffer; +interface IDirectMusicPort; +interface IDirectMusicThru; +interface IReferenceClock; + +#ifndef __cplusplus + +typedef interface IDirectMusic IDirectMusic; +typedef interface IDirectMusic8 IDirectMusic8; +typedef interface IDirectMusicPort IDirectMusicPort; +typedef interface IDirectMusicBuffer IDirectMusicBuffer; +typedef interface IDirectMusicThru IDirectMusicThru; +typedef interface IReferenceClock IReferenceClock; + +#endif /* C++ */ + +typedef IDirectMusic *LPDIRECTMUSIC; +typedef IDirectMusic8 *LPDIRECTMUSIC8; +typedef IDirectMusicPort *LPDIRECTMUSICPORT; +typedef IDirectMusicBuffer *LPDIRECTMUSICBUFFER; + +#undef INTERFACE +#define INTERFACE IDirectMusic +DECLARE_INTERFACE_(IDirectMusic, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusic */ + STDMETHOD(EnumPort) (THIS_ DWORD dwIndex, + LPDMUS_PORTCAPS pPortCaps) PURE; + STDMETHOD(CreateMusicBuffer) (THIS_ LPDMUS_BUFFERDESC pBufferDesc, + LPDIRECTMUSICBUFFER *ppBuffer, + LPUNKNOWN pUnkOuter) PURE; + STDMETHOD(CreatePort) (THIS_ REFCLSID rclsidPort, + LPDMUS_PORTPARAMS pPortParams, + LPDIRECTMUSICPORT *ppPort, + LPUNKNOWN pUnkOuter) PURE; + STDMETHOD(EnumMasterClock) (THIS_ DWORD dwIndex, + LPDMUS_CLOCKINFO lpClockInfo) PURE; + STDMETHOD(GetMasterClock) (THIS_ LPGUID pguidClock, + IReferenceClock **ppReferenceClock) PURE; + STDMETHOD(SetMasterClock) (THIS_ REFGUID rguidClock) PURE; + STDMETHOD(Activate) (THIS_ BOOL fEnable) PURE; + STDMETHOD(GetDefaultPort) (THIS_ LPGUID pguidPort) PURE; + STDMETHOD(SetDirectSound) (THIS_ LPDIRECTSOUND pDirectSound, + HWND hWnd) PURE; +}; + +#undef INTERFACE +#define INTERFACE IDirectMusic8 +DECLARE_INTERFACE_(IDirectMusic8, IDirectMusic) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusic */ + STDMETHOD(EnumPort) (THIS_ DWORD dwIndex, + LPDMUS_PORTCAPS pPortCaps) PURE; + STDMETHOD(CreateMusicBuffer) (THIS_ LPDMUS_BUFFERDESC pBufferDesc, + LPDIRECTMUSICBUFFER *ppBuffer, + LPUNKNOWN pUnkOuter) PURE; + STDMETHOD(CreatePort) (THIS_ REFCLSID rclsidPort, + LPDMUS_PORTPARAMS pPortParams, + LPDIRECTMUSICPORT *ppPort, + LPUNKNOWN pUnkOuter) PURE; + STDMETHOD(EnumMasterClock) (THIS_ DWORD dwIndex, + LPDMUS_CLOCKINFO lpClockInfo) PURE; + STDMETHOD(GetMasterClock) (THIS_ LPGUID pguidClock, + IReferenceClock **ppReferenceClock) PURE; + STDMETHOD(SetMasterClock) (THIS_ REFGUID rguidClock) PURE; + STDMETHOD(Activate) (THIS_ BOOL fEnable) PURE; + STDMETHOD(GetDefaultPort) (THIS_ LPGUID pguidPort) PURE; + STDMETHOD(SetDirectSound) (THIS_ LPDIRECTSOUND pDirectSound, + HWND hWnd) PURE; + /* IDirectMusic8 */ + STDMETHOD(SetExternalMasterClock) + (THIS_ IReferenceClock *pClock) PURE; +}; + +#undef INTERFACE +#define INTERFACE IDirectMusicBuffer +DECLARE_INTERFACE_(IDirectMusicBuffer, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicBuffer */ + STDMETHOD(Flush) (THIS) PURE; + STDMETHOD(TotalTime) (THIS_ LPREFERENCE_TIME prtTime) PURE; + + STDMETHOD(PackStructured) (THIS_ REFERENCE_TIME rt, + DWORD dwChannelGroup, + DWORD dwChannelMessage) PURE; + + STDMETHOD(PackUnstructured) (THIS_ REFERENCE_TIME rt, + DWORD dwChannelGroup, + DWORD cb, + LPBYTE lpb) PURE; + + STDMETHOD(ResetReadPtr) (THIS) PURE; + STDMETHOD(GetNextEvent) (THIS_ LPREFERENCE_TIME prt, + LPDWORD pdwChannelGroup, + LPDWORD pdwLength, + LPBYTE *ppData) PURE; + + STDMETHOD(GetRawBufferPtr) (THIS_ LPBYTE *ppData) PURE; + STDMETHOD(GetStartTime) (THIS_ LPREFERENCE_TIME prt) PURE; + STDMETHOD(GetUsedBytes) (THIS_ LPDWORD pcb) PURE; + STDMETHOD(GetMaxBytes) (THIS_ LPDWORD pcb) PURE; + STDMETHOD(GetBufferFormat) (THIS_ LPGUID pGuidFormat) PURE; + + STDMETHOD(SetStartTime) (THIS_ REFERENCE_TIME rt) PURE; + STDMETHOD(SetUsedBytes) (THIS_ DWORD cb) PURE; +}; + +typedef IDirectMusicBuffer IDirectMusicBuffer8; +typedef IDirectMusicBuffer8 *LPDIRECTMUSICBUFFER8; + +#undef INTERFACE +#define INTERFACE IDirectMusicInstrument +DECLARE_INTERFACE_(IDirectMusicInstrument, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicInstrument */ + STDMETHOD(GetPatch) (THIS_ DWORD* pdwPatch) PURE; + STDMETHOD(SetPatch) (THIS_ DWORD dwPatch) PURE; +}; + +typedef IDirectMusicInstrument IDirectMusicInstrument8; +typedef IDirectMusicInstrument8 *LPDIRECTMUSICINSTRUMENT8; + +#undef INTERFACE +#define INTERFACE IDirectMusicDownloadedInstrument +DECLARE_INTERFACE_(IDirectMusicDownloadedInstrument, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicDownloadedInstrument */ + /* None at this time */ +}; + +typedef IDirectMusicDownloadedInstrument IDirectMusicDownloadedInstrument8; +typedef IDirectMusicDownloadedInstrument8 *LPDIRECTMUSICDOWNLOADEDINSTRUMENT8; + +#undef INTERFACE +#define INTERFACE IDirectMusicCollection +DECLARE_INTERFACE_(IDirectMusicCollection, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicCollection */ + STDMETHOD(GetInstrument) (THIS_ DWORD dwPatch, + IDirectMusicInstrument** ppInstrument) PURE; + STDMETHOD(EnumInstrument) (THIS_ DWORD dwIndex, + DWORD* pdwPatch, + LPWSTR pwszName, + DWORD dwNameLen) PURE; +}; + +typedef IDirectMusicCollection IDirectMusicCollection8; +typedef IDirectMusicCollection8 *LPDIRECTMUSICCOLLECTION8; + +#undef INTERFACE +#define INTERFACE IDirectMusicDownload +DECLARE_INTERFACE_(IDirectMusicDownload , IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicDownload */ + STDMETHOD(GetBuffer) (THIS_ void** ppvBuffer, + DWORD* pdwSize) PURE; +}; + +typedef IDirectMusicDownload IDirectMusicDownload8; +typedef IDirectMusicDownload8 *LPDIRECTMUSICDOWNLOAD8; + +#undef INTERFACE +#define INTERFACE IDirectMusicPortDownload +DECLARE_INTERFACE_(IDirectMusicPortDownload, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicPortDownload */ + STDMETHOD(GetBuffer) (THIS_ DWORD dwDLId, + IDirectMusicDownload** ppIDMDownload) PURE; + STDMETHOD(AllocateBuffer) (THIS_ DWORD dwSize, + IDirectMusicDownload** ppIDMDownload) PURE; + STDMETHOD(GetDLId) (THIS_ DWORD* pdwStartDLId, + DWORD dwCount) PURE; + STDMETHOD(GetAppend) (THIS_ DWORD* pdwAppend) PURE; + STDMETHOD(Download) (THIS_ IDirectMusicDownload* pIDMDownload) PURE; + STDMETHOD(Unload) (THIS_ IDirectMusicDownload* pIDMDownload) PURE; +}; + +typedef IDirectMusicPortDownload IDirectMusicPortDownload8; +typedef IDirectMusicPortDownload8 *LPDIRECTMUSICPORTDOWNLOAD8; + +/* Standard values for voice priorities. Numerically higher priorities are higher in priority. + * These priorities are used to set the voice priority for all voices on a channel. They are + * used in the dwPriority parameter of IDirectMusicPort::GetPriority and returned in the + * lpwPriority parameter of pdwPriority. + * + * These priorities are shared with DirectSound. + */ + +#ifndef _DIRECTAUDIO_PRIORITIES_DEFINED_ +#define _DIRECTAUDIO_PRIORITIES_DEFINED_ + +#define DAUD_CRITICAL_VOICE_PRIORITY (0xF0000000) +#define DAUD_HIGH_VOICE_PRIORITY (0xC0000000) +#define DAUD_STANDARD_VOICE_PRIORITY (0x80000000) +#define DAUD_LOW_VOICE_PRIORITY (0x40000000) +#define DAUD_PERSIST_VOICE_PRIORITY (0x10000000) + +/* These are the default priorities assigned if not overridden. By default priorities are + * equal across channel groups (e.g. channel 5 on channel group 1 has the same priority as + * channel 5 on channel group 2). + * + * In accordance with DLS level 1, channel 10 has the highest priority, followed by 1 through 16 + * except for 10. + */ +#define DAUD_CHAN1_VOICE_PRIORITY_OFFSET (0x0000000E) +#define DAUD_CHAN2_VOICE_PRIORITY_OFFSET (0x0000000D) +#define DAUD_CHAN3_VOICE_PRIORITY_OFFSET (0x0000000C) +#define DAUD_CHAN4_VOICE_PRIORITY_OFFSET (0x0000000B) +#define DAUD_CHAN5_VOICE_PRIORITY_OFFSET (0x0000000A) +#define DAUD_CHAN6_VOICE_PRIORITY_OFFSET (0x00000009) +#define DAUD_CHAN7_VOICE_PRIORITY_OFFSET (0x00000008) +#define DAUD_CHAN8_VOICE_PRIORITY_OFFSET (0x00000007) +#define DAUD_CHAN9_VOICE_PRIORITY_OFFSET (0x00000006) +#define DAUD_CHAN10_VOICE_PRIORITY_OFFSET (0x0000000F) +#define DAUD_CHAN11_VOICE_PRIORITY_OFFSET (0x00000005) +#define DAUD_CHAN12_VOICE_PRIORITY_OFFSET (0x00000004) +#define DAUD_CHAN13_VOICE_PRIORITY_OFFSET (0x00000003) +#define DAUD_CHAN14_VOICE_PRIORITY_OFFSET (0x00000002) +#define DAUD_CHAN15_VOICE_PRIORITY_OFFSET (0x00000001) +#define DAUD_CHAN16_VOICE_PRIORITY_OFFSET (0x00000000) + + +#define DAUD_CHAN1_DEF_VOICE_PRIORITY (DAUD_STANDARD_VOICE_PRIORITY | DAUD_CHAN1_VOICE_PRIORITY_OFFSET) +#define DAUD_CHAN2_DEF_VOICE_PRIORITY (DAUD_STANDARD_VOICE_PRIORITY | DAUD_CHAN2_VOICE_PRIORITY_OFFSET) +#define DAUD_CHAN3_DEF_VOICE_PRIORITY (DAUD_STANDARD_VOICE_PRIORITY | DAUD_CHAN3_VOICE_PRIORITY_OFFSET) +#define DAUD_CHAN4_DEF_VOICE_PRIORITY (DAUD_STANDARD_VOICE_PRIORITY | DAUD_CHAN4_VOICE_PRIORITY_OFFSET) +#define DAUD_CHAN5_DEF_VOICE_PRIORITY (DAUD_STANDARD_VOICE_PRIORITY | DAUD_CHAN5_VOICE_PRIORITY_OFFSET) +#define DAUD_CHAN6_DEF_VOICE_PRIORITY (DAUD_STANDARD_VOICE_PRIORITY | DAUD_CHAN6_VOICE_PRIORITY_OFFSET) +#define DAUD_CHAN7_DEF_VOICE_PRIORITY (DAUD_STANDARD_VOICE_PRIORITY | DAUD_CHAN7_VOICE_PRIORITY_OFFSET) +#define DAUD_CHAN8_DEF_VOICE_PRIORITY (DAUD_STANDARD_VOICE_PRIORITY | DAUD_CHAN8_VOICE_PRIORITY_OFFSET) +#define DAUD_CHAN9_DEF_VOICE_PRIORITY (DAUD_STANDARD_VOICE_PRIORITY | DAUD_CHAN9_VOICE_PRIORITY_OFFSET) +#define DAUD_CHAN10_DEF_VOICE_PRIORITY (DAUD_STANDARD_VOICE_PRIORITY | DAUD_CHAN10_VOICE_PRIORITY_OFFSET) +#define DAUD_CHAN11_DEF_VOICE_PRIORITY (DAUD_STANDARD_VOICE_PRIORITY | DAUD_CHAN11_VOICE_PRIORITY_OFFSET) +#define DAUD_CHAN12_DEF_VOICE_PRIORITY (DAUD_STANDARD_VOICE_PRIORITY | DAUD_CHAN12_VOICE_PRIORITY_OFFSET) +#define DAUD_CHAN13_DEF_VOICE_PRIORITY (DAUD_STANDARD_VOICE_PRIORITY | DAUD_CHAN13_VOICE_PRIORITY_OFFSET) +#define DAUD_CHAN14_DEF_VOICE_PRIORITY (DAUD_STANDARD_VOICE_PRIORITY | DAUD_CHAN14_VOICE_PRIORITY_OFFSET) +#define DAUD_CHAN15_DEF_VOICE_PRIORITY (DAUD_STANDARD_VOICE_PRIORITY | DAUD_CHAN15_VOICE_PRIORITY_OFFSET) +#define DAUD_CHAN16_DEF_VOICE_PRIORITY (DAUD_STANDARD_VOICE_PRIORITY | DAUD_CHAN16_VOICE_PRIORITY_OFFSET) + +#endif /* _DIRECTAUDIO_PRIORITIES_DEFINED_ */ + + +#undef INTERFACE +#define INTERFACE IDirectMusicPort +DECLARE_INTERFACE_(IDirectMusicPort, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicPort */ + /* */ + STDMETHOD(PlayBuffer) (THIS_ LPDIRECTMUSICBUFFER pBuffer) PURE; + STDMETHOD(SetReadNotificationHandle) (THIS_ HANDLE hEvent) PURE; + STDMETHOD(Read) (THIS_ LPDIRECTMUSICBUFFER pBuffer) PURE; + STDMETHOD(DownloadInstrument) (THIS_ IDirectMusicInstrument *pInstrument, + IDirectMusicDownloadedInstrument **ppDownloadedInstrument, + DMUS_NOTERANGE *pNoteRanges, + DWORD dwNumNoteRanges) PURE; + STDMETHOD(UnloadInstrument) (THIS_ IDirectMusicDownloadedInstrument *pDownloadedInstrument) PURE; + STDMETHOD(GetLatencyClock) (THIS_ IReferenceClock **ppClock) PURE; + STDMETHOD(GetRunningStats) (THIS_ LPDMUS_SYNTHSTATS pStats) PURE; + STDMETHOD(Compact) (THIS) PURE; + STDMETHOD(GetCaps) (THIS_ LPDMUS_PORTCAPS pPortCaps) PURE; + STDMETHOD(DeviceIoControl) (THIS_ DWORD dwIoControlCode, + LPVOID lpInBuffer, + DWORD nInBufferSize, + LPVOID lpOutBuffer, + DWORD nOutBufferSize, + LPDWORD lpBytesReturned, + LPOVERLAPPED lpOverlapped) PURE; + STDMETHOD(SetNumChannelGroups) (THIS_ DWORD dwChannelGroups) PURE; + STDMETHOD(GetNumChannelGroups) (THIS_ LPDWORD pdwChannelGroups) PURE; + STDMETHOD(Activate) (THIS_ BOOL fActive) PURE; + STDMETHOD(SetChannelPriority) (THIS_ DWORD dwChannelGroup, DWORD dwChannel, DWORD dwPriority) PURE; + STDMETHOD(GetChannelPriority) (THIS_ DWORD dwChannelGroup, DWORD dwChannel, LPDWORD pdwPriority) PURE; + STDMETHOD(SetDirectSound) (THIS_ LPDIRECTSOUND pDirectSound, LPDIRECTSOUNDBUFFER pDirectSoundBuffer) PURE; + STDMETHOD(GetFormat) (THIS_ LPWAVEFORMATEX pWaveFormatEx, LPDWORD pdwWaveFormatExSize, LPDWORD pdwBufferSize) PURE; +}; + +typedef IDirectMusicPort IDirectMusicPort8; +typedef IDirectMusicPort8 *LPDIRECTMUSICPORT8; + +#undef INTERFACE +#define INTERFACE IDirectMusicThru +DECLARE_INTERFACE_(IDirectMusicThru, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicThru + */ + STDMETHOD(ThruChannel) (THIS_ DWORD dwSourceChannelGroup, + DWORD dwSourceChannel, + DWORD dwDestinationChannelGroup, + DWORD dwDestinationChannel, + LPDIRECTMUSICPORT pDestinationPort) PURE; +}; + +typedef IDirectMusicThru IDirectMusicThru8; +typedef IDirectMusicThru8 *LPDIRECTMUSICTHRU8; + +#ifndef __IReferenceClock_INTERFACE_DEFINED__ +#define __IReferenceClock_INTERFACE_DEFINED__ + +DEFINE_GUID(IID_IReferenceClock,0x56a86897,0x0ad4,0x11ce,0xb0,0x3a,0x00,0x20,0xaf,0x0b,0xa7,0x70); + +#undef INTERFACE +#define INTERFACE IReferenceClock +DECLARE_INTERFACE_(IReferenceClock, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IReferenceClock */ + /* */ + + /* get the time now */ + STDMETHOD(GetTime) (THIS_ REFERENCE_TIME *pTime) PURE; + + /* ask for an async notification that a time has elapsed */ + STDMETHOD(AdviseTime) (THIS_ REFERENCE_TIME baseTime, /* base time */ + REFERENCE_TIME streamTime, /* stream offset time */ + HANDLE hEvent, /* advise via this event */ + DWORD * pdwAdviseCookie) PURE; /* where your cookie goes */ + + /* ask for an async periodic notification that a time has elapsed */ + STDMETHOD(AdvisePeriodic) (THIS_ REFERENCE_TIME startTime, /* starting at this time */ + REFERENCE_TIME periodTime, /* time between notifications */ + HANDLE hSemaphore, /* advise via a semaphore */ + DWORD * pdwAdviseCookie) PURE; /* where your cookie goes */ + + /* cancel a request for notification */ + STDMETHOD(Unadvise) (THIS_ DWORD dwAdviseCookie) PURE; +}; + +#endif /* __IReferenceClock_INTERFACE_DEFINED__ */ + +DEFINE_GUID(CLSID_DirectMusic,0x636b9f10,0x0c7d,0x11d1,0x95,0xb2,0x00,0x20,0xaf,0xdc,0x74,0x21); +DEFINE_GUID(CLSID_DirectMusicCollection,0x480ff4b0, 0x28b2, 0x11d1, 0xbe, 0xf7, 0x0, 0xc0, 0x4f, 0xbf, 0x8f, 0xef); +DEFINE_GUID(CLSID_DirectMusicSynth,0x58C2B4D0,0x46E7,0x11D1,0x89,0xAC,0x00,0xA0,0xC9,0x05,0x41,0x29); + +DEFINE_GUID(IID_IDirectMusic,0x6536115a,0x7b2d,0x11d2,0xba,0x18,0x00,0x00,0xf8,0x75,0xac,0x12); +DEFINE_GUID(IID_IDirectMusicBuffer,0xd2ac2878, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(IID_IDirectMusicPort, 0x08f2d8c9,0x37c2,0x11d2,0xb9,0xf9,0x00,0x00,0xf8,0x75,0xac,0x12); +DEFINE_GUID(IID_IDirectMusicThru, 0xced153e7, 0x3606, 0x11d2, 0xb9, 0xf9, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); +DEFINE_GUID(IID_IDirectMusicPortDownload,0xd2ac287a, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(IID_IDirectMusicDownload,0xd2ac287b, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(IID_IDirectMusicCollection,0xd2ac287c, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(IID_IDirectMusicInstrument,0xd2ac287d, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(IID_IDirectMusicDownloadedInstrument,0xd2ac287e, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + + +/* Alternate interface ID for IID_IDirectMusic, available in DX7 release and after. */ +DEFINE_GUID(IID_IDirectMusic2,0x6fc2cae1, 0xbc78, 0x11d2, 0xaf, 0xa6, 0x0, 0xaa, 0x0, 0x24, 0xd8, 0xb6); + +DEFINE_GUID(IID_IDirectMusic8,0x2d3629f7,0x813d,0x4939,0x85,0x08,0xf0,0x5c,0x6b,0x75,0xfd,0x97); + +#define IID_IDirectMusicThru8 IID_IDirectMusicThru +#define IID_IDirectMusicPortDownload8 IID_IDirectMusicPortDownload +#define IID_IDirectMusicDownload8 IID_IDirectMusicDownload +#define IID_IDirectMusicCollection8 IID_IDirectMusicCollection +#define IID_IDirectMusicInstrument8 IID_IDirectMusicInstrument +#define IID_IDirectMusicDownloadedInstrument8 IID_IDirectMusicDownloadedInstrument +#define IID_IDirectMusicPort8 IID_IDirectMusicPort + + +/* Property Query GUID_DMUS_PROP_GM_Hardware - Local GM set, no need to download + * Property Query GUID_DMUS_PROP_GS_Hardware - Local GS set, no need to download + * Property Query GUID_DMUS_PROP_XG_Hardware - Local XG set, no need to download + * Property Query GUID_DMUS_PROP_DLS1 - Support DLS level 1 + * Property Query GUID_DMUS_PROP_INSTRUMENT2 - Support new INSTRUMENT2 download format + * Property Query GUID_DMUS_PROP_XG_Capable - Support minimum requirements of XG + * Property Query GUID_DMUS_PROP_GS_Capable - Support minimum requirements of GS + * Property Query GUID_DMUS_PROP_SynthSink_DSOUND - Synthsink talks to DSound + * Property Query GUID_DMUS_PROP_SynthSink_WAVE - Synthsink talks to Wave device + * + * Item 0: Supported + * Returns a DWORD which is non-zero if the feature is supported + */ +DEFINE_GUID(GUID_DMUS_PROP_GM_Hardware, 0x178f2f24, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); +DEFINE_GUID(GUID_DMUS_PROP_GS_Hardware, 0x178f2f25, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); +DEFINE_GUID(GUID_DMUS_PROP_XG_Hardware, 0x178f2f26, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); +DEFINE_GUID(GUID_DMUS_PROP_XG_Capable, 0x6496aba1, 0x61b0, 0x11d2, 0xaf, 0xa6, 0x0, 0xaa, 0x0, 0x24, 0xd8, 0xb6); +DEFINE_GUID(GUID_DMUS_PROP_GS_Capable, 0x6496aba2, 0x61b0, 0x11d2, 0xaf, 0xa6, 0x0, 0xaa, 0x0, 0x24, 0xd8, 0xb6); +DEFINE_GUID(GUID_DMUS_PROP_DLS1, 0x178f2f27, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); +DEFINE_GUID(GUID_DMUS_PROP_DLS2, 0xf14599e5, 0x4689, 0x11d2, 0xaf, 0xa6, 0x0, 0xaa, 0x0, 0x24, 0xd8, 0xb6); +DEFINE_GUID(GUID_DMUS_PROP_INSTRUMENT2, 0x865fd372, 0x9f67, 0x11d2, 0x87, 0x2a, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(GUID_DMUS_PROP_SynthSink_DSOUND,0xaa97844, 0xc877, 0x11d1, 0x87, 0xc, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(GUID_DMUS_PROP_SynthSink_WAVE,0xaa97845, 0xc877, 0x11d1, 0x87, 0xc, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(GUID_DMUS_PROP_SampleMemorySize, 0x178f2f28, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); +DEFINE_GUID(GUID_DMUS_PROP_SamplePlaybackRate, 0x2a91f713, 0xa4bf, 0x11d2, 0xbb, 0xdf, 0x0, 0x60, 0x8, 0x33, 0xdb, 0xd8); + +/* Property Get/Set GUID_DMUS_PROP_WriteLatency + * + * Item 0: Synth buffer write latency, in milliseconds + * Get/Set SynthSink latency, the average time after the play _head that the next buffer gets written. + */ +DEFINE_GUID(GUID_DMUS_PROP_WriteLatency,0x268a0fa0, 0x60f2, 0x11d2, 0xaf, 0xa6, 0x0, 0xaa, 0x0, 0x24, 0xd8, 0xb6); + +/* Property Get/Set GUID_DMUS_PROP_WritePeriod + * + * Item 0: Synth buffer write period, in milliseconds + * Get/Set SynthSink buffer write period, time span between successive writes. + */ +DEFINE_GUID(GUID_DMUS_PROP_WritePeriod,0x268a0fa1, 0x60f2, 0x11d2, 0xaf, 0xa6, 0x0, 0xaa, 0x0, 0x24, 0xd8, 0xb6); + +/* Property Get GUID_DMUS_PROP_MemorySize + * + * Item 0: Memory size + * Returns a DWORD containing the total number of bytes of sample RAM + */ +DEFINE_GUID(GUID_DMUS_PROP_MemorySize, 0x178f2f28, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); + +/* Property Set GUID_DMUS_PROP_WavesReverb + * + * Item 0: DMUS_WAVES_REVERB structure + * Sets reverb parameters + */ +DEFINE_GUID(GUID_DMUS_PROP_WavesReverb,0x4cb5622, 0x32e5, 0x11d2, 0xaf, 0xa6, 0x0, 0xaa, 0x0, 0x24, 0xd8, 0xb6); + +/* Property Set GUID_DMUS_PROP_Effects + * + * Item 0: DWORD with effects flags. + * Get/Set effects bits, same as dwEffectFlags in DMUS_PORTPARAMS and DMUS_PORTCAPS: + * DMUS_EFFECT_NONE + * DMUS_EFFECT_REVERB + * DMUS_EFFECT_CHORUS + */ +DEFINE_GUID(GUID_DMUS_PROP_Effects, 0xcda8d611, 0x684a, 0x11d2, 0x87, 0x1e, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* Property Set GUID_DMUS_PROP_LegacyCaps + * + * Item 0: The MIDINCAPS or MIDIOUTCAPS which describes the port's underlying WinMM device. This property is only supported + * by ports which wrap WinMM devices. + */ + +DEFINE_GUID(GUID_DMUS_PROP_LegacyCaps,0xcfa7cdc2, 0x00a1, 0x11d2, 0xaa, 0xd5, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); + +/* Property Set GUID_DMUS_PROP_Volume + * + * Item 0: A long which contains an offset, in 1/100 dB, to be added to the final volume + * + */ +DEFINE_GUID(GUID_DMUS_PROP_Volume, 0xfedfae25L, 0xe46e, 0x11d1, 0xaa, 0xce, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); + +/* Min and Max values for setting volume with GUID_DMUS_PROP_Volume */ + +#define DMUS_VOLUME_MAX 2000 /* +20 dB */ +#define DMUS_VOLUME_MIN -20000 /* -200 dB */ + +#ifdef __cplusplus +}; /* extern "C" */ +#endif + +#include <poppack.h> + +#endif /* #ifndef _DMUSICC_ */ diff --git a/Src/Plugins/Input/in_midi/dmusicf.h b/Src/Plugins/Input/in_midi/dmusicf.h new file mode 100644 index 00000000..10908a9b --- /dev/null +++ b/Src/Plugins/Input/in_midi/dmusicf.h @@ -0,0 +1,2373 @@ +/************************************************************************ +* * +* dmusicf.h -- This module defines the DirectMusic file formats * +* * +* Copyright (c) 1998-1999 Microsoft Corporation +* * +************************************************************************/ + +#ifndef _DMUSICF_ +#define _DMUSICF_ + + +#include <windows.h> + +#define COM_NO_WINDOWS_H +#include <objbase.h> + +#include <mmsystem.h> + +#include <pshpack8.h> + +#ifdef __cplusplus +extern "C" { +#endif + +interface IDirectMusicCollection; +#ifndef __cplusplus +typedef interface IDirectMusicCollection IDirectMusicCollection; +#endif + +/* Common chunks */ + +#define DMUS_FOURCC_GUID_CHUNK mmioFOURCC('g','u','i','d') +#define DMUS_FOURCC_INFO_LIST mmioFOURCC('I','N','F','O') +#define DMUS_FOURCC_UNFO_LIST mmioFOURCC('U','N','F','O') +#define DMUS_FOURCC_UNAM_CHUNK mmioFOURCC('U','N','A','M') +#define DMUS_FOURCC_UART_CHUNK mmioFOURCC('U','A','R','T') +#define DMUS_FOURCC_UCOP_CHUNK mmioFOURCC('U','C','O','P') +#define DMUS_FOURCC_USBJ_CHUNK mmioFOURCC('U','S','B','J') +#define DMUS_FOURCC_UCMT_CHUNK mmioFOURCC('U','C','M','T') +#define DMUS_FOURCC_CATEGORY_CHUNK mmioFOURCC('c','a','t','g') +#define DMUS_FOURCC_VERSION_CHUNK mmioFOURCC('v','e','r','s') + +/* The following structures are used by the Tracks, and are the packed structures */ +/* that are passed to the Tracks inside the IStream. */ + + +typedef struct _DMUS_IO_SEQ_ITEM +{ + MUSIC_TIME mtTime; + MUSIC_TIME mtDuration; + DWORD dwPChannel; + short nOffset; + BYTE bStatus; + BYTE bByte1; + BYTE bByte2; +} DMUS_IO_SEQ_ITEM; + + +typedef struct _DMUS_IO_CURVE_ITEM +{ + MUSIC_TIME mtStart; + MUSIC_TIME mtDuration; + MUSIC_TIME mtResetDuration; + DWORD dwPChannel; + short nOffset; + short nStartValue; + short nEndValue; + short nResetValue; + BYTE bType; + BYTE bCurveShape; + BYTE bCCData; + BYTE bFlags; + /* Following was added for DX8. */ + WORD wParamType; /* RPN or NRPN parameter number. */ + WORD wMergeIndex; /* Allows multiple parameters to be merged (pitchbend, volume, and expression.) */ +} DMUS_IO_CURVE_ITEM; + + +typedef struct _DMUS_IO_TEMPO_ITEM +{ + MUSIC_TIME lTime; + double dblTempo; +} DMUS_IO_TEMPO_ITEM; + + +typedef struct _DMUS_IO_SYSEX_ITEM +{ + MUSIC_TIME mtTime; + DWORD dwPChannel; + DWORD dwSysExLength; +} DMUS_IO_SYSEX_ITEM; + +typedef DMUS_CHORD_KEY DMUS_CHORD_PARAM; /* DMUS_CHORD_KEY defined in dmusici.h */ + +typedef struct _DMUS_RHYTHM_PARAM +{ + DMUS_TIMESIGNATURE TimeSig; + DWORD dwRhythmPattern; +} DMUS_RHYTHM_PARAM; + +typedef struct _DMUS_TEMPO_PARAM +{ + MUSIC_TIME mtTime; + double dblTempo; +} DMUS_TEMPO_PARAM; + + +typedef struct _DMUS_MUTE_PARAM +{ + DWORD dwPChannel; + DWORD dwPChannelMap; + BOOL fMute; +} DMUS_MUTE_PARAM; + +/* Style chunks */ + +#define DMUS_FOURCC_STYLE_FORM mmioFOURCC('D','M','S','T') +#define DMUS_FOURCC_STYLE_CHUNK mmioFOURCC('s','t','y','h') +#define DMUS_FOURCC_PART_LIST mmioFOURCC('p','a','r','t') +#define DMUS_FOURCC_PART_CHUNK mmioFOURCC('p','r','t','h') +#define DMUS_FOURCC_NOTE_CHUNK mmioFOURCC('n','o','t','e') +#define DMUS_FOURCC_CURVE_CHUNK mmioFOURCC('c','r','v','e') +#define DMUS_FOURCC_MARKER_CHUNK mmioFOURCC('m','r','k','r') +#define DMUS_FOURCC_RESOLUTION_CHUNK mmioFOURCC('r','s','l','n') +#define DMUS_FOURCC_ANTICIPATION_CHUNK mmioFOURCC('a','n','p','n') +#define DMUS_FOURCC_PATTERN_LIST mmioFOURCC('p','t','t','n') +#define DMUS_FOURCC_PATTERN_CHUNK mmioFOURCC('p','t','n','h') +#define DMUS_FOURCC_RHYTHM_CHUNK mmioFOURCC('r','h','t','m') +#define DMUS_FOURCC_PARTREF_LIST mmioFOURCC('p','r','e','f') +#define DMUS_FOURCC_PARTREF_CHUNK mmioFOURCC('p','r','f','c') +#define DMUS_FOURCC_STYLE_PERS_REF_LIST mmioFOURCC('p','r','r','f') +#define DMUS_FOURCC_MOTIFSETTINGS_CHUNK mmioFOURCC('m','t','f','s') + +/* Flags used by variations: these make up the DWORDs in dwVariationChoices. */ + +/* These flags determine the types of chords supported by a given variation in DirectMusic */ +/* mode. The first seven flags (bits 1-7) are set if the variation supports major chords */ +/* rooted in scale positions, so, e.g., if bits 1, 2, and 4 are set, the variation */ +/* supports major chords rooted in the tonic, second, and fourth scale positions. The */ +/* next seven flags serve the same purpose, but for minor chords, and the following seven */ +/* flags serve the same purpose for chords that are not major or minor (e.g., SUS 4 */ +/* chords). Bits 22, 23, and 24 are set if the variation supports chords rooted in the */ +/* scale, chords rooted sharp of scale tones, and chords rooted flat of scale tones, */ +/* respectively. For example, to support a C# minor chord in the scale of C Major, */ +/* bits 8 (for tonic minor) and 24 (for sharp) need to be set. Bits 25, 26, an 27 handle */ +/* chords that are triads, 6th or 7th chords, and chords with extensions, respectively. */ +/* bits 28 and 29 handle chords that are followed by tonic and dominant chords, */ +/* respectively. */ +#define DMUS_VARIATIONF_MAJOR 0x0000007F /* Seven positions in the scale - major chords. */ +#define DMUS_VARIATIONF_MINOR 0x00003F80 /* Seven positions in the scale - minor chords. */ +#define DMUS_VARIATIONF_OTHER 0x001FC000 /* Seven positions in the scale - other chords. */ +#define DMUS_VARIATIONF_ROOT_SCALE 0x00200000 /* Handles chord roots in the scale. */ +#define DMUS_VARIATIONF_ROOT_FLAT 0x00400000 /* Handles flat chord roots (based on scale notes). */ +#define DMUS_VARIATIONF_ROOT_SHARP 0x00800000 /* Handles sharp chord roots (based on scale notes). */ +#define DMUS_VARIATIONF_TYPE_TRIAD 0x01000000 /* Handles simple chords - triads. */ +#define DMUS_VARIATIONF_TYPE_6AND7 0x02000000 /* Handles simple chords - 6 and 7. */ +#define DMUS_VARIATIONF_TYPE_COMPLEX 0x04000000 /* Handles complex chords. */ +#define DMUS_VARIATIONF_DEST_TO1 0x08000000 /* Handles transitions to 1 chord. */ +#define DMUS_VARIATIONF_DEST_TO5 0x10000000 /* Handles transitions to 5 chord. */ +#define DMUS_VARIATIONF_DEST_OTHER 0x40000000 /* Handles transitions to chords other than 1 . */ + +/* legacy mask for variation modes */ +#define DMUS_VARIATIONF_MODES 0xE0000000 +/* Bits 29 and 31 of the variation flags are the Mode bits. If both are 0, it's IMA. */ +/* If bit 29 is 1, it's Direct Music. */ +#define DMUS_VARIATIONF_MODES_EX (0x20000000 | 0x80000000) +#define DMUS_VARIATIONF_IMA25_MODE 0x00000000 +#define DMUS_VARIATIONF_DMUS_MODE 0x20000000 + +/* Set this if the part uses marker events */ +#define DMUS_PARTF_USE_MARKERS 0x1 +/* Set this if the part is allowed to switch only on chord-aligned markers */ +#define DMUS_PARTF_ALIGN_CHORDS 0x2 + +/* These specify if the marker event signals whether to stop a variation or start a +pattern/variation (or both), and whether new variations must align with a chord */ +#define DMUS_MARKERF_START 0x1 +#define DMUS_MARKERF_STOP 0x2 +#define DMUS_MARKERF_CHORD_ALIGN 0x4 + +/* if this flag is set, variation settings in a playing pattern-based track's state data will +persist in the track after it stops playing */ +#define DMUS_PATTERNF_PERSIST_CONTROL 0x1 + +/* These specify possible values for DMUS_IO_PARTREF.bRandomVariation + all but DMUS_VARIATIONT_SEQUENTIAL and DMUS_VARIATIONT_RANDOM are dx8. */ +typedef enum enumDMUS_VARIATIONT_TYPES +{ + DMUS_VARIATIONT_SEQUENTIAL = 0, /* Play sequential starting with variation 1. */ + DMUS_VARIATIONT_RANDOM = 1, /* Play randomly. */ + DMUS_VARIATIONT_RANDOM_START = 2, /* Play sequential starting with a random variation. */ + DMUS_VARIATIONT_NO_REPEAT = 3, /* Play randomly, but don't play the same variation twice. */ + DMUS_VARIATIONT_RANDOM_ROW = 4 /* Play randomly as a row: don't repeat any variation until all have played. */ +} DMUS_VARIATIONT_TYPES; + +#pragma pack(2) + +typedef struct _DMUS_IO_TIMESIG +{ + /* Time signatures define how many beats per measure, which note receives */ + /* the beat, and the grid resolution. */ + BYTE bBeatsPerMeasure; /* beats per measure (top of time sig) */ + BYTE bBeat; /* what note receives the beat (bottom of time sig.) */ + /* we can assume that 0 means 256th note */ + WORD wGridsPerBeat; /* grids per beat */ +} DMUS_IO_TIMESIG; + +typedef struct _DMUS_IO_STYLE +{ + DMUS_IO_TIMESIG timeSig; /* Styles have a default Time Signature */ + double dblTempo; +} DMUS_IO_STYLE; + +typedef struct _DMUS_IO_VERSION +{ + DWORD dwVersionMS; /* Version # high-order 32 bits */ + DWORD dwVersionLS; /* Version # low-order 32 bits */ +} DMUS_IO_VERSION; + +typedef struct _DMUS_IO_PATTERN +{ + DMUS_IO_TIMESIG timeSig; /* Patterns can override the Style's Time sig. */ + BYTE bGrooveBottom; /* bottom of groove range */ + BYTE bGrooveTop; /* top of groove range */ + WORD wEmbellishment; /* Fill, Break, Intro, End, Normal, Motif */ + WORD wNbrMeasures; /* length in measures */ + BYTE bDestGrooveBottom; /* bottom of groove range for next pattern */ + BYTE bDestGrooveTop; /* top of groove range for next pattern */ + DWORD dwFlags; /* various flags */ +} DMUS_IO_PATTERN; + +typedef struct _DMUS_IO_STYLEPART +{ + DMUS_IO_TIMESIG timeSig; /* can override pattern's */ + DWORD dwVariationChoices[32]; /* MOAW choice bitfield */ + GUID guidPartID; /* identifies the part */ + WORD wNbrMeasures; /* length of the Part */ + BYTE bPlayModeFlags; /* see PLAYMODE flags */ + BYTE bInvertUpper; /* inversion upper limit */ + BYTE bInvertLower; /* inversion lower limit */ + BYTE bPad[3]; /* for DWORD alignment */ + DWORD dwFlags; /* various flags */ +} DMUS_IO_STYLEPART; + +typedef struct _DMUS_IO_PARTREF +{ + GUID guidPartID; /* unique ID for matching up with parts */ + WORD wLogicalPartID; /* corresponds to port/device/midi channel OBSOLETE */ + BYTE bVariationLockID; /* parts with the same ID lock variations. */ + /* high bit is used to identify master Part */ + BYTE bSubChordLevel; /* tells which sub chord level this part wants */ + BYTE bPriority; /* 256 priority levels. Parts with lower priority */ + /* aren't played first when a device runs out of */ + /* notes */ + BYTE bRandomVariation; /* when set, matching variations play in random order */ + /* when clear, matching variations play sequentially */ + WORD wPad; /* not used */ + DWORD dwPChannel; /* replaces wLogicalPartID */ +} DMUS_IO_PARTREF; + +typedef struct _DMUS_IO_STYLENOTE +{ + MUSIC_TIME mtGridStart; /* when this note occurs */ + DWORD dwVariation; /* variation bits */ + MUSIC_TIME mtDuration; /* how long this note lasts */ + short nTimeOffset; /* offset from mtGridStart */ + WORD wMusicValue; /* Position in scale. */ + BYTE bVelocity; /* Note velocity. */ + BYTE bTimeRange; /* Range to randomize start time. */ + BYTE bDurRange; /* Range to randomize duration. */ + BYTE bVelRange; /* Range to randomize velocity. */ + BYTE bInversionID; /* Identifies inversion group to which this note belongs */ + BYTE bPlayModeFlags; /* Can override part */ + /* Following exists only under DX8 and on */ + BYTE bNoteFlags; /* values from DMUS_NOTEF_FLAGS */ +} DMUS_IO_STYLENOTE; + +typedef struct _DMUS_IO_STYLECURVE +{ + MUSIC_TIME mtGridStart; /* when this curve occurs */ + DWORD dwVariation; /* variation bits */ + MUSIC_TIME mtDuration; /* how long this curve lasts */ + MUSIC_TIME mtResetDuration;/* how long after the end of the curve to reset the curve */ + short nTimeOffset; /* offset from mtGridStart */ + short nStartValue; /* curve's start value */ + short nEndValue; /* curve's end value */ + short nResetValue; /* the value to which to reset the curve */ + BYTE bEventType; /* type of curve */ + BYTE bCurveShape; /* shape of curve */ + BYTE bCCData; /* CC# */ + BYTE bFlags; /* Bit 1=TRUE means to send nResetValue. Otherwise, don't. + Other bits are reserved. */ + /* Following was added for DX8. */ + WORD wParamType; /* RPN or NRPN parameter number. */ + WORD wMergeIndex; /* Allows multiple parameters to be merged (pitchbend, volume, and expression.) */ +} DMUS_IO_STYLECURVE; + +typedef struct _DMUS_IO_STYLEMARKER +{ + MUSIC_TIME mtGridStart; /* when this marker occurs */ + DWORD dwVariation; /* variation bits */ + WORD wMarkerFlags; /* how the marker is used */ +} DMUS_IO_STYLEMARKER; + +typedef struct _DMUS_IO_STYLERESOLUTION +{ + DWORD dwVariation; /* variation bits */ + WORD wMusicValue; /* Position in scale. */ + BYTE bInversionID; /* Identifies inversion group to which this note belongs */ + BYTE bPlayModeFlags; /* Can override part */ +} DMUS_IO_STYLERESOLUTION; + +typedef struct _DMUS_IO_STYLE_ANTICIPATION +{ + MUSIC_TIME mtGridStart; /* when this anticipation occurs */ + DWORD dwVariation; /* variation bits */ + short nTimeOffset; /* offset from mtGridStart */ + BYTE bTimeRange; /* Range to randomize start time. */ +} DMUS_IO_STYLE_ANTICIPATION; + +typedef struct _DMUS_IO_MOTIFSETTINGS +{ + DWORD dwRepeats; /* Number of repeats. By default, 0. */ + MUSIC_TIME mtPlayStart; /* Start of playback. By default, 0. */ + MUSIC_TIME mtLoopStart; /* Start of looping portion. By default, 0. */ + MUSIC_TIME mtLoopEnd; /* End of loop. Must be greater than mtLoopStart. Or, 0, indicating loop full motif. */ + DWORD dwResolution; /* Default resolution. */ +} DMUS_IO_MOTIFSETTINGS; + +#pragma pack() + + +/* +RIFF +( + 'DMST' // Style + <styh-ck> // Style header chunk + <guid-ck> // Every Style has a GUID + [<UNFO-list>] // Name, author, copyright info., comments + [<vers-ck>] // version chunk + <part-list>... // Array of parts in the Style, used by patterns + <pttn-list>... // Array of patterns in the Style + <DMBD-form>... // Array of bands in the Style + [<prrf-list>]...// Optional array of chord map references in the Style +) + + // <styh-ck> + styh + ( + <DMUS_IO_STYLE> + ) + + // <guid-ck> + guid + ( + <GUID> + ) + + // <vers-ck> + vers + ( + <DMUS_IO_VERSION> + ) + + // <part-list> + LIST + ( + 'part' + <prth-ck> // Part header chunk + [<UNFO-list>] // Name, author, copyright info., comments + [<note-ck>] // Optional chunk containing an array of notes in Part + [<crve-ck>] // Optional chunk containing an array of curves in Part + [<mrkr-ck>] // Optional chunk containing an array of markers in Part + [<rsln-ck>] // Optional chunk containing an array of variation resolutions in Part + [<anpn-ck>] // Optional chunk containing an array of resolution anticipations in Part + ) + + // <orth-ck> + prth + ( + <DMUS_IO_STYLEPART> + ) + + // <note-ck> + 'note' + ( + // sizeof DMUS_IO_STYLENOTE:DWORD + <DMUS_IO_STYLENOTE>... + ) + + // <crve-ck> + 'crve' + ( + // sizeof DMUS_IO_STYLECURVE:DWORD + <DMUS_IO_STYLECURVE>... + ) + + // <mrkr-ck> + 'mrkr' + ( + // sizeof DMUS_IO_STYLEMARKER:DWORD + <DMUS_IO_STYLEMARKER>... + ) + + // <rsln-ck> + 'rsln' + ( + // sizeof DMUS_IO_STYLERESOLUTION:DWORD + <DMUS_IO_STYLERESOLUTION>... + ) + + // <anpn-ck> + 'anpn' + ( + // sizeof DMUS_IO_STYLE_ANTICIPATION:DWORD + <DMUS_IO_STYLE_ANTICIPATION>... + ) + + // <pttn-list> + LIST + ( + 'pttn' + <ptnh-ck> // Pattern header chunk + <rhtm-ck> // Chunk containing an array of rhythms for chord matching + [<UNFO-list>] // Name, author, copyright info., comments + [<mtfs-ck>] // Motif settings chunk + [<DMBD-form>] // Optional band to be associated with the pattern (for motifs) + <pref-list>... // Array of part reference id's + ) + + // <ptnh-ck> + ptnh + ( + <DMUS_IO_PATTERN> + ) + + // <rhtm-ck> + 'rhtm' + ( + // DWORD's representing rhythms for chord matching based on number + // of measures in the pattern + ) + + + // pref-list + LIST + ( + 'pref' + <prfc-ck> // part ref chunk + ) + + // <prfc-ck> + prfc + ( + <DMUS_IO_PARTREF> + ) + + // <mtfs-ck> + mtfs + ( + <DMUS_IO_MOTIFSETTINGS> + ) + + // <prrf-list> + LIST + ( + 'prrf' + <DMRF-list>... // Array of Chordmap references + ) +*/ + +/* Pattern chunk, for use in Pattern tracks */ + +#define DMUS_FOURCC_PATTERN_FORM mmioFOURCC('D','M','P','T') + +/* +RIFF +( + 'DMPT' // Pattern + <styh-ck> // Style header chunk + <pttn-list> // The pattern, in single pattern format (includes DMUS_FOURCC_PART_LIST chunks) +) +*/ + + +/* Chord and command file formats */ + +/* These specify possible values for DMUS_IO_COMMAND.bRepeatMode (dx8) */ +typedef enum enumDMUS_PATTERNT_TYPES +{ + DMUS_PATTERNT_RANDOM = 0, /* Play randomly. (dx7 behavior) */ + DMUS_PATTERNT_REPEAT = 1, /* Repeat last pattern. */ + DMUS_PATTERNT_SEQUENTIAL = 2, /* Play sequential starting with first matching pattern. */ + DMUS_PATTERNT_RANDOM_START = 3, /* Play sequential starting with a random pattern. */ + DMUS_PATTERNT_NO_REPEAT = 4, /* Play randomly, but don't play the same pattern twice. */ + DMUS_PATTERNT_RANDOM_ROW = 5 /* Play randomly as a row: don't repeat any pattern until all have played. */ +} DMUS_PATTERNT_TYPES; + +#define DMUS_FOURCC_CHORDTRACK_LIST mmioFOURCC('c','o','r','d') +#define DMUS_FOURCC_CHORDTRACKHEADER_CHUNK mmioFOURCC('c','r','d','h') +#define DMUS_FOURCC_CHORDTRACKBODY_CHUNK mmioFOURCC('c','r','d','b') + +#define DMUS_FOURCC_COMMANDTRACK_CHUNK mmioFOURCC('c','m','n','d') + +typedef struct _DMUS_IO_CHORD +{ + WCHAR wszName[16]; /* Name of the chord */ + MUSIC_TIME mtTime; /* Time of this chord */ + WORD wMeasure; /* Measure this falls on */ + BYTE bBeat; /* Beat this falls on */ + BYTE bFlags; /* Various flags */ +} DMUS_IO_CHORD; + +typedef struct _DMUS_IO_SUBCHORD +{ + DWORD dwChordPattern; /* Notes in the subchord */ + DWORD dwScalePattern; /* Notes in the scale */ + DWORD dwInversionPoints; /* Where inversions can occur */ + DWORD dwLevels; /* Which levels are supported by this subchord */ + BYTE bChordRoot; /* Root of the subchord */ + BYTE bScaleRoot; /* Root of the scale */ +} DMUS_IO_SUBCHORD; + +typedef struct _DMUS_IO_COMMAND +{ + MUSIC_TIME mtTime; /* Time of this command */ + WORD wMeasure; /* Measure this falls on */ + BYTE bBeat; /* Beat this falls on */ + BYTE bCommand; /* Command type (see #defines below) */ + BYTE bGrooveLevel; /* Groove level (0 if command is not a groove) */ + BYTE bGrooveRange; /* Groove range */ + BYTE bRepeatMode; /* Used to control selection of patterns with same groove level */ +} DMUS_IO_COMMAND; + + +/* + + // <cord-list> + LIST + ( + 'cord' + <crdh-ck> + <crdb-ck> // Chord body chunk + ) + + // <crdh-ck> + crdh + ( + // Scale: dword (upper 8 bits for root, lower 24 for scale) + ) + + // <crdb-ck> + crdb + ( + // sizeof DMUS_IO_CHORD:dword + <DMUS_IO_CHORD> + // # of DMUS_IO_SUBCHORDS:dword + // sizeof DMUS_IO_SUBCHORDS:dword + // a number of <DMUS_IO_SUBCHORD> + ) + + + // <cmnd-list> + 'cmnd' + ( + //sizeof DMUS_IO_COMMAND: DWORD + <DMUS_IO_COMMAND>... + ) + +*/ + +/* File io for DirectMusic Tool and ToolGraph objects +*/ + +/* RIFF ids: */ + +#define DMUS_FOURCC_TOOLGRAPH_FORM mmioFOURCC('D','M','T','G') +#define DMUS_FOURCC_TOOL_LIST mmioFOURCC('t','o','l','l') +#define DMUS_FOURCC_TOOL_FORM mmioFOURCC('D','M','T','L') +#define DMUS_FOURCC_TOOL_CHUNK mmioFOURCC('t','o','l','h') + +/* io structures: */ + +typedef struct _DMUS_IO_TOOL_HEADER +{ + GUID guidClassID; /* Class id of tool. */ + long lIndex; /* Position in graph. */ + DWORD cPChannels; /* Number of items in channels array. */ + FOURCC ckid; /* chunk ID of tool's data chunk if 0 fccType valid. */ + FOURCC fccType; /* list type if NULL ckid valid. */ + DWORD dwPChannels[1]; /* Array of PChannels, size determined by cPChannels. */ +} DMUS_IO_TOOL_HEADER; + +/* +RIFF +( + 'DMTG' // DirectMusic ToolGraph chunk + [<guid-ck>] // GUID for ToolGraph + [<vers-ck>] // Optional version info + [<UNFO-list>] // Name, author, copyright info., comments + <toll-list> // List of Tools +) + + // <guid-ck> + 'guid' + ( + <GUID> + ) + + // <vers-ck> + vers + ( + <DMUS_IO_VERSION> + ) + + // <toll-list> + LIST + ( + 'toll' // Array of tools + <DMTL-form>... // Each tool is encapsulated in a RIFF chunk + ) + +// <DMTL-form> Tools are embedded in a graph. Theoretically, they can be saved as individual files too. +RIFF +( + 'DMTL' + <tolh-ck> + [<data>] // Tool data. Must be a RIFF readable chunk. +) + + // <tolh-ck> // Tool header chunk + ( + 'tolh' + <DMUS_IO_TOOL_HEADER> // Tool header + ) +*/ + +/* The AudioPath file carries everything for describing a specific audio path, + including Tool Graph and Buffer Descriptor. + This can even be used for configuring a complete performance. +*/ + +#define DMUS_FOURCC_AUDIOPATH_FORM mmioFOURCC('D','M','A','P') + +/* +RIFF +( + 'DMAP' // DirectMusic AudioPath chunk + [<guid-ck>] // GUID for this Audio Path configuration + [<vers-ck>] // Optional version info + [<UNFO-list>] // Name, author, copyright info., comments + [<DMTG-form>] // Optional ToolGraph + [<pcsl-list>] // Optional list of port configurations + [<dbfl-list>]...// Optional array of Dsound buffer descriptors +) +*/ + +#define DMUS_FOURCC_PORTCONFIGS_LIST mmioFOURCC('p','c','s','l') +#define DMUS_FOURCC_PORTCONFIG_LIST mmioFOURCC('p','c','f','l') +#define DMUS_FOURCC_PORTCONFIG_ITEM mmioFOURCC('p','c','f','h') +#define DMUS_FOURCC_PORTPARAMS_ITEM mmioFOURCC('p','p','r','h') +#define DMUS_FOURCC_DSBUFFER_LIST mmioFOURCC('d','b','f','l') +#define DMUS_FOURCC_DSBUFFATTR_ITEM mmioFOURCC('d','d','a','h') +#define DMUS_FOURCC_PCHANNELS_LIST mmioFOURCC('p','c','h','l') +#define DMUS_FOURCC_PCHANNELS_ITEM mmioFOURCC('p','c','h','h') + +typedef struct _DMUS_IO_PORTCONFIG_HEADER +{ + GUID guidPort; /* GUID of requested port. */ + DWORD dwPChannelBase; /* PChannel that this should start on. */ + DWORD dwPChannelCount; /* How many channels. */ + DWORD dwFlags; /* Various flags. */ +} DMUS_IO_PORTCONFIG_HEADER; + +#define DMUS_PORTCONFIGF_DRUMSON10 1 /* This port configured for drums on channel 10. */ +#define DMUS_PORTCONFIGF_USEDEFAULT 2 /* Use the default port. */ + +/* Each portconfig has one or more pchannel to buffer mappings. Each buffer + is identified by a guid. Each pchannel can map to one or more buffers. + This is defined with one or more DMUS_IO_PCHANNELTOBUFFER_HEADER + structures. Each defines a range of PChannels and the set of buffers + that they connect to. +*/ + +typedef struct _DMUS_IO_PCHANNELTOBUFFER_HEADER +{ + DWORD dwPChannelBase; /* PChannel that this should start on. */ + DWORD dwPChannelCount; /* How many PChannels. */ + DWORD dwBufferCount; /* How many buffers do these connect to. */ + DWORD dwFlags; /* Various flags. Currently reserved for future use. Must be 0. */ +} DMUS_IO_PCHANNELTOBUFFER_HEADER; + +/* Each buffer is represented by an DSBC form. This is wrapped by the + DMUS_IO_BUFFER_ATTRIBUTES_HEADER which identifies how to use the + buffer. In particular, it indicates whether this gets dynamically duplicated + or all references to this should share the same instance. + To resolve references, the unique GUID of the buffer is also stored + in this structure. +*/ + +typedef struct _DMUS_IO_BUFFER_ATTRIBUTES_HEADER +{ + GUID guidBufferID; /* Each buffer config has a unique ID. */ + DWORD dwFlags; /* Various flags. */ +} DMUS_IO_BUFFER_ATTRIBUTES_HEADER; + +/* DMUS_IO_BUFFER_ATTRIBUTES_HEADER.dwFlags: */ +#define DMUS_BUFFERF_SHARED 1 /* Share this with other audio paths, instead of creating unique copies. */ +#define DMUS_BUFFERF_DEFINED 2 /* Use one of the standard predefined buffers (see GUID_Buffer... in dmusici.h.) */ +#define DMUS_BUFFERF_MIXIN 8 /* This is a mixin buffer. */ + +/* + +LIST +( + 'pcsl' // Array of port configurations + <pcfl-list>... // One or more port configurations, each in a list chunk +) + +LIST +( + 'pcfl' // List container for one port configuration. + <pcfh-ck> // Portconfig header chunk. + <pprh-ck> // Port params, to be used to create the port. + [<dbfl-list>]...// Optional array of Dsound buffer descriptors + [<pchl-list>] // Optional list of pchannel to buffer assignments + +) + + // <pcfh-ck> // Port config header chunk + ( + 'pcfh' + <DMUS_IO_PORTCONFIG_HEADER> // Port config header + ) + + // <pprh-ck> // Port params header chunk + ( + 'pprh' + <DMUS_PORTPARAMS8> // Port params header + ) + +LIST +( + 'pchl' // List container for one or more pchannel to buffer assignments. + <pchh-ck>... // One or more pchannel to buffer assignment headers and data. + + // <pchh-ck> + ( + 'pchh' + <DMUS_IO_PCHANNELTOBUFFER_HEADER> // Description of PChannels + <GUID>... // Array of GUIDs defining the buffers they all connect to. + ) +) + +LIST +( + 'dbfl' // List container for one buffer and buffer attributes header. + <ddah-ck> // Buffer attributes header. + [<DSBC-form>] // Buffer configuration. Not required when header uses a predefined buffer type. + + // <ddah-ck> + ( + 'ddah' + <DMUS_IO_BUFFER_ATTRIBUTES_HEADER> // Buffer attributes. + ) +) +*/ + +/* File io for DirectMusic Band Track object */ + + +/* RIFF ids: */ +#define DMUS_FOURCC_BANDTRACK_FORM mmioFOURCC('D','M','B','T') +#define DMUS_FOURCC_BANDTRACK_CHUNK mmioFOURCC('b','d','t','h') +#define DMUS_FOURCC_BANDS_LIST mmioFOURCC('l','b','d','l') +#define DMUS_FOURCC_BAND_LIST mmioFOURCC('l','b','n','d') +#define DMUS_FOURCC_BANDITEM_CHUNK mmioFOURCC('b','d','i','h') +#define DMUS_FOURCC_BANDITEM_CHUNK2 mmioFOURCC('b','d','2','h') + +/* io structures */ +typedef struct _DMUS_IO_BAND_TRACK_HEADER +{ + BOOL bAutoDownload; /* Determines if Auto-Download is enabled. */ +} DMUS_IO_BAND_TRACK_HEADER; + +typedef struct _DMUS_IO_BAND_ITEM_HEADER +{ + MUSIC_TIME lBandTime; /* Position in track list. */ +} DMUS_IO_BAND_ITEM_HEADER; + +typedef struct _DMUS_IO_BAND_ITEM_HEADER2 +{ + MUSIC_TIME lBandTimeLogical; /* Position in track list. Time in the music with which band change is associated. */ + MUSIC_TIME lBandTimePhysical; /* Precise time band change will take effect. Should be close to logical time. */ +} DMUS_IO_BAND_ITEM_HEADER2; + +/* +RIFF +( + 'DMBT' // DirectMusic Band Track form-type + [<bdth-ck>] // Band track header + [<guid-ck>] // GUID for band track + [<vers-ck>] // Optional version info + [<UNFO-list>] // Name, author, copyright info., comments + <lbdl-list> // List of Band items +) + + // <bnth-ck> + 'bdth' + ( + <DMUS_IO_BAND_TRACK_HEADER> + ) + + // <guid-ck> + 'guid' + ( + <GUID> + ) + + // <vers-ck> + vers + ( + <DMUS_IO_VERSION> + ) + + // <lbdl-list> + LIST + ( + 'lbdl' + <lbnd-list>... // Array of bands, each encapsulated in a list chunk + ) + + // <lbnd-list> + LIST + ( + 'lbnd' + <bdih-ck> or <bd2h-ck> // bdih is a legacy format. bd2h is preferred for new content. + <DMBD-form> // Band + ) + + // <bdih-ck> or <bd2h-ck> // band item header + ( + <DMUS_IO_BAND_ITEM_HEADER> or <DMUS_IO_BAND_ITEM_HEADER2> // Band item header + ) +*/ + + +/* File io for DirectMusic Band object +*/ + +/* RIFF ids: */ + +#define DMUS_FOURCC_BAND_FORM mmioFOURCC('D','M','B','D') +#define DMUS_FOURCC_INSTRUMENTS_LIST mmioFOURCC('l','b','i','l') +#define DMUS_FOURCC_INSTRUMENT_LIST mmioFOURCC('l','b','i','n') +#define DMUS_FOURCC_INSTRUMENT_CHUNK mmioFOURCC('b','i','n','s') + +/* Flags for DMUS_IO_INSTRUMENT + */ +#define DMUS_IO_INST_PATCH (1 << 0) /* dwPatch is valid. */ +#define DMUS_IO_INST_BANKSELECT (1 << 1) /* dwPatch contains a valid Bank Select MSB and LSB part */ +#define DMUS_IO_INST_ASSIGN_PATCH (1 << 3) /* dwAssignPatch is valid */ +#define DMUS_IO_INST_NOTERANGES (1 << 4) /* dwNoteRanges is valid */ +#define DMUS_IO_INST_PAN (1 << 5) /* bPan is valid */ +#define DMUS_IO_INST_VOLUME (1 << 6 ) /* bVolume is valid */ +#define DMUS_IO_INST_TRANSPOSE (1 << 7) /* nTranspose is valid */ +#define DMUS_IO_INST_GM (1 << 8) /* Instrument is from GM collection */ +#define DMUS_IO_INST_GS (1 << 9) /* Instrument is from GS collection */ +#define DMUS_IO_INST_XG (1 << 10) /* Instrument is from XG collection */ +#define DMUS_IO_INST_CHANNEL_PRIORITY (1 << 11) /* dwChannelPriority is valid */ +#define DMUS_IO_INST_USE_DEFAULT_GM_SET (1 << 12) /* Always use the default GM set for this patch, */ + /* don't rely on the synth caps stating GM or GS in hardware. */ +#define DMUS_IO_INST_PITCHBENDRANGE (1 << 13) /* nPitchBendRange is valid */ + +/* io structures */ +typedef struct _DMUS_IO_INSTRUMENT +{ + DWORD dwPatch; /* MSB, LSB and Program change to define instrument */ + DWORD dwAssignPatch; /* MSB, LSB and Program change to assign to instrument when downloading */ + DWORD dwNoteRanges[4]; /* 128 bits; one for each MIDI note instrument needs to able to play */ + DWORD dwPChannel; /* PChannel instrument plays on */ + DWORD dwFlags; /* DMUS_IO_INST_ flags */ + BYTE bPan; /* Pan for instrument */ + BYTE bVolume; /* Volume for instrument */ + short nTranspose; /* Number of semitones to transpose notes */ + DWORD dwChannelPriority; /* Channel priority */ + short nPitchBendRange; /* Number of semitones shifted by pitch bend */ +} DMUS_IO_INSTRUMENT; + +/* +// <DMBD-form> bands can be embedded in other forms +RIFF +( + 'DMBD' // DirectMusic Band chunk + [<guid-ck>] // GUID for band + [<vers-ck>] // Optional version info + [<UNFO-list>] // Name, author, copyright info., comments + <lbil-list> // List of Instruments +) + + // <guid-ck> + 'guid' + ( + <GUID> + ) + + // <vers-ck> + vers + ( + <DMUS_IO_VERSION> + ) + + // <lbil-list> + LIST + ( + 'lbil' // Array of instruments + <lbin-list>... // Each instrument is encapsulated in a list + ) + + // <lbin-list> + LIST + ( + 'lbin' + <bins-ck> + [<DMRF-list>] // Optional reference to DLS Collection file. + ) + + // <bins-ck> // Instrument chunk + ( + 'bins' + <DMUS_IO_INSTRUMENT> // Instrument header + ) +*/ + +/* This RIFF id and io struct have been added to allow wave files (and the wave object) to + differentiate between streaming and one-shot waves, and to give a prefetch for streaming + waves */ + +#define DMUS_FOURCC_WAVEHEADER_CHUNK mmioFOURCC('w','a','v','h') + +typedef struct _DMUS_IO_WAVE_HEADER +{ + REFERENCE_TIME rtReadAhead; /* How far ahead in the stream wave data will be read (in REFERENCE_TIME). Ignored for one-shot waves. */ + DWORD dwFlags; /* Various flags, including whether this is a streaming wave and whether it can be invalidated. */ +} DMUS_IO_WAVE_HEADER; + + +/* File io for Wave track */ + +/* RIFF ids: */ + +#define DMUS_FOURCC_WAVETRACK_LIST mmioFOURCC('w','a','v','t') +#define DMUS_FOURCC_WAVETRACK_CHUNK mmioFOURCC('w','a','t','h') +#define DMUS_FOURCC_WAVEPART_LIST mmioFOURCC('w','a','v','p') +#define DMUS_FOURCC_WAVEPART_CHUNK mmioFOURCC('w','a','p','h') +#define DMUS_FOURCC_WAVEITEM_LIST mmioFOURCC('w','a','v','i') +#define DMUS_FOURCC_WAVE_LIST mmioFOURCC('w','a','v','e') +#define DMUS_FOURCC_WAVEITEM_CHUNK mmioFOURCC('w','a','i','h') + +/* This flag is included in DMUS_IO_WAVE_TRACK_HEADER.dwFlags. If set, the track will get its + variations from a pattern track, via GetParam(GUID_Variations). */ +#define DMUS_WAVETRACKF_SYNC_VAR 0x1 +/* This is also included in DMUS_IO_WAVE_TRACK_HEADER.dwFlags. If set, variation control + information will persist from one playback instance to the next.*/ +#define DMUS_WAVETRACKF_PERSIST_CONTROL 0x2 + +typedef struct _DMUS_IO_WAVE_TRACK_HEADER +{ + long lVolume; /* Gain, in 1/100th of dB, to be applied to all waves. Note: All gain values should be negative. */ + DWORD dwFlags; /* Flags, including whether this track syncs to a pattern track for its variations. */ +} DMUS_IO_WAVE_TRACK_HEADER; + +typedef struct _DMUS_IO_WAVE_PART_HEADER +{ + long lVolume; /* Gain, in 1/100th of dB, to be applied to all waves in wave part. Note: All gain values should be negative. */ + DWORD dwVariations; /* Variation mask for which of 32 variations */ + DWORD dwPChannel; /* PChannel */ + DWORD dwLockToPart; /* Part ID to lock to. */ + DWORD dwFlags; /* Flags, including stuff for managing how variations are chosen (in low-order nibble) */ + DWORD dwIndex; /* Index for distinguishing multiple parts on the same PChannel*/ +} DMUS_IO_WAVE_PART_HEADER; + +typedef struct _DMUS_IO_WAVE_ITEM_HEADER +{ + long lVolume; /* Gain, in 1/100th of dB. Note: All gain values should be negative. */ + long lPitch; /* Pitch offset in 1/100th of a semitone. */ + DWORD dwVariations; /* Variation flags for which of 32 variations this wave belongs to. */ + REFERENCE_TIME rtTime; /* Start time, in REFERENCE_TIME, if clock time track, or MUSIC_TIME for music time track. */ + REFERENCE_TIME rtStartOffset; /* Distance into wave to start playback, in reference time units. */ + REFERENCE_TIME rtReserved; /* Reserved field. */ + REFERENCE_TIME rtDuration; /* Duration, in REFERENCE_TIME or MUSIC_TIME, depending on track timing format. */ + MUSIC_TIME mtLogicalTime; /* If in music track format, this indicates the musical boundary where this belongs. Otherwise, ignored. */ + DWORD dwLoopStart; /* Start point for a looping wave. */ + DWORD dwLoopEnd; /* End point for a looping wave. */ + DWORD dwFlags; /* Various flags, including whether this is a streaming wave and whether it can be invalidated. */ +} DMUS_IO_WAVE_ITEM_HEADER; + +/* +LIST +{ + 'wavt' // Wave track chunk + <wath-ck> // Wave track header + <wavp-list>... // Array of Wave Parts +} + // <wath-ck> + 'wath' + { + <DMUS_IO_WAVE_TRACK_HEADER> + } + + // <wavp-list> + LIST + { + 'wavp' + <waph-ck> // Wave Part Header + <wavi-list> // List of wave items + } + + // <waph-ck> + 'waph' + { + <DMUS_IO_WAVE_PART_HEADER> + } + + // <wavi-list> + LIST + { + 'wavi' + <wave-list>... // Array of waves; each wave is encapsulated in a list + } + + // <wave-list> + LIST + { + 'wave' + <waih-ck> // Wave item header + <DMRF-list> // Reference to wave object + } + + // <waih-ck> + 'waih' + { + <DMUS_IO_WAVE_ITEM_HEADER> + } + +*/ + +/* File io for DirectMusic Container file. This embeds a set of related files. And, + in turn, it can be embedded within a segment or script file. +*/ + +#define DMUS_FOURCC_CONTAINER_FORM mmioFOURCC('D','M','C','N') +#define DMUS_FOURCC_CONTAINER_CHUNK mmioFOURCC('c','o','n','h') +#define DMUS_FOURCC_CONTAINED_ALIAS_CHUNK mmioFOURCC('c','o','b','a') +#define DMUS_FOURCC_CONTAINED_OBJECT_CHUNK mmioFOURCC('c','o','b','h') +#define DMUS_FOURCC_CONTAINED_OBJECTS_LIST mmioFOURCC('c','o','s','l') +#define DMUS_FOURCC_CONTAINED_OBJECT_LIST mmioFOURCC('c','o','b','l') + +typedef struct _DMUS_IO_CONTAINER_HEADER +{ + DWORD dwFlags; /* Flags. */ +} DMUS_IO_CONTAINER_HEADER; + +#define DMUS_CONTAINER_NOLOADS (1 << 1) /* Contained items are not loaded when the container is loaded. + Entries will be created in the loader (via SetObject) but + the actual objects will not be created until they are + specifically loaded at a later time. */ + +typedef struct _DMUS_IO_CONTAINED_OBJECT_HEADER +{ + GUID guidClassID; /* Class id of object. */ + DWORD dwFlags; /* Flags, for example DMUS_CONTAINED_OBJF_KEEP. */ + FOURCC ckid; /* chunk ID of track's data chunk if 0 fccType valid. */ + FOURCC fccType; /* list type if NULL ckid valid */ + /* Note that LIST:DMRF may be used for ckid and fccType in order to reference an + object instead of embedding it within the container. */ +} DMUS_IO_CONTAINED_OBJECT_HEADER; + +#define DMUS_CONTAINED_OBJF_KEEP 1 /* Keep the object cached in the loader after the container is released. */ + +/* +RIFF +( + 'DMCN' // DirectMusic Container chunk + <conh-ck> // Container header chunk + [<guid-ck>] // GUID for container + [<vers-ck>] // Optional version info + [<UNFO-list>] // Name, author, copyright info., comments + <cosl-list> // List of objects. +) + + // <conh-ck> + 'conh' + ( + <DMUS_IO_CONTAINER_HEADER> + ) + + // <guid-ck> + 'guid' + ( + <GUID> + ) + + // <vers-ck> + vers + ( + <DMUS_IO_VERSION> + ) + + LIST + ( + 'cosl' // Array of embedded objects. + <cobl-list>... // Each object is encapsulated in a LIST chunk + ) + + // <cobl-list> // Encapsulates one object + LIST + ( + 'cobl' + [<coba-ck>] // Alias. An alternative name by which this object is known + // within the container. + <cobh-ck> // Required header, includes CLASS ID for object. + [<data>] or <DMRF> // Object data of the type specified in <cobh-ck>. + // If DMRF, it is a reference of where to find the object. + // Otherwise, it could be any RIFF readable chunk in the + // exact same format as a file. The object will load + // itself from this data. + ) + + // <coba-ck> + 'coba' + ( + // Alias, stored as NULL terminated string of WCHARs + ) + + // <cobh-ck> + 'cobh' + ( + <DMUS_IO_CONTAINED_OBJECT_HEADER> + ) +*/ + +/* File io for DirectMusic Segment object */ + +/* RIFF ids: */ + +#define DMUS_FOURCC_SEGMENT_FORM mmioFOURCC('D','M','S','G') +#define DMUS_FOURCC_SEGMENT_CHUNK mmioFOURCC('s','e','g','h') +#define DMUS_FOURCC_TRACK_LIST mmioFOURCC('t','r','k','l') +#define DMUS_FOURCC_TRACK_FORM mmioFOURCC('D','M','T','K') +#define DMUS_FOURCC_TRACK_CHUNK mmioFOURCC('t','r','k','h') +#define DMUS_FOURCC_TRACK_EXTRAS_CHUNK mmioFOURCC('t','r','k','x') + +/* io structures:*/ + +typedef struct _DMUS_IO_SEGMENT_HEADER +{ + DWORD dwRepeats; /* Number of repeats. By default, 0. */ + MUSIC_TIME mtLength; /* Length, in music time. */ + MUSIC_TIME mtPlayStart; /* Start of playback. By default, 0. */ + MUSIC_TIME mtLoopStart; /* Start of looping portion. By default, 0. */ + MUSIC_TIME mtLoopEnd; /* End of loop. Must be greater than dwPlayStart. Or, 0, indicating loop full segment. */ + DWORD dwResolution; /* Default resolution. */ + /* Following added for DX8: */ + REFERENCE_TIME rtLength; /* Length, in reference time (overrides music time length.) */ + DWORD dwFlags; + DWORD dwReserved; /* Reserved. */ +} DMUS_IO_SEGMENT_HEADER; + +#define DMUS_SEGIOF_REFLENGTH 1 /* Use the time in rtLength for the segment length. */ + +typedef struct _DMUS_IO_TRACK_HEADER +{ + GUID guidClassID; /* Class id of track. */ + DWORD dwPosition; /* Position in track list. */ + DWORD dwGroup; /* Group bits for track. */ + FOURCC ckid; /* chunk ID of track's data chunk. */ + FOURCC fccType; /* list type if ckid is RIFF or LIST */ +} DMUS_IO_TRACK_HEADER; + +/* Additional parameters for the track header chunk, introduced in DX8 and + on, are stored in a separate chunk. */ + +typedef struct _DMUS_IO_TRACK_EXTRAS_HEADER +{ + DWORD dwFlags; /* DX8 Added flags for control tracks. */ + DWORD dwPriority; /* Priority for composition. */ +} DMUS_IO_TRACK_EXTRAS_HEADER; + +/* +RIFF +( + 'DMSG' // DirectMusic Segment chunk + <segh-ck> // Segment header chunk + [<guid-ck>] // GUID for segment + [<vers-ck>] // Optional version info + [<UNFO-list>] // Name, author, copyright info., comments + [<DMCN-form>] // Optional container of objects embedded in file. Must precede tracklist. + <trkl-list> // List of Tracks + [<DMTG-form>] // Optional ToolGraph + [<DMAP-form>] // Optional Audio Path +) + + // <segh-ck> + 'segh' + ( + <DMUS_IO_SEGMENT_HEADER> + ) + + // <guid-ck> + 'guid' + ( + <GUID> + ) + + // <vers-ck> + vers + ( + <DMUS_IO_VERSION> + ) + + // <trkl-list> + LIST + ( + 'trkl' // Array of tracks + <DMTK-form>... // Each track is encapsulated in a RIFF chunk + ) + + // <DMTK-form> // Tracks can be embedded in a segment or stored as separate files. + RIFF + ( + 'DMTK' + <trkh-ck> + [<trkx-ck>] // Optional track flags. + [<guid-ck>] // Optional GUID for track object instance (not to be confused with Class id in track header) + [<vers-ck>] // Optional version info + [<UNFO-list>] // Optional name, author, copyright info., comments + [<data>] // Track data. Must be a RIFF readable chunk. + ) + + // <trkh-ck> // Track header chunk + ( + 'trkh' + <DMUS_IO_TRACK_HEADER> // Track header + ) + + // <trkx-ck> // Track flags chunk + ( + 'trkx' + <DMUS_IO_TRACK_EXTRAS_HEADER> // DX8 Track flags header + ) +*/ + +/* File io for DirectMusic Song object */ +/* Note: Song file format is not supported in DX8. */ + +/* RIFF ids: */ + +#define DMUS_FOURCC_SONG_FORM mmioFOURCC('D','M','S','O') /* Entire song. */ +#define DMUS_FOURCC_SONG_CHUNK mmioFOURCC('s','n','g','h') /* Song header info. */ +#define DMUS_FOURCC_SONGSEGMENTS_LIST mmioFOURCC('s','e','g','l') /* List of embedded segments. */ +#define DMUS_FOURCC_SONGSEGMENT_LIST mmioFOURCC('s','s','g','l') /* Container for a segment or segment reference. */ +#define DMUS_FOURCC_TOOLGRAPHS_LIST mmioFOURCC('t','l','g','l') /* List of embedded tool graphs. */ +#define DMUS_FOURCC_SEGREFS_LIST mmioFOURCC('s','r','s','l') /* List of segment references. */ +#define DMUS_FOURCC_SEGREF_LIST mmioFOURCC('s','g','r','l') /* Container for a segment reference. */ +#define DMUS_FOURCC_SEGREF_CHUNK mmioFOURCC('s','g','r','h') /* Segment reference header. */ +#define DMUS_FOURCC_SEGTRANS_CHUNK mmioFOURCC('s','t','r','h') /* Set of transitions to this segment. */ +#define DMUS_FOURCC_TRACKREFS_LIST mmioFOURCC('t','r','s','l') /* Set of track references within the segment reference. */ +#define DMUS_FOURCC_TRACKREF_LIST mmioFOURCC('t','k','r','l') /* Container for a track reference. */ +#define DMUS_FOURCC_TRACKREF_CHUNK mmioFOURCC('t','k','r','h') /* Track reference header. */ + +/* io structures:*/ + +typedef struct _DMUS_IO_SONG_HEADER +{ + DWORD dwFlags; + DWORD dwStartSegID; /* Id of the segment that starts playback. */ +} DMUS_IO_SONG_HEADER; + +typedef struct _DMUS_IO_SEGREF_HEADER +{ + DWORD dwID; /* Each has a unique ID. Must be less than DMUS_SONG_MAXSEGID. */ + DWORD dwSegmentID; /* Optional segment to link to. */ + DWORD dwToolGraphID; /* Optional tool graph to use for processing. */ + DWORD dwFlags; /* Various control flags. Currently reserved for future use. Must be 0. */ + DWORD dwNextPlayID; /* ID of next segment, to chain segments into a song. */ +} DMUS_IO_SEGREF_HEADER; + + +typedef struct _DMUS_IO_TRACKREF_HEADER +{ + DWORD dwSegmentID; /* Which segment to find this in. */ + DWORD dwFlags; /* Reference control flags. */ +} DMUS_IO_TRACKREF_HEADER; + +/* Transition definition chunk defines a transition, using an optional transition template + segment. +*/ + +typedef struct _DMUS_IO_TRANSITION_DEF +{ + DWORD dwSegmentID; /* Segment the transition goes to. */ + DWORD dwTransitionID; /* Template segment to use for the transition. */ + DWORD dwPlayFlags; /* Flags to use for transition. */ +} DMUS_IO_TRANSITION_DEF; + +#define DMUS_SONG_MAXSEGID 0x7FFFFFFF /* Segment ids can not go higher than this. */ +#define DMUS_SONG_ANYSEG 0x80000000 /* Special ID to indicate any segment. */ +#define DMUS_SONG_NOSEG 0xFFFFFFFF /* Special ID to indicate no segment. */ +#define DMUS_SONG_NOFROMSEG 0x80000001 /* Special ID for dwSegmentID to indicate transition from nothing (or outside the song) into this segment. */ + +/* +RIFF +( + 'DMSO' // DirectMusic Song chunk + <sngh-ck> // Song header chunk + [<guid-ck>] // GUID for song + [<vers-ck>] // Optional version info + [<UNFO-list>] // Name, author, copyright info., comments + [<DMCN-form>] // Optional container of objects embedded in file. Must precede segment list. + <segl-list> // List of Segments + [<tlgl-list>] // Optional list of ToolGraphs + [<DMAP-form>] // Optional Audio Path - to be shared by all segments in song. + <srsl-list> // List of segment references. +) + + // <sngh-ck> + 'sngh' + ( + <DMUS_IO_SONG_HEADER> + ) + + // <segl-list> + LIST + ( + 'segl' // Array of segments + <ssgl-list>... // Each segment is wrapped in this. + ) + + // <ssgl-list> + LIST + ( + 'ssgl' // Segment container. + [DMSG-form] // Each segment is either a full embedded segment RIFF form. + [DMRF-list] // Or a reference to an external segment. + ) + + // <tlgl-list> + LIST + ( + 'tlgl' // Array of toolgraphs + <DMTG-form>... // Each toolgraph is a full RIFF form. + ) + + // <srsl-list> + LIST + ( + 'srsl' // Array of segment references + <sgrl-list>... // Each segment reference is contained in a RIFF list. + ) + + // <sgrl-list> // Segment reference container. + LIST + ( + 'sgrl' + <sgrh-ck> // Segment reference header chunk. + <segh-ck> // Segment header chunk. Defines the segment. + <UNFO-list> // Name, author, etc. Primarily for name, though, which is required for Song->GetSegment(). + [<strh-ck>] // Segment transition chunk. Defines how to do transitions from other segments. + [<trsl-list>] // List of track references, to create a segment from tracks in multiple segments. + ) + + // <sgrh-ck> // Segment reference header chunk + ( + 'sgrh' + <DMUS_IO_SEGREF_HEADER> // Segment reference header + ) + + // <strh-ck> // Segment transition chunk. + ( + 'strh' + <DMUS_IO_TRANSITION_DEF> // Default transition. + <DMUS_IO_TRANSITION_DEF>... // Additional transitions. + ) + + // <trsl-list> // Array of track references + ( + 'trsl' + <tkrl-list>... // Each track reference is multiple chunks in a tkrl list. + ) + + // <tkrl-list> // Track reference container + ( + 'tkrl' + <tkrh-ck> // Track reference header chunk. + <trkh-ck> // Normal track header chunk. + [<trkx-ck>] // Optional track flags. + ) + + // <tkrh-ck> // Track reference header chunk + ( + 'tkrh' + <DMUS_IO_TRACKREF_HEADER> // Track reference header + ) +*/ + +/* File io for DirectMusic reference chunk. + This is used to embed a reference to an object. +*/ + +/* RIFF ids: */ + +#define DMUS_FOURCC_REF_LIST mmioFOURCC('D','M','R','F') +#define DMUS_FOURCC_REF_CHUNK mmioFOURCC('r','e','f','h') +#define DMUS_FOURCC_DATE_CHUNK mmioFOURCC('d','a','t','e') +#define DMUS_FOURCC_NAME_CHUNK mmioFOURCC('n','a','m','e') +#define DMUS_FOURCC_FILE_CHUNK mmioFOURCC('f','i','l','e') + +typedef struct _DMUS_IO_REFERENCE +{ + GUID guidClassID; /* Class id is always required. */ + DWORD dwValidData; /* Flags. */ +} DMUS_IO_REFERENCE; + +/* +LIST +( + 'DMRF' // DirectMusic Reference chunk + <refh-ck> // Reference header chunk + [<guid-ck>] // Optional object GUID. + [<date-ck>] // Optional file date. + [<name-ck>] // Optional name. + [<file-ck>] // Optional file name. + [<catg-ck>] // Optional category name. + [<vers-ck>] // Optional version info. +) + + // <refh-ck> + 'refh' + ( + <DMUS_IO_REFERENCE> + ) + + // <guid-ck> + 'guid' + ( + <GUID> + ) + + // <date-ck> + date + ( + <FILETIME> + ) + + // <name-ck> + name + ( + // Name, stored as NULL terminated string of WCHARs + ) + + // <file-ck> + file + ( + // File name, stored as NULL terminated string of WCHARs + ) + + // <catg-ck> + catg + ( + // Category name, stored as NULL terminated string of WCHARs + ) + + // <vers-ck> + vers + ( + <DMUS_IO_VERSION> + ) +*/ + +/* Chord Maps */ + +/* runtime chunks */ +#define DMUS_FOURCC_CHORDMAP_FORM mmioFOURCC('D','M','P','R') +#define DMUS_FOURCC_IOCHORDMAP_CHUNK mmioFOURCC('p','e','r','h') +#define DMUS_FOURCC_SUBCHORD_CHUNK mmioFOURCC('c','h','d','t') +#define DMUS_FOURCC_CHORDENTRY_CHUNK mmioFOURCC('c','h','e','h') +#define DMUS_FOURCC_SUBCHORDID_CHUNK mmioFOURCC('s','b','c','n') +#define DMUS_FOURCC_IONEXTCHORD_CHUNK mmioFOURCC('n','c','r','d') +#define DMUS_FOURCC_NEXTCHORDSEQ_CHUNK mmioFOURCC('n','c','s','q') +#define DMUS_FOURCC_IOSIGNPOST_CHUNK mmioFOURCC('s','p','s','h') +#define DMUS_FOURCC_CHORDNAME_CHUNK mmioFOURCC('I','N','A','M') + +/* runtime list chunks */ +#define DMUS_FOURCC_CHORDENTRY_LIST mmioFOURCC('c','h','o','e') +#define DMUS_FOURCC_CHORDMAP_LIST mmioFOURCC('c','m','a','p') +#define DMUS_FOURCC_CHORD_LIST mmioFOURCC('c','h','r','d') +#define DMUS_FOURCC_CHORDPALETTE_LIST mmioFOURCC('c','h','p','l') +#define DMUS_FOURCC_CADENCE_LIST mmioFOURCC('c','a','d','e') +#define DMUS_FOURCC_SIGNPOSTITEM_LIST mmioFOURCC('s','p','s','t') + +#define DMUS_FOURCC_SIGNPOST_LIST mmioFOURCC('s','p','s','q') + +/* values for dwChord field of DMUS_IO_PERS_SIGNPOST */ +/* DMUS_SIGNPOSTF_ flags are also used in templates (DMUS_IO_SIGNPOST) */ +#define DMUS_SIGNPOSTF_A 1 +#define DMUS_SIGNPOSTF_B 2 +#define DMUS_SIGNPOSTF_C 4 +#define DMUS_SIGNPOSTF_D 8 +#define DMUS_SIGNPOSTF_E 0x10 +#define DMUS_SIGNPOSTF_F 0x20 +#define DMUS_SIGNPOSTF_LETTER (DMUS_SIGNPOSTF_A | DMUS_SIGNPOSTF_B | DMUS_SIGNPOSTF_C | DMUS_SIGNPOSTF_D | DMUS_SIGNPOSTF_E | DMUS_SIGNPOSTF_F) +#define DMUS_SIGNPOSTF_1 0x100 +#define DMUS_SIGNPOSTF_2 0x200 +#define DMUS_SIGNPOSTF_3 0x400 +#define DMUS_SIGNPOSTF_4 0x800 +#define DMUS_SIGNPOSTF_5 0x1000 +#define DMUS_SIGNPOSTF_6 0x2000 +#define DMUS_SIGNPOSTF_7 0x4000 +#define DMUS_SIGNPOSTF_ROOT (DMUS_SIGNPOSTF_1 | DMUS_SIGNPOSTF_2 | DMUS_SIGNPOSTF_3 | DMUS_SIGNPOSTF_4 | DMUS_SIGNPOSTF_5 | DMUS_SIGNPOSTF_6 | DMUS_SIGNPOSTF_7) +#define DMUS_SIGNPOSTF_CADENCE 0x8000 + +/* values for dwFlags field of DMUS_IO_CHORDMAP */ +#define DMUS_CHORDMAPF_VERSION8 1 /* Chordmap is version 8 or above. */ + +/* values for dwChord field of DMUS_IO_PERS_SIGNPOST */ +#define DMUS_SPOSTCADENCEF_1 2 /* Use the first cadence chord. */ +#define DMUS_SPOSTCADENCEF_2 4 /* Use the second cadence chord. */ + +/* run time data structs */ +typedef struct _DMUS_IO_CHORDMAP +{ + WCHAR wszLoadName[20]; + DWORD dwScalePattern; + DWORD dwFlags; /* Various flags. Only lower 16 bits are significant. */ +} DMUS_IO_CHORDMAP; + +typedef struct _DMUS_IO_CHORDMAP_SUBCHORD +{ + DWORD dwChordPattern; + DWORD dwScalePattern; + DWORD dwInvertPattern; + BYTE bChordRoot; + BYTE bScaleRoot; + WORD wCFlags; + DWORD dwLevels; /* parts or which subchord levels this chord supports */ +} DMUS_IO_CHORDMAP_SUBCHORD; + +/* Legacy name... */ +typedef DMUS_IO_CHORDMAP_SUBCHORD DMUS_IO_PERS_SUBCHORD; + +typedef struct _DMUS_IO_CHORDENTRY +{ + DWORD dwFlags; + WORD wConnectionID; /* replaces runtime "pointer to this" */ +} DMUS_IO_CHORDENTRY; + +typedef struct _DMUS_IO_NEXTCHORD +{ + DWORD dwFlags; + WORD nWeight; + WORD wMinBeats; + WORD wMaxBeats; + WORD wConnectionID; /* points to an ioChordEntry */ +} DMUS_IO_NEXTCHORD; + +typedef struct _DMUS_IO_CHORDMAP_SIGNPOST +{ + DWORD dwChords; /* 1bit per group */ + DWORD dwFlags; +} DMUS_IO_CHORDMAP_SIGNPOST; + +/* Legacy name... */ +typedef DMUS_IO_CHORDMAP_SIGNPOST DMUS_IO_PERS_SIGNPOST; + +/* +RIFF +( + 'DMPR' + <perh-ck> // Chord map header chunk + [<guid-ck>] // guid chunk + [<vers-ck>] // version chunk (two DWORDS) + [<UNFO-list>] // Unfo chunk + <chdt-ck> // subchord database + <chpl-list> // chord palette + <cmap-list> // chord map + <spsq-list> // signpost list + ) + +<cmap-list> ::= LIST('cmap' <choe-list> ) + +<choe-list> ::= LIST('choe' + <cheh-ck> // chord entry data + <chrd-list> // chord definition + <ncsq-ck> // connecting(next) chords + ) + +<chrd-list> ::= LIST('chrd' + <INAM-ck> // name of chord in wide char format + <sbcn-ck> // list of subchords composing chord + ) + +<chpl-list> ::= LIST('chpl' + <chrd-list> ... // chord definition + ) + +<spsq-list> ::== LIST('spsq' <spst-list> ... ) + +<spst-list> ::= LIST('spst' + <spsh-ck> + <chrd-list> + [<cade-list>] + ) + +<cade-list> ::= LIST('cade' <chrd-list> ...) + +<perh-ck> ::= perh(<DMUS_IO_CHORDMAP>) + +<chdt-ck> ::= chdt(<cbChordSize::WORD> + <DMUS_IO_PERS_SUBCHORD> ... ) + +<cheh-ck> ::= cheh(<DMUS_IO_CHORDENTRY>) + +<sbcn-ck> ::= sbcn(<cSubChordID:WORD> ...) + +<ncsq-ck> ::= ncsq(<wNextChordSize:WORD> + <DMUS_IO_NEXTCHORD>...) + +<spsh-ck> ::= spsh(<DMUS_IO_PERS_SIGNPOST>) + +*/ + +/* File io for DirectMusic Script object */ + +/* RIFF ids: */ + +#define DMUS_FOURCC_SCRIPT_FORM mmioFOURCC('D','M','S','C') +#define DMUS_FOURCC_SCRIPT_CHUNK mmioFOURCC('s','c','h','d') +#define DMUS_FOURCC_SCRIPTVERSION_CHUNK mmioFOURCC('s','c','v','e') +#define DMUS_FOURCC_SCRIPTLANGUAGE_CHUNK mmioFOURCC('s','c','l','a') +#define DMUS_FOURCC_SCRIPTSOURCE_CHUNK mmioFOURCC('s','c','s','r') + +/* io structures:*/ + +typedef struct _DMUS_IO_SCRIPT_HEADER +{ + DWORD dwFlags; /* DMUS_SCRIPTIOF_ flags */ +} DMUS_IO_SCRIPT_HEADER; + +#define DMUS_SCRIPTIOF_LOAD_ALL_CONTENT (1 << 0) + /* If set, when the script loads it will also load all the content in its container. */ +#define DMUS_SCRIPTIOF_DOWNLOAD_ALL_SEGMENTS (1 << 1) + /* If set and LOAD_ALL_CONTENT is also set, when the script initializes it will also download all the segments in its container. + If set and LOAD_ALL_CONTENT is not set, when the script calls segment.Load on a segment then the segment will also be downloaded. + If not set, the script must manually download and unload by calling segment.DownloadSoundData and segment.UnloadSoundData. */ + +/* +RIFF +( + 'DMSC' // DirectMusic Script chunk + <schd-ck> // Script header chunk + [<guid-ck>] // GUID for script + [<vers-ck>] // Optional version info + [<UNFO-list>] // Name, author, copyright info., comments + <scve-ck> // Version of DirectMusic this script was authored to run against + <DMCN-form> // Container of content referenced by the script. + <scla-ck> // ActiveX scripting language in which the script is written + <scsr-ck> or <DMRF> // The script's source code. + // If scsr-ck, the source is embedding in the chunk. + // If DMRF, it is a reference of where to find a text file with the source. + // Class id (guidClassID in DMUS_IO_REFERENCE) must be GUID_NULL because + // this text file is not a DirectMusic object in its own right. +) + + // <schd-ck> + 'schd' + ( + <DMUS_FOURCC_SCRIPT_CHUNK> + ) + + // <guid-ck> + 'guid' + ( + <GUID> + ) + + // <vers-ck> + vers + ( + <DMUS_IO_VERSION> + ) + + // <scve-ck> + scve + ( + <DMUS_IO_VERSION> + ) + + 'scla' + ( + // Language name, stored as NULL terminated string of WCHARs + ) + + 'scsr' + ( + // Source code, stored as NULL terminated string of WCHARs + ) +*/ + +/* Signpost tracks */ + +#define DMUS_FOURCC_SIGNPOST_TRACK_CHUNK mmioFOURCC( 's', 'g', 'n', 'p' ) + + +typedef struct _DMUS_IO_SIGNPOST +{ + MUSIC_TIME mtTime; + DWORD dwChords; + WORD wMeasure; +} DMUS_IO_SIGNPOST; + +/* + + // <sgnp-list> + 'sgnp' + ( + //sizeof DMUS_IO_SIGNPOST: DWORD + <DMUS_IO_SIGNPOST>... + ) + +*/ + +#define DMUS_FOURCC_MUTE_CHUNK mmioFOURCC('m','u','t','e') + +typedef struct _DMUS_IO_MUTE +{ + MUSIC_TIME mtTime; + DWORD dwPChannel; + DWORD dwPChannelMap; +} DMUS_IO_MUTE; + +/* + + // <mute-list> + 'mute' + ( + //sizeof DMUS_IO_MUTE:DWORD + <DMUS_IO_MUTE>... + ) + + +*/ + +/* Used for both style and chord map tracks */ + +#define DMUS_FOURCC_TIME_STAMP_CHUNK mmioFOURCC('s', 't', 'm', 'p') + +/* Style tracks */ + +#define DMUS_FOURCC_STYLE_TRACK_LIST mmioFOURCC('s', 't', 't', 'r') +#define DMUS_FOURCC_STYLE_REF_LIST mmioFOURCC('s', 't', 'r', 'f') + +/* + + // <sttr-list> + LIST('sttr' + ( + <strf-list>... // Array of Style references + ) + + // <strf-list> + LIST('strf' + ( + <stmp-ck> + <DMRF> + ) + + // <stmp-ck> + 'stmp' + ( + // time:DWORD + ) + +*/ + +/* Chord map tracks */ + +#define DMUS_FOURCC_PERS_TRACK_LIST mmioFOURCC('p', 'f', 't', 'r') +#define DMUS_FOURCC_PERS_REF_LIST mmioFOURCC('p', 'f', 'r', 'f') + +/* + + // <pftr-list> + LIST('pftr' + ( + <pfrf-list>... // Array of Chord map references + ) + + // <pfrf-list> + LIST('pfrf' + ( + <stmp-ck> + <DMRF> + ) + + // <stmp-ck> + 'stmp' + ( + // time:DWORD + ) + +*/ + +#define DMUS_FOURCC_TEMPO_TRACK mmioFOURCC('t','e','t','r') + +/* + // tempo array + 'tetr' + ( + // sizeof DMUS_IO_TEMPO_ITEM: DWORD + <DMUS_IO_TEMPO_ITEM>... + ) + */ + +#define DMUS_FOURCC_SEQ_TRACK mmioFOURCC('s','e','q','t') +#define DMUS_FOURCC_SEQ_LIST mmioFOURCC('e','v','t','l') +#define DMUS_FOURCC_CURVE_LIST mmioFOURCC('c','u','r','l') + +/* + // sequence track + 'seqt' + ( + // sequence array + 'evtl' + ( + // sizeof DMUS_IO_SEQ_ITEM: DWORD + <DMUS_IO_SEQ_ITEM>... + ) + // curve array + 'curl' + ( + // sizeof DMUS_IO_CURVE_ITEM: DWORD + <DMUS_IO_CURVE_ITEM>... + ) + ) +*/ + +#define DMUS_FOURCC_SYSEX_TRACK mmioFOURCC('s','y','e','x') + +/* + // sysex track + 'syex' + ( + { + <DMUS_IO_SYSEX_ITEM> + <BYTE>... // Array of bytes, length defined in the DMUS_IO_SYSEXITEM structure + }... + ) +*/ + +#define DMUS_FOURCC_TIMESIGNATURE_TRACK mmioFOURCC('t','i','m','s') + +typedef struct _DMUS_IO_TIMESIGNATURE_ITEM +{ + MUSIC_TIME lTime; + BYTE bBeatsPerMeasure; /* beats per measure (top of time sig) */ + BYTE bBeat; /* what note receives the beat (bottom of time sig.) */ + /* we can assume that 0 means 256th note */ + WORD wGridsPerBeat; /* grids per beat */ +} DMUS_IO_TIMESIGNATURE_ITEM; + +/* DX6 time signature track + + 'tims' + ( + // size of DMUS_IO_TIMESIGNATURE_ITEM : DWORD + <DMUS_IO_TIMESIGNATURE_ITEM>... + ) +*/ + +/* DX8 Time signature track. The track has been updated from DX7 to support a list of + RIFF chunks. This will allow the time signature track to expand in the future. +*/ + +#define DMUS_FOURCC_TIMESIGTRACK_LIST mmioFOURCC('T','I','M','S') +#define DMUS_FOURCC_TIMESIG_CHUNK DMUS_FOURCC_TIMESIGNATURE_TRACK + +/* +LIST +( + 'TIMS' // Time Signature Track list-type + <tims-ck> // Chunk containing an array of time signatures +) + + 'tims' + ( + // size of DMUS_IO_TIMESIGNATURE_ITEM : DWORD + <DMUS_IO_TIMESIGNATURE_ITEM>... + ) + +*/ + +/* DX8 Marker track. This is used to store valid start points and other + flow control parameters that may come later. For example, if we want + to implement more sophisticated looping and branching constructs, they + would live in this track. +*/ + +#define DMUS_FOURCC_MARKERTRACK_LIST mmioFOURCC('M','A','R','K') +#define DMUS_FOURCC_VALIDSTART_CHUNK mmioFOURCC('v','a','l','s') +#define DMUS_FOURCC_PLAYMARKER_CHUNK mmioFOURCC('p','l','a','y') + +/* io structures */ +typedef struct _DMUS_IO_VALID_START +{ + MUSIC_TIME mtTime; /* Time of a legal start. */ +} DMUS_IO_VALID_START; + +typedef struct _DMUS_IO_PLAY_MARKER +{ + MUSIC_TIME mtTime; /* Time of a next legal play point marker. */ +} DMUS_IO_PLAY_MARKER; + +/* +LIST +( + 'MARK' // Marker Track list-type + [<vals-ck>] // Chunk containing an array of start points + [<play-ck>] // Chunk containing an array of play start markers +) + + 'vals' + ( + // size of DMUS_IO_VALID_START : DWORD + <DMUS_IO_VALID_START>... + ) + + 'play' + ( + // size of DMUS_IO_PLAY_MARKER : DWORD + <DMUS_IO_PLAY_MARKER>... + ) + +*/ + +/* segment trigger tracks */ + +/* RIFF ids: */ +#define DMUS_FOURCC_SEGTRACK_LIST mmioFOURCC('s','e','g','t') +#define DMUS_FOURCC_SEGTRACK_CHUNK mmioFOURCC('s','g','t','h') +#define DMUS_FOURCC_SEGMENTS_LIST mmioFOURCC('l','s','g','l') +#define DMUS_FOURCC_SEGMENT_LIST mmioFOURCC('l','s','e','g') +#define DMUS_FOURCC_SEGMENTITEM_CHUNK mmioFOURCC('s','g','i','h') +#define DMUS_FOURCC_SEGMENTITEMNAME_CHUNK mmioFOURCC('s','n','a','m') + +/* io structures */ +typedef struct _DMUS_IO_SEGMENT_TRACK_HEADER +{ + DWORD dwFlags; /* Reserved leave as 0. */ +} DMUS_IO_SEGMENT_TRACK_HEADER; + +typedef struct _DMUS_IO_SEGMENT_ITEM_HEADER +{ + MUSIC_TIME lTimeLogical; /* Position in track list. Time in the music with which the event is associated. */ + MUSIC_TIME lTimePhysical; /* Precise time event will be triggered. Should be close to logical time. */ + DWORD dwPlayFlags; /* Flags for PlaySegment(). */ + DWORD dwFlags; /* Flags. */ +} DMUS_IO_SEGMENT_ITEM_HEADER; + +/* values for dwflags field of DMUS_IO_SEGMENT_ITEM_HEADER */ +#define DMUS_SEGMENTTRACKF_MOTIF 1 /* interpret DMRF as link to style, and use snam as the name of a motif within the style */ + +/* +LIST +( + 'segt' // DirectMusic Segment Trigger Track form-type + [<sgth-ck>] // Segment track header + <lsgl-list> // List of Segment Lists +) + + // <sgth-ck> + 'sgth' + ( + <DMUS_IO_SEGMENT_TRACK_HEADER> + ) + + // <lsgl-list> + LIST + ( + 'lsgl' // Array of segments + <lseg-list>... // Each segment is encapsulated in a list (that way it can still be riff parsed.) + ) + + // <lseg-list> + LIST + ( + 'lseg' + <sgih-ck> + <DMRF-list> // Link to a segment or style file. + [<snam-ck>] // Name field. Used with DMUS_SEGMENTTRACKF_MOTIF flag. + ) + + // <sgih-ck> // segment item header + ( + <DMUS_IO_SEGMENT_ITEM_HEADER> // Segment item header + ) + + // <snam-ck> + ( + // Name, stored as NULL terminated string of WCHARs + ) +*/ + +/* Script track. */ + +/* RIFF ids: */ +#define DMUS_FOURCC_SCRIPTTRACK_LIST mmioFOURCC('s','c','r','t') +#define DMUS_FOURCC_SCRIPTTRACKEVENTS_LIST mmioFOURCC('s','c','r','l') +#define DMUS_FOURCC_SCRIPTTRACKEVENT_LIST mmioFOURCC('s','c','r','e') +#define DMUS_FOURCC_SCRIPTTRACKEVENTHEADER_CHUNK mmioFOURCC('s','c','r','h') +#define DMUS_FOURCC_SCRIPTTRACKEVENTNAME_CHUNK mmioFOURCC('s','c','r','n') + +/* Flags for DMUS_IO_SCRIPTTRACK_TIMING + */ +#define DMUS_IO_SCRIPTTRACKF_PREPARE (1 << 0) /* Fire event in advance of time stamp, at Prepare time. This is the default because it leaves the script time to change the music happening at the target time. */ +#define DMUS_IO_SCRIPTTRACKF_QUEUE (1 << 1) /* Fire event just before time stamp, at Queue time. */ +#define DMUS_IO_SCRIPTTRACKF_ATTIME (1 << 2) /* Fire event right at the time stamp. */ + +typedef struct _DMUS_IO_SCRIPTTRACK_EVENTHEADER +{ + DWORD dwFlags; /* various bits (see DMUS_IO_SCRIPTTRACKF_*) */ + MUSIC_TIME lTimeLogical; /* Position in track list. Time in the music with which the event is associated. */ + MUSIC_TIME lTimePhysical; /* Precise time event will be triggered. Should be close to logical time. */ +} DMUS_IO_SCRIPTTRACK_EVENTHEADER; + +/* + // Script Track + + // <scrt-list> + LIST + ( + <scrl-list> // List of script events + ) + + // <scrl-list> + LIST + ( + <scre-list>... // Array of event descriptions + ) + + // <scre-list> + LIST + ( + <scrh-ck> // Event header chunk + <DMRF> + <scrn-ck> // Routine name + ) + + 'scrh' + ( + <DMUS_IO_SCRIPTTRACK_EVENTHEADER> + ) + + 'scrn' + ( + // Name, stored as NULL terminated string of WCHARs + ) +*/ + +/* Lyrics/Notification track. */ + +/* RIFF ids: */ +#define DMUS_FOURCC_LYRICSTRACK_LIST mmioFOURCC('l','y','r','t') +#define DMUS_FOURCC_LYRICSTRACKEVENTS_LIST mmioFOURCC('l','y','r','l') +#define DMUS_FOURCC_LYRICSTRACKEVENT_LIST mmioFOURCC('l','y','r','e') +#define DMUS_FOURCC_LYRICSTRACKEVENTHEADER_CHUNK mmioFOURCC('l','y','r','h') +#define DMUS_FOURCC_LYRICSTRACKEVENTTEXT_CHUNK mmioFOURCC('l','y','r','n') + +typedef struct _DMUS_IO_LYRICSTRACK_EVENTHEADER +{ + DWORD dwFlags; /* Reserved leave as 0. */ + DWORD dwTimingFlags; /* Combination DMUS_PMSGF_TOOL_* flags. Determines the precise timing of when the notification happens. Invalid with the flag DMUS_PMSGF_REFTIME, DMUS_PMSGF_MUSICTIME, DMUS_PMSGF_TOOL_FLUSH, or DMUS_PMSGF_LOCKTOREFTIME. */ + MUSIC_TIME lTimeLogical; /* Position in track list. Time in the music with which the event is associated. */ + MUSIC_TIME lTimePhysical; /* Precise time event will be triggered. Should be close to logical time. */ +} DMUS_IO_LYRICSTRACK_EVENTHEADER; + +/* + // Lyrics/Notification Track + + // <lyrt-list> + LIST + ( + <lyrl-list> // List of notification events + ) + + // <lyrl-list> + LIST + ( + <lyre-list>... // Array of event descriptions + ) + + // <lyre-list> + LIST + ( + <lyrh-ck> // Event header chunk + <lyrn-ck> // Notification text + ) + + 'lyrh' + ( + <DMUS_IO_LYRICSTRACK_EVENTHEADER> + ) + + 'lyrn' + ( + // Name, stored as NULL terminated string of WCHARs + ) +*/ + +/* Parameter control track */ + +/* RIFF ids: */ +#define DMUS_FOURCC_PARAMCONTROLTRACK_TRACK_LIST mmioFOURCC('p','r','m','t') +#define DMUS_FOURCC_PARAMCONTROLTRACK_OBJECT_LIST mmioFOURCC('p','r','o','l') +#define DMUS_FOURCC_PARAMCONTROLTRACK_OBJECT_CHUNK mmioFOURCC('p','r','o','h') +#define DMUS_FOURCC_PARAMCONTROLTRACK_PARAM_LIST mmioFOURCC('p','r','p','l') +#define DMUS_FOURCC_PARAMCONTROLTRACK_PARAM_CHUNK mmioFOURCC('p','r','p','h') +#define DMUS_FOURCC_PARAMCONTROLTRACK_CURVES_CHUNK mmioFOURCC('p','r','c','c') + +typedef struct _DMUS_IO_PARAMCONTROLTRACK_OBJECTHEADER +{ + DWORD dwFlags; /* Reserved. Must be zero. */ + GUID guidTimeFormat; /* Time format to set the object to. Must be GUID_TIME_REFERNCE or GUID_TIME_MUSIC from medparam.h. */ + /* Path for finding the object. These fields correspond to the first five parameters of IDirectMusicSegmentState::GetObjectInPath. */ + DWORD dwPChannel; + DWORD dwStage; + DWORD dwBuffer; + GUID guidObject; + DWORD dwIndex; +} DMUS_IO_PARAMCONTROLTRACK_OBJECTHEADER; + +typedef struct _DMUS_IO_PARAMCONTROLTRACK_PARAMHEADER +{ + DWORD dwFlags; /* Reserved. Must be zero. */ + DWORD dwIndex; /* Index number of the parameter on the object */ +} DMUS_IO_PARAMCONTROLTRACK_PARAMHEADER; + +typedef struct _DMUS_IO_PARAMCONTROLTRACK_CURVEINFO +{ + MUSIC_TIME mtStartTime; + MUSIC_TIME mtEndTime; + float fltStartValue; + float fltEndValue; + DWORD dwCurveType; /* One of the items from the MP_CURVE_TYPE enum in medparam.h */ + DWORD dwFlags; /* A combination of the MPF_ENVLP_* constants in medparam.h */ +} DMUS_IO_PARAMCONTROLTRACK_CURVEINFO; + +/* + // <prmt-list> + LIST + ( + <prol-list>... // one for each object + ) + + // <prol-list> + LIST + ( + <proh-ck> // object header chunk + <prpl-list>... // one for each parameter + ) + + // <proh-ck> + proh + ( + <DMUS_IO_PARAMCONTROLTRACK_OBJECTHEADER> + ) + + // <prpl-list> + LIST + ( + <prph-ck> // parameter header chunk + <prcc-ck> // chunk containing an array of curves + ) + + // <prph-ck> + prph + ( + <DMUS_IO_PARAMCONTROLTRACK_PARAMHEADER> + ) + + // <prcc-ck> + prcc + ( + // sizeof DMUS_IO_PARAMCONTROLTRACK_CURVEINFO:DWORD + <DMUS_IO_PARAMCONTROLTRACK_CURVEINFO>... // curves, sorted in order of mtTime + ) +*/ + +/* Melody formulation track */ +/* Note: Melody formulation file format is not supported in DX8. */ + +typedef DMUS_CONNECTION_RULE DMUS_IO_CONNECTION_RULE; /* defined in dmusici.h */ + +typedef DMUS_MELODY_FRAGMENT DMUS_IO_MELODY_FRAGMENT; /* defined in dmusici.h */ + +#define DMUS_FOURCC_MELODYFORM_TRACK_LIST mmioFOURCC( 'm', 'f', 'r', 'm' ) +#define DMUS_FOURCC_MELODYFORM_HEADER_CHUNK mmioFOURCC( 'm', 'l', 'f', 'h' ) +#define DMUS_FOURCC_MELODYFORM_BODY_CHUNK mmioFOURCC( 'm', 'l', 'f', 'b' ) + +typedef struct _DMUS_IO_MELFORM +{ + DWORD dwPlaymode; /* NOT CURRENTLY USED - MUST BE 0 */ +} DMUS_IO_MELFORM; + + +/* + // <mfrm-list> + LIST + ( + 'mfrm' + <mlfh-ck> // Melody formulation header chunk + <mlfb-ck> // Melody formulation body chunk + ) + + // <mlfb-ck> + 'mlfb' + ( + <DMUS_IO_MELFORM> + ) + + // <mlfb-ck> + 'mlfb' + ( + //sizeof DMUS_IO_MELODY_FRAGMENT: DWORD + <DMUS_IO_MELODY_FRAGMENT>... + ) + +*/ + +#if (DIRECTSOUND_VERSION >= 0x0800) + +/* DirectSoundBufferConfig FX Map */ + +/* RIFF ids: */ + +#define DMUS_FOURCC_DSBC_FORM mmioFOURCC('D','S','B','C') +#define DMUS_FOURCC_DSBD_CHUNK mmioFOURCC('d','s','b','d') +#define DMUS_FOURCC_BSID_CHUNK mmioFOURCC('b','s','i','d') +#define DMUS_FOURCC_DS3D_CHUNK mmioFOURCC('d','s','3','d') +#define DMUS_FOURCC_DSBC_LIST mmioFOURCC('f','x','l','s') +#define DMUS_FOURCC_DSFX_FORM mmioFOURCC('D','S','F','X') +#define DMUS_FOURCC_DSFX_CHUNK mmioFOURCC('f','x','h','r') +#define DMUS_FOURCC_DSFX_DATA mmioFOURCC('d','a','t','a') + +/* io structures */ + +typedef struct _DSOUND_IO_DSBUFFERDESC +{ + DWORD dwFlags; /* DirectSound buffer creation flags */ + WORD nChannels; /* No. of channels (rest of buffer format is determined by owning sink) */ + LONG lVolume; /* Initial pan; only used if CTRLVOLUME is specified */ + LONG lPan; /* Initial pan; only used if CTRLPAN is specified */ + DWORD dwReserved; /* Reserved - must be 0 */ +} DSOUND_IO_DSBUFFERDESC; + +typedef struct _DSOUND_IO_DSBUSID +{ + DWORD busid[1]; /* Array size determined from chunk size */ +} DSOUND_IO_DSBUSID; + +typedef struct _DSOUND_IO_3D +{ + GUID guid3DAlgorithm; /* GUID identifying the 3D algorithm to use (defined in dsound.h) */ + DS3DBUFFER ds3d; /* Initial 3D parameters */ +} DSOUND_IO_3D; + +typedef struct _DSOUND_IO_DXDMO_HEADER +{ + DWORD dwEffectFlags; /* Effect creation flags - equivalent to DSEFFECTDESC::dwFlags */ + GUID guidDSFXClass; /* GUID identifying the effect to use - corresponds to a COM CLSID */ + GUID guidReserved; /* Reserved - must be the null GUID */ + GUID guidSendBuffer; /* GUID identifying the buffer to send to if this is a send effect */ + DWORD dwReserved; /* Reserved - must be 0 */ +} DSOUND_IO_DXDMO_HEADER; + +typedef struct _DSOUND_IO_DXDMO_DATA +{ + DWORD data[1]; /* Array size determined by the DMO involved */ +} DSOUND_IO_DXDMO_DATA; + +/* +RIFF +( + 'DSBC' // DirectSoundBufferConfig chunk + [<guid-ck>] // GUID identifier for this DirectSoundBufferConfig + [<vers-ck>] // Optional version info + [<UNFO-list>] // Name, author, copyright info., comments + <dsbd-ck> // DirectSound Buffer descriptor chunk + [<bsid-ck>] // Optional bus id array + [<ds3d-ck>] // Optional 3d Parameters + [<fxls-list>] // Optional list of FX descriptors +) + + // <guid-ck> + 'guid' + ( + <GUID> + ) + + // <vers-ck> + 'vers' + ( + <DMUS_IO_VERSION> + ) + + // <dsbd-ck> + 'dsbd' + ( + <DSOUND_IO_DSBUFFERDESC> // Creation parameters and initial settings for the buffer + ) + + // <bsid-ck> + 'bsid' + ( + <DSOUND_IO_DSBUSID> // The size of DSOUND_IO_DSBUSID is determined by the chunk size + ) + + // <ds3d-ck> + 'ds3d' + ( + <DSOUND_IO_3D> // Initial 3D buffer parameters: position, etc. + ) + + // <fx-list> + LIST + ( + 'fxls' // Array of DMO creation parameter blocks + <DSFX-form>... // Each DMO is encapsulated in a RIFF chunk + ) + +// <DSFX-form> // DMOs can be embedded in a buffer configuration or stored as separate files +RIFF +( + 'DSFX' + <fxhr-ck> // FX header chunk + [<data-ck>] // FX initial settings chunk +) + + // <fxhr-ck> + 'fxhr' + ( + <DSOUND_IO_DXDMO_HEADER> + ) + + // <data-ck> + 'data' + ( + <DSOUND_IO_DXDMO_DATA> // Opaque data block used by the DMO to load itself. + // For our standard included DMOs, this is simply the structure accepted by + // the DMO's SetAllParameters() method - e.g. struct DSFXChorus for Chorus. + ) +*/ + +#endif + +#ifdef __cplusplus +}; /* extern "C" */ +#endif + +#include <poppack.h> + +#endif /* #ifndef _DMUSICF_ */ diff --git a/Src/Plugins/Input/in_midi/dmusici.h b/Src/Plugins/Input/in_midi/dmusici.h new file mode 100644 index 00000000..c6bc37db --- /dev/null +++ b/Src/Plugins/Input/in_midi/dmusici.h @@ -0,0 +1,1964 @@ +/************************************************************************ +* * +* dmusici.h -- This module contains the API for the * +* DirectMusic performance layer * +* * +* Copyright (c) 1998-1999 Microsoft Corporation +* * +************************************************************************/ + +#ifndef _DMUSICI_ +#define _DMUSICI_ + +#include <windows.h> + +#define COM_NO_WINDOWS_H +#include <objbase.h> + +#include <mmsystem.h> +#include <dmusicc.h> +/* plugin (track and tool) interfaces. This #include will eventually go away. */ +#include <dmplugin.h> + +#include <pshpack8.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef WORD TRANSITION_TYPE; +typedef __int64 REFERENCE_TIME; +typedef long MUSIC_TIME; + +#define MT_MIN 0x80000000 /* Minimum music time value. */ +#define MT_MAX 0x7FFFFFFF /* Maximum music time value. */ + +#define DMUS_PPQ 768 /* parts per quarter note */ + +interface IDirectMusicTrack; +interface IDirectMusicPerformance; +interface IDirectMusicPerformance8; +interface IDirectMusicTool; +interface IDirectMusicSegment; +interface IDirectMusicSegment8; +interface IDirectMusicSegmentState; +interface IDirectMusicSegmentState8; +interface IDirectMusicGraph; +interface IDirectMusicBuffer; +interface IDirectMusicInstrument; +interface IDirectMusicDownloadedInstrument; +interface IDirectMusicBand; +interface IDirectMusicChordMap; +interface IDirectMusicLoader; +interface IDirectMusicLoader8; +interface IDirectMusicScript; +interface IDirectMusicObject; +interface IDirectMusicStyle8; +interface IDirectMusicPatternTrack; +interface IDirectMusicContainer; +interface IDirectMusicTool8; +interface IDirectMusicTrack8; +interface IDirectMusicSong; +interface IDirectMusicAudioPath; +#ifndef __cplusplus +typedef interface IDirectMusicTrack IDirectMusicTrack; +typedef interface IDirectMusicPerformance IDirectMusicPerformance; +typedef interface IDirectMusicPerformance8 IDirectMusicPerformance8; +typedef interface IDirectMusicTool IDirectMusicTool; +typedef interface IDirectMusicSegment IDirectMusicSegment; +typedef interface IDirectMusicSegment8 IDirectMusicSegment8; +typedef interface IDirectMusicSegmentState IDirectMusicSegmentState; +typedef interface IDirectMusicSegmentState8 IDirectMusicSegmentState8; +typedef interface IDirectMusicGraph IDirectMusicGraph; +typedef interface IDirectMusicBuffer IDirectMusicBuffer; +typedef interface IDirectMusicInstrument IDirectMusicInstrument; +typedef interface IDirectMusicDownloadedInstrument IDirectMusicDownloadedInstrument; +typedef interface IDirectMusicBand IDirectMusicBand; +typedef interface IDirectMusicChordMap IDirectMusicChordMap; +typedef interface IDirectMusicObject IDirectMusicObject; +typedef interface IDirectMusicLoader IDirectMusicLoader; +typedef interface IDirectMusicLoader8 IDirectMusicLoader8; +typedef interface IDirectMusicScript IDirectMusicScript; +typedef interface IDirectMusicStyle8 IDirectMusicStyle8; +typedef interface IDirectMusicPatternTrack IDirectMusicPatternTrack; +typedef interface IDirectMusicContainer IDirectMusicContainer; +typedef interface IDirectMusicTool8 IDirectMusicTool8; +typedef interface IDirectMusicTrack8 IDirectMusicTrack8; +typedef interface IDirectMusicSong IDirectMusicSong; +typedef interface IDirectMusicAudioPath IDirectMusicAudioPath; +#endif + +typedef enum enumDMUS_STYLET_TYPES +{ + DMUS_STYLET_PATTERN = 0, + DMUS_STYLET_MOTIF = 1, + DMUS_STYLET_FRAGMENT = 2, +} DMUS_STYLET_TYPES; + + +typedef enum enumDMUS_COMMANDT_TYPES +{ + DMUS_COMMANDT_GROOVE = 0, + DMUS_COMMANDT_FILL = 1, + DMUS_COMMANDT_INTRO = 2, + DMUS_COMMANDT_BREAK = 3, + DMUS_COMMANDT_END = 4, + DMUS_COMMANDT_ENDANDINTRO = 5 +} DMUS_COMMANDT_TYPES; + +typedef enum enumDMUS_SHAPET_TYPES +{ + DMUS_SHAPET_FALLING = 0, + DMUS_SHAPET_LEVEL = 1, + DMUS_SHAPET_LOOPABLE = 2, + DMUS_SHAPET_LOUD = 3, + DMUS_SHAPET_QUIET = 4, + DMUS_SHAPET_PEAKING = 5, + DMUS_SHAPET_RANDOM = 6, + DMUS_SHAPET_RISING = 7, + DMUS_SHAPET_SONG = 8 +} DMUS_SHAPET_TYPES; + +typedef enum enumDMUS_COMPOSEF_FLAGS +{ + DMUS_COMPOSEF_NONE = 0, + DMUS_COMPOSEF_ALIGN = 0x1, + DMUS_COMPOSEF_OVERLAP = 0x2, + DMUS_COMPOSEF_IMMEDIATE = 0x4, + DMUS_COMPOSEF_GRID = 0x8, + DMUS_COMPOSEF_BEAT = 0x10, + DMUS_COMPOSEF_MEASURE = 0x20, + DMUS_COMPOSEF_AFTERPREPARETIME = 0x40, + DMUS_COMPOSEF_VALID_START_BEAT = 0x80, /* In conjunction with DMUS_COMPOSEF_ALIGN, allows the switch to occur on any beat. */ + DMUS_COMPOSEF_VALID_START_GRID = 0x100, /* In conjunction with DMUS_COMPOSEF_ALIGN, allows the switch to occur on any grid. */ + DMUS_COMPOSEF_VALID_START_TICK = 0x200, /* In conjunction with DMUS_COMPOSEF_ALIGN, allows the switch to occur any time. */ + DMUS_COMPOSEF_SEGMENTEND = 0x400, /* Play the transition at the end of the current segment. */ + DMUS_COMPOSEF_MARKER = 0x800, /* Play the transition at the next marker in the current segment. */ + DMUS_COMPOSEF_MODULATE = 0x1000, + DMUS_COMPOSEF_LONG = 0x2000, + DMUS_COMPOSEF_ENTIRE_TRANSITION = 0x4000, /* play the entire transition pattern */ + DMUS_COMPOSEF_1BAR_TRANSITION = 0x8000, /* play one bar of the transition pattern */ + DMUS_COMPOSEF_ENTIRE_ADDITION = 0x10000, /* play the additional pattern in its entirety */ + DMUS_COMPOSEF_1BAR_ADDITION = 0x20000, /* play one bar of the additional pattern */ + DMUS_COMPOSEF_VALID_START_MEASURE = 0x40000, /* In conjunction with DMUS_COMPOSEF_ALIGN, allows the switch to occur on any bar. */ + DMUS_COMPOSEF_DEFAULT = 0x80000, /* Use segment's default boundary */ + DMUS_COMPOSEF_NOINVALIDATE = 0x100000, /* Play without invalidating the currently playing segment(s) */ + DMUS_COMPOSEF_USE_AUDIOPATH = 0x200000, /* Uses the audio paths that are embedded in the segments */ + DMUS_COMPOSEF_INVALIDATE_PRI = 0x400000 /* Invalidate only the current primary seg state */ +} DMUS_COMPOSEF_FLAGS; + +#define DMUS_PMSG_PART \ + DWORD dwSize; \ + REFERENCE_TIME rtTime; /* real time (in 100 nanosecond increments) */ \ + MUSIC_TIME mtTime; /* music time */ \ + DWORD dwFlags; /* various bits (see DMUS_PMSGF_FLAGS enumeration) */ \ + DWORD dwPChannel; /* Performance Channel. The Performance can */ \ + /* use this to determine the port/channel. */ \ + DWORD dwVirtualTrackID; /* virtual track ID */ \ + IDirectMusicTool* pTool; /* tool interface pointer */ \ + IDirectMusicGraph* pGraph; /* tool graph interface pointer */ \ + DWORD dwType; /* PMSG type (see DMUS_PMSGT_TYPES defines) */ \ + DWORD dwVoiceID; /* unique voice id which allows synthesizers to */ \ + /* identify a specific event. For DirectX 6.0, */ \ + /* this field should always be 0. */ \ + DWORD dwGroupID; /* Track group id */ \ + IUnknown* punkUser; /* user com pointer, auto released upon PMSG free */ + +/* every DMUS_PMSG is based off of this structure. The Performance needs + to access these members consistently in every PMSG that goes through it. */ +typedef struct _DMUS_PMSG +{ + /* begin DMUS_PMSG_PART */ + DMUS_PMSG_PART + /* end DMUS_PMSG_PART */ + +} DMUS_PMSG; + +#define DMUS_PCHANNEL_BROADCAST_PERFORMANCE 0xFFFFFFFF /* PMsg is sent on all PChannels of the performance. */ +#define DMUS_PCHANNEL_BROADCAST_AUDIOPATH 0xFFFFFFFE /* PMsg is sent on all PChannels of the audio path. */ +#define DMUS_PCHANNEL_BROADCAST_SEGMENT 0xFFFFFFFD /* PMsg is sent on all PChannels of the segment. */ +#define DMUS_PCHANNEL_BROADCAST_GROUPS 0xFFFFFFFC /* A duplicate PMsg is for each Channels Groups in the performance. */ + +/* The DMUS_PATH constants are used in conjunction with GetObjectInPath to find a requested + interface at a particular stage in the audio path. +*/ +#define DMUS_PATH_SEGMENT 0x1000 /* Get the segment itself (from a segment state.) */ +#define DMUS_PATH_SEGMENT_TRACK 0x1100 /* Look in Track List of Segment. */ +#define DMUS_PATH_SEGMENT_GRAPH 0x1200 /* Get the segment's tool graph. */ +#define DMUS_PATH_SEGMENT_TOOL 0x1300 /* Look in Tool Graph of Segment. */ +#define DMUS_PATH_AUDIOPATH 0x2000 /* Get the audiopath itself (from a segment state.) */ +#define DMUS_PATH_AUDIOPATH_GRAPH 0x2200 /* Get the audiopath's tool graph. */ +#define DMUS_PATH_AUDIOPATH_TOOL 0x2300 /* Look in Tool Graph of Audio Path. */ +#define DMUS_PATH_PERFORMANCE 0x3000 /* Access the performance. */ +#define DMUS_PATH_PERFORMANCE_GRAPH 0x3200 /* Get the performance's tool graph. */ +#define DMUS_PATH_PERFORMANCE_TOOL 0x3300 /* Look in Tool Graph of Performance. */ +#define DMUS_PATH_PORT 0x4000 /* Access the synth. */ +#define DMUS_PATH_BUFFER 0x6000 /* Look in DirectSoundBuffer. */ +#define DMUS_PATH_BUFFER_DMO 0x6100 /* Access a DMO in the buffer. */ +#define DMUS_PATH_MIXIN_BUFFER 0x7000 /* Look in a global mixin buffer. */ +#define DMUS_PATH_MIXIN_BUFFER_DMO 0x7100 /* Access a DMO in a global mixin buffer. */ +#define DMUS_PATH_PRIMARY_BUFFER 0x8000 /* Access the primary buffer. */ + +/* To ignore PChannels when calling GetObjectInPath(), use the DMUS_PCHANNEL_ALL constant. */ +#define DMUS_PCHANNEL_ALL 0xFFFFFFFB + +/* The DMUS_APATH types are used in conjunction with CreateStandardAudioPath to + build default path types. _SHARED_ means the same buffer is shared across multiple + instantiations of the audiopath type. _DYNAMIC_ means a unique buffer is created + every time. +*/ + +#define DMUS_APATH_SHARED_STEREOPLUSREVERB 1 /* A standard music set up with stereo outs and reverb. */ +#define DMUS_APATH_DYNAMIC_3D 6 /* An audio path with one dynamic bus from the synth feeding to a dynamic 3d buffer. Does not send to env reverb. */ +#define DMUS_APATH_DYNAMIC_MONO 7 /* An audio path with one dynamic bus from the synth feeding to a dynamic mono buffer. */ +#define DMUS_APATH_DYNAMIC_STEREO 8 /* An audio path with two dynamic buses from the synth feeding to a dynamic stereo buffer. */ + +typedef struct _DMUS_AUDIOPARAMS +{ + DWORD dwSize; /* Size of this structure. */ + BOOL fInitNow; /* If true, the sink and synth are created immediately and results returned in this structure. */ + DWORD dwValidData; /* Flags indicating which fields below are valid. */ + DWORD dwFeatures; /* Required DMUS_AUDIOF features. */ + DWORD dwVoices; /* Required number of voices. */ + DWORD dwSampleRate; /* Sample rate of synths and sink. */ + CLSID clsidDefaultSynth; /* Class ID of default synthesizer. */ +} DMUS_AUDIOPARAMS; + +/* dwFeatures flags. These indicate which features are required for the audio environment. */ +#define DMUS_AUDIOF_3D 0x1 /* Require 3D buffers. */ +#define DMUS_AUDIOF_ENVIRON 0x2 /* Require environmental modeling. */ +#define DMUS_AUDIOF_EAX 0x4 /* Require use of EAX effects. */ +#define DMUS_AUDIOF_DMOS 0x8 /* Require use of additional DMOs. */ +#define DMUS_AUDIOF_STREAMING 0x10 /* Require support for streaming waves. */ +#define DMUS_AUDIOF_BUFFERS 0x20 /* Require support for multiple buffers (all above cases need this.) */ +#define DMUS_AUDIOF_ALL 0x3F /* Requires everything. */ + +/* dwValidData flags. These indicate which fields in DMUS_AUDIOPARAMS have been filled in. If fInitNow is set, these also return what was allocated. */ +#define DMUS_AUDIOPARAMS_FEATURES 0x00000001 +#define DMUS_AUDIOPARAMS_VOICES 0x00000002 +#define DMUS_AUDIOPARAMS_SAMPLERATE 0x00000004 +#define DMUS_AUDIOPARAMS_DEFAULTSYNTH 0x00000008 + +/* DMUS_PMSGF_FLAGS fill the DMUS_PMSG's dwFlags member */ +typedef enum enumDMUS_PMSGF_FLAGS +{ + DMUS_PMSGF_REFTIME = 1, /* if rtTime is valid */ + DMUS_PMSGF_MUSICTIME = 2, /* if mtTime is valid */ + DMUS_PMSGF_TOOL_IMMEDIATE = 4, /* if PMSG should be processed immediately */ + DMUS_PMSGF_TOOL_QUEUE = 8, /* if PMSG should be processed a little early, at Queue time */ + DMUS_PMSGF_TOOL_ATTIME = 0x10, /* if PMSG should be processed at the time stamp */ + DMUS_PMSGF_TOOL_FLUSH = 0x20, /* if PMSG is being flushed */ + DMUS_PMSGF_LOCKTOREFTIME = 0x40, /* if rtTime can not be overriden by a tempo change. */ + DMUS_PMSGF_DX8 = 0x80 /* if the message has DX8 or later extensions. */ + /* The values of DMUS_TIME_RESOLVE_FLAGS may also be used inside the */ + /* DMUS_PMSG's dwFlags member. */ +} DMUS_PMSGF_FLAGS; + +/* DMUS_PMSGT_TYPES fill the DMUS_PMSG's dwType member */ +typedef enum enumDMUS_PMSGT_TYPES +{ + DMUS_PMSGT_MIDI = 0, /* MIDI short message */ + DMUS_PMSGT_NOTE = 1, /* Interactive Music Note */ + DMUS_PMSGT_SYSEX = 2, /* MIDI long message (system exclusive message) */ + DMUS_PMSGT_NOTIFICATION = 3, /* Notification message */ + DMUS_PMSGT_TEMPO = 4, /* Tempo message */ + DMUS_PMSGT_CURVE = 5, /* Control change / pitch bend, etc. curve */ + DMUS_PMSGT_TIMESIG = 6, /* Time signature */ + DMUS_PMSGT_PATCH = 7, /* Patch changes */ + DMUS_PMSGT_TRANSPOSE = 8, /* Transposition messages */ + DMUS_PMSGT_CHANNEL_PRIORITY = 9, /* Channel priority */ + DMUS_PMSGT_STOP = 10, /* Stop message */ + DMUS_PMSGT_DIRTY = 11, /* Tells Tools that cache GetParam() info to refresh */ + DMUS_PMSGT_WAVE = 12, /* Carries control information for playing a wave. */ + DMUS_PMSGT_LYRIC = 13, /* Lyric message from lyric track. */ + DMUS_PMSGT_SCRIPTLYRIC = 14, /* Lyric message sent by a script with the Trace function. */ + DMUS_PMSGT_USER = 255 /* User message */ +} DMUS_PMSGT_TYPES; + +/* DMUS_SEGF_FLAGS correspond to IDirectMusicPerformance::PlaySegment, and other API */ +typedef enum enumDMUS_SEGF_FLAGS +{ + DMUS_SEGF_REFTIME = 1<<6, /* 0x40 Time parameter is in reference time */ + DMUS_SEGF_SECONDARY = 1<<7, /* 0x80 Secondary segment */ + DMUS_SEGF_QUEUE = 1<<8, /* 0x100 Queue at the end of the primary segment queue (primary only) */ + DMUS_SEGF_CONTROL = 1<<9, /* 0x200 Play as a control track (secondary segments only) */ + DMUS_SEGF_AFTERPREPARETIME = 1<<10, /* 0x400 Play after the prepare time (See IDirectMusicPerformance::GetPrepareTime) */ + DMUS_SEGF_GRID = 1<<11, /* 0x800 Play on grid boundary */ + DMUS_SEGF_BEAT = 1<<12, /* 0x1000 Play on beat boundary */ + DMUS_SEGF_MEASURE = 1<<13, /* 0x2000 Play on measure boundary */ + DMUS_SEGF_DEFAULT = 1<<14, /* 0x4000 Use segment's default boundary */ + DMUS_SEGF_NOINVALIDATE = 1<<15, /* 0x8000 Play without invalidating the currently playing segment(s) */ + DMUS_SEGF_ALIGN = 1<<16, /* 0x10000 Align segment with requested boundary, but switch at first valid point */ + DMUS_SEGF_VALID_START_BEAT = 1<<17, /* 0x20000 In conjunction with DMUS_SEGF_ALIGN, allows the switch to occur on any beat. */ + DMUS_SEGF_VALID_START_GRID = 1<<18, /* 0x40000 In conjunction with DMUS_SEGF_ALIGN, allows the switch to occur on any grid. */ + DMUS_SEGF_VALID_START_TICK = 1<<19, /* 0x80000 In conjunction with DMUS_SEGF_ALIGN, allows the switch to occur any time. */ + DMUS_SEGF_AUTOTRANSITION = 1<<20, /* 0x100000 Compose and play a transition segment, using either the transition template or transition embedded in song. */ + DMUS_SEGF_AFTERQUEUETIME = 1<<21, /* 0x200000 Make sure to play after the queue time. This is default for primary segments */ + DMUS_SEGF_AFTERLATENCYTIME = 1<<22, /* 0x400000 Make sure to play after the latency time. This is true for all segments, so this is a nop */ + DMUS_SEGF_SEGMENTEND = 1<<23, /* 0x800000 Play at the next end of segment. */ + DMUS_SEGF_MARKER = 1<<24, /* 0x1000000 Play at next marker in the primary segment. If there are no markers, default to any other resolution requests. */ + DMUS_SEGF_TIMESIG_ALWAYS = 1<<25, /* 0x2000000 Even if there is no primary segment, align start time with current time signature. */ + DMUS_SEGF_USE_AUDIOPATH = 1<<26, /* 0x4000000 Uses the audio path that is embedded in the segment or song. */ + DMUS_SEGF_VALID_START_MEASURE = 1<<27, /* 0x8000000 In conjunction with DMUS_SEGF_ALIGN, allows the switch to occur on any bar. */ + DMUS_SEGF_INVALIDATE_PRI = 1<<28 /* 0x10000000 invalidate only the current primary seg state */ +} DMUS_SEGF_FLAGS; + +#define DMUS_SEG_REPEAT_INFINITE 0xFFFFFFFF /* For IDirectMusicSegment::SetRepeat*/ +#define DMUS_SEG_ALLTRACKS 0x80000000 /* For IDirectMusicSegment::SetParam() and SetTrackConfig() - selects all tracks instead on nth index. */ +#define DMUS_SEG_ANYTRACK 0x80000000 /* For IDirectMusicSegment::GetParam() - checks each track until it finds one that returns data (not DMUS_E_NOT_FOUND.) */ + + +/* DMUS_TIME_RESOLVE_FLAGS correspond to IDirectMusicPerformance::GetResolvedTime, and can */ +/* also be used interchangeably with the corresponding DMUS_SEGF_FLAGS, since their values */ +/* are intentionally the same */ +typedef enum enumDMUS_TIME_RESOLVE_FLAGS +{ + DMUS_TIME_RESOLVE_AFTERPREPARETIME = DMUS_SEGF_AFTERPREPARETIME, + DMUS_TIME_RESOLVE_AFTERQUEUETIME = DMUS_SEGF_AFTERQUEUETIME, + DMUS_TIME_RESOLVE_AFTERLATENCYTIME = DMUS_SEGF_AFTERLATENCYTIME, + DMUS_TIME_RESOLVE_GRID = DMUS_SEGF_GRID, + DMUS_TIME_RESOLVE_BEAT = DMUS_SEGF_BEAT, + DMUS_TIME_RESOLVE_MEASURE = DMUS_SEGF_MEASURE, + DMUS_TIME_RESOLVE_MARKER = DMUS_SEGF_MARKER, + DMUS_TIME_RESOLVE_SEGMENTEND = DMUS_SEGF_SEGMENTEND, +} DMUS_TIME_RESOLVE_FLAGS; + +/* The following flags are sent inside the DMUS_CHORD_KEY.dwFlags parameter */ +typedef enum enumDMUS_CHORDKEYF_FLAGS +{ + DMUS_CHORDKEYF_SILENT = 1, /* is the chord silent? */ +} DMUS_CHORDKEYF_FLAGS; + +#define DMUS_MAXSUBCHORD 8 + +typedef struct _DMUS_SUBCHORD +{ + DWORD dwChordPattern; /* Notes in the subchord */ + DWORD dwScalePattern; /* Notes in the scale */ + DWORD dwInversionPoints; /* Where inversions can occur */ + DWORD dwLevels; /* Which levels are supported by this subchord */ + BYTE bChordRoot; /* Root of the subchord */ + BYTE bScaleRoot; /* Root of the scale */ +} DMUS_SUBCHORD; + +typedef struct _DMUS_CHORD_KEY +{ + WCHAR wszName[16]; /* Name of the chord */ + WORD wMeasure; /* Measure this falls on */ + BYTE bBeat; /* Beat this falls on */ + BYTE bSubChordCount; /* Number of chords in the list of subchords */ + DMUS_SUBCHORD SubChordList[DMUS_MAXSUBCHORD]; /* List of sub chords */ + DWORD dwScale; /* Scale underlying the entire chord */ + BYTE bKey; /* Key underlying the entire chord */ + BYTE bFlags; /* Miscelaneous flags */ +} DMUS_CHORD_KEY; + +/* DMUS_NOTE_PMSG */ +typedef struct _DMUS_NOTE_PMSG +{ + /* begin DMUS_PMSG_PART */ + DMUS_PMSG_PART + /* end DMUS_PMSG_PART */ + + MUSIC_TIME mtDuration; /* duration */ + WORD wMusicValue; /* Description of note in chord and key. */ + WORD wMeasure; /* Measure in which this note occurs */ + short nOffset; /* Offset from grid at which this note occurs */ + BYTE bBeat; /* Beat (in measure) at which this note occurs */ + BYTE bGrid; /* Grid offset from beat at which this note occurs */ + BYTE bVelocity; /* Note velocity */ + BYTE bFlags; /* see DMUS_NOTEF_FLAGS */ + BYTE bTimeRange; /* Range to randomize time. */ + BYTE bDurRange; /* Range to randomize duration. */ + BYTE bVelRange; /* Range to randomize velocity. */ + BYTE bPlayModeFlags; /* Play mode */ + BYTE bSubChordLevel; /* Which subchord level this note uses. */ + BYTE bMidiValue; /* The MIDI note value, converted from wMusicValue */ + char cTranspose; /* Transposition to add to midi note value after converted from wMusicValue. */ +} DMUS_NOTE_PMSG; + +typedef enum enumDMUS_NOTEF_FLAGS +{ + DMUS_NOTEF_NOTEON = 1, /* Set if this is a MIDI Note On. Otherwise, it is MIDI Note Off */ + /* DX8 flags: */ + DMUS_NOTEF_NOINVALIDATE = 2, /* Don't invalidate this note off. */ + DMUS_NOTEF_NOINVALIDATE_INSCALE = 4,/* Don't invalidate if still within the scale. */ + DMUS_NOTEF_NOINVALIDATE_INCHORD = 8,/* Don't invalidate if still within the chord. */ + DMUS_NOTEF_REGENERATE = 0x10, /* Regenerate the note on an invalidate. */ +} DMUS_NOTEF_FLAGS; + +/* The DMUS_PLAYMODE_FLAGS are used to determine how to convert wMusicValue + into the appropriate bMidiValue. +*/ + +typedef enum enumDMUS_PLAYMODE_FLAGS +{ + DMUS_PLAYMODE_KEY_ROOT = 1, /* Transpose on top of the key root. */ + DMUS_PLAYMODE_CHORD_ROOT = 2, /* Transpose on top of the chord root. */ + DMUS_PLAYMODE_SCALE_INTERVALS = 4, /* Use scale intervals from scale pattern. */ + DMUS_PLAYMODE_CHORD_INTERVALS = 8, /* Use chord intervals from chord pattern. */ + DMUS_PLAYMODE_NONE = 16, /* No mode. Indicates the parent part's mode should be used. */ +} DMUS_PLAYMODE_FLAGS; + +/* The following are playback modes that can be created by combining the DMUS_PLAYMODE_FLAGS + in various ways: +*/ + +/* Fixed. wMusicValue holds final MIDI note value. This is used for drums, sound effects, and sequenced + notes that should not be transposed by the chord or scale. +*/ +#define DMUS_PLAYMODE_FIXED 0 +/* In fixed to key, the musicvalue is again a fixed MIDI value, but it + is transposed on top of the key root. +*/ +#define DMUS_PLAYMODE_FIXEDTOKEY DMUS_PLAYMODE_KEY_ROOT +/* In fixed to chord, the musicvalue is also a fixed MIDI value, but it + is transposed on top of the chord root. +*/ +#define DMUS_PLAYMODE_FIXEDTOCHORD DMUS_PLAYMODE_CHORD_ROOT +/* In Pedalpoint, the key root is used and the notes only track the intervals in + the scale. The chord root and intervals are completely ignored. This is useful + for melodic lines that play relative to the key root. +*/ +#define DMUS_PLAYMODE_PEDALPOINT (DMUS_PLAYMODE_KEY_ROOT | DMUS_PLAYMODE_SCALE_INTERVALS) +/* In the Melodic mode, the chord root is used but the notes only track the intervals in + the scale. The key root and chord intervals are completely ignored. This is useful + for melodic lines that play relative to the chord root. +*/ +#define DMUS_PLAYMODE_MELODIC (DMUS_PLAYMODE_CHORD_ROOT | DMUS_PLAYMODE_SCALE_INTERVALS) +/* Normal chord mode is the prevalent playback mode. + The notes track the intervals in the chord, which is based on the chord root. + If there is a scale component to the MusicValue, the additional intervals + are pulled from the scale and added. + If the chord does not have an interval to match the chord component of + the MusicValue, the note is silent. +*/ +#define DMUS_PLAYMODE_NORMALCHORD (DMUS_PLAYMODE_CHORD_ROOT | DMUS_PLAYMODE_CHORD_INTERVALS) +/* If it is desirable to play a note that is above the top of the chord, the + always play mode (known as "purpleized" in a former life) finds a position + for the note by using intervals from the scale. Essentially, this mode is + a combination of the Normal and Melodic playback modes, where a failure + in Normal causes a second try in Melodic mode. +*/ +#define DMUS_PLAYMODE_ALWAYSPLAY (DMUS_PLAYMODE_MELODIC | DMUS_PLAYMODE_NORMALCHORD) + +/* These playmodes are new for dx8. */ +/* In PedalpointChord, the key root is used and the notes only track the intervals in + the chord. The chord root and scale intervals are completely ignored. This is useful + for chordal lines that play relative to the key root. +*/ +#define DMUS_PLAYMODE_PEDALPOINTCHORD (DMUS_PLAYMODE_KEY_ROOT | DMUS_PLAYMODE_CHORD_INTERVALS) + +/* For completeness, here's a mode that tries for pedalpointchord, but if it fails + uses scale intervals +*/ +#define DMUS_PLAYMODE_PEDALPOINTALWAYS (DMUS_PLAYMODE_PEDALPOINT | DMUS_PLAYMODE_PEDALPOINTCHORD) + + +/* Legacy names for modes... */ +#define DMUS_PLAYMODE_PURPLEIZED DMUS_PLAYMODE_ALWAYSPLAY +#define DMUS_PLAYMODE_SCALE_ROOT DMUS_PLAYMODE_KEY_ROOT +#define DMUS_PLAYMODE_FIXEDTOSCALE DMUS_PLAYMODE_FIXEDTOKEY + + +/* DMUS_MIDI_PMSG */ +typedef struct _DMUS_MIDI_PMSG +{ + /* begin DMUS_PMSG_PART */ + DMUS_PMSG_PART + /* end DMUS_PMSG_PART */ + + BYTE bStatus; + BYTE bByte1; + BYTE bByte2; + BYTE bPad[1]; +} DMUS_MIDI_PMSG; + +/* DMUS_PATCH_PMSG */ +typedef struct _DMUS_PATCH_PMSG +{ + /* begin DMUS_PMSG_PART */ + DMUS_PMSG_PART + /* end DMUS_PMSG_PART */ + + BYTE byInstrument; + BYTE byMSB; + BYTE byLSB; + BYTE byPad[1]; +} DMUS_PATCH_PMSG; + +/* DMUS_TRANSPOSE_PMSG */ +typedef struct _DMUS_TRANSPOSE_PMSG +{ + /* begin DMUS_PMSG_PART */ + DMUS_PMSG_PART + /* end DMUS_PMSG_PART */ + + short nTranspose; + /* Following exists only under DX8 and on (check dwFlags for DMUS_PMSGF_DX8) */ + WORD wMergeIndex; /* Allows multiple parameters to be merged (pitchbend, volume, and expression.)*/ +} DMUS_TRANSPOSE_PMSG; + +/* DMUS_CHANNEL_PRIORITY_PMSG */ +typedef struct _DMUS_CHANNEL_PRIORITY_PMSG +{ + /* begin DMUS_PMSG_PART */ + DMUS_PMSG_PART + /* end DMUS_PMSG_PART */ + + DWORD dwChannelPriority; +} DMUS_CHANNEL_PRIORITY_PMSG; + +/* DMUS_TEMPO_PMSG */ +typedef struct _DMUS_TEMPO_PMSG +{ + /* begin DMUS_PMSG_PART */ + DMUS_PMSG_PART + /* end DMUS_PMSG_PART */ + + double dblTempo; /* the tempo */ +} DMUS_TEMPO_PMSG; + +#define DMUS_TEMPO_MAX 1000 +#define DMUS_TEMPO_MIN 1 + +#define DMUS_MASTERTEMPO_MAX 100.0f +#define DMUS_MASTERTEMPO_MIN 0.01f + +/* DMUS_SYSEX_PMSG */ +typedef struct _DMUS_SYSEX_PMSG +{ + /* begin DMUS_PMSG_PART */ + DMUS_PMSG_PART + /* end DMUS_PMSG_PART */ + + DWORD dwLen; /* length of the data */ + BYTE abData[1]; /* array of data, length equal to dwLen */ +} DMUS_SYSEX_PMSG; + +/* DMUS_CURVE_PMSG */ +typedef struct _DMUS_CURVE_PMSG +{ + /* begin DMUS_PMSG_PART */ + DMUS_PMSG_PART + /* end DMUS_PMSG_PART */ + + MUSIC_TIME mtDuration; /* how long this curve lasts */ + MUSIC_TIME mtOriginalStart; /* must be set to either zero when this PMSG is created or to the original mtTime of the curve */ + MUSIC_TIME mtResetDuration; /* how long after the curve is finished to allow a flush or + invalidation to reset to the reset value, nResetValue */ + short nStartValue; /* curve's start value */ + short nEndValue; /* curve's end value */ + short nResetValue; /* curve's reset value, set when a flush or invalidation + occurs within mtDuration + mtResetDuration */ + WORD wMeasure; /* Measure in which this curve occurs */ + short nOffset; /* Offset from grid at which this curve occurs */ + BYTE bBeat; /* Beat (in measure) at which this curve occurs */ + BYTE bGrid; /* Grid offset from beat at which this curve occurs */ + BYTE bType; /* type of curve */ + BYTE bCurveShape; /* shape of curve */ + BYTE bCCData; /* CC# if this is a control change type */ + BYTE bFlags; /* Curve reset and start from current value flags. */ + /* Following exists only under DX8 and on (check dwFlags for DMUS_PMSGF_DX8) */ + WORD wParamType; /* RPN or NRPN parameter number. */ + WORD wMergeIndex; /* Allows multiple parameters to be merged (pitchbend, volume, and expression.)*/ +} DMUS_CURVE_PMSG; + +typedef enum enumDMUS_CURVE_FLAGS +{ + DMUS_CURVE_RESET = 1, /* When set, the nResetValue must be sent when the + time is reached or an invalidate occurs because + of a transition. If not set, the curve stays + permanently stuck at the new value. */ + DMUS_CURVE_START_FROM_CURRENT = 2/* Ignore Start, start the curve at the current value. + This only works for volume, expression, and pitchbend. */ +} DMUS_CURVE_FLAGS; + + +#define DMUS_CURVE_RESET 1 + +/* Curve shapes */ +enum +{ + DMUS_CURVES_LINEAR = 0, + DMUS_CURVES_INSTANT = 1, + DMUS_CURVES_EXP = 2, + DMUS_CURVES_LOG = 3, + DMUS_CURVES_SINE = 4 +}; +/* curve types */ +#define DMUS_CURVET_PBCURVE 0x03 /* Pitch bend curve. */ +#define DMUS_CURVET_CCCURVE 0x04 /* Control change curve. */ +#define DMUS_CURVET_MATCURVE 0x05 /* Mono aftertouch curve. */ +#define DMUS_CURVET_PATCURVE 0x06 /* Poly aftertouch curve. */ +#define DMUS_CURVET_RPNCURVE 0x07 /* RPN curve with curve type in wParamType. */ +#define DMUS_CURVET_NRPNCURVE 0x08 /* NRPN curve with curve type in wParamType. */ + +/* DMUS_TIMESIG_PMSG */ +typedef struct _DMUS_TIMESIG_PMSG +{ + /* begin DMUS_PMSG_PART */ + DMUS_PMSG_PART + /* end DMUS_PMSG_PART */ + + /* Time signatures define how many beats per measure, which note receives */ + /* the beat, and the grid resolution. */ + BYTE bBeatsPerMeasure; /* beats per measure (top of time sig) */ + BYTE bBeat; /* what note receives the beat (bottom of time sig.) */ + /* we can assume that 0 means 256th note */ + WORD wGridsPerBeat; /* grids per beat */ +} DMUS_TIMESIG_PMSG; + + + +/* notification type values */ +/* The following correspond to GUID_NOTIFICATION_SEGMENT */ +#define DMUS_NOTIFICATION_SEGSTART 0 +#define DMUS_NOTIFICATION_SEGEND 1 +#define DMUS_NOTIFICATION_SEGALMOSTEND 2 +#define DMUS_NOTIFICATION_SEGLOOP 3 +#define DMUS_NOTIFICATION_SEGABORT 4 +/* The following correspond to GUID_NOTIFICATION_PERFORMANCE */ +#define DMUS_NOTIFICATION_MUSICSTARTED 0 +#define DMUS_NOTIFICATION_MUSICSTOPPED 1 +#define DMUS_NOTIFICATION_MUSICALMOSTEND 2 +/* The following corresponds to GUID_NOTIFICATION_MEASUREANDBEAT */ +#define DMUS_NOTIFICATION_MEASUREBEAT 0 +/* The following corresponds to GUID_NOTIFICATION_CHORD */ +#define DMUS_NOTIFICATION_CHORD 0 +/* The following correspond to GUID_NOTIFICATION_COMMAND */ +#define DMUS_NOTIFICATION_GROOVE 0 +#define DMUS_NOTIFICATION_EMBELLISHMENT 1 +/* The following corresponds to GUID_NOTIFICATION_RECOMPOSE */ +#define DMUS_NOTIFICATION_RECOMPOSE 0 + +/* DMUS_NOTIFICATION_PMSG */ +typedef struct _DMUS_NOTIFICATION_PMSG +{ + /* begin DMUS_PMSG_PART */ + DMUS_PMSG_PART + /* end DMUS_PMSG_PART */ + + GUID guidNotificationType; + DWORD dwNotificationOption; + DWORD dwField1; + DWORD dwField2; +} DMUS_NOTIFICATION_PMSG; + +/* DMUS_WAVE_PMSG */ +typedef struct _DMUS_WAVE_PMSG +{ + /* begin DMUS_PMSG_PART */ + DMUS_PMSG_PART + /* end DMUS_PMSG_PART */ + + REFERENCE_TIME rtStartOffset; /* How far into the wave to start, in reference time units only. */ + REFERENCE_TIME rtDuration; /* Duration of the wave, in either reference time or music time. */ + long lOffset; /* Offset from actual time to logical time, in music or ref time. */ + long lVolume; /* Initial volume, in 100ths of a dB. */ + long lPitch; /* Initial pitch, in 100ths of a semitone. */ + BYTE bFlags; /* Flags, including DMUS_WAVEF_OFF... */ +} DMUS_WAVE_PMSG; + +#define DMUS_WAVEF_OFF 1 /* If wave is playing and this is the off message. */ +#define DMUS_WAVEF_STREAMING 2 /* If wave is streaming. */ +#define DMUS_WAVEF_NOINVALIDATE 4 /* Don't invalidate this wave. */ +#define DMUS_WAVEF_NOPREROLL 8 /* Don't preroll any wave data. */ + +/* DMUS_LYRIC_PMSG */ +typedef struct _DMUS_LYRIC_PMSG +{ + /* begin DMUS_PMSG_PART */ + DMUS_PMSG_PART + /* end DMUS_PMSG_PART */ + + WCHAR wszString[1]; /* null-terminated Unicode lyric string (structure is actually larger than size 1) */ +} DMUS_LYRIC_PMSG; + +#define DMUS_MAX_NAME 64 /* Maximum object name length. */ +#define DMUS_MAX_CATEGORY 64 /* Maximum object category name length. */ +#define DMUS_MAX_FILENAME MAX_PATH + +typedef struct _DMUS_VERSION { + DWORD dwVersionMS; + DWORD dwVersionLS; +}DMUS_VERSION, FAR *LPDMUS_VERSION; + +/* Time Signature structure, used by IDirectMusicStyle */ +/* Also used as a parameter for GetParam() and SetParam */ +typedef struct _DMUS_TIMESIGNATURE +{ + MUSIC_TIME mtTime; + BYTE bBeatsPerMeasure; /* beats per measure (top of time sig) */ + BYTE bBeat; /* what note receives the beat (bottom of time sig.) */ + /* we can assume that 0 means 256th note */ + WORD wGridsPerBeat; /* grids per beat */ +} DMUS_TIMESIGNATURE; + +typedef struct _DMUS_VALID_START_PARAM +{ + MUSIC_TIME mtTime; /* Time of the first legal start + point after (or including) the requested time. + This is a returned value. + Time format is the relative offset from requested time. */ +} DMUS_VALID_START_PARAM; + +typedef struct _DMUS_PLAY_MARKER_PARAM +{ + MUSIC_TIME mtTime; /* Time of the first legal segment play + marker before (or including) the requested time. + This is a returned value. + Time format is the relative offset from requested time. */ +} DMUS_PLAY_MARKER_PARAM; + +/* The DMUSOBJECTDESC structure is used to communicate everything you could */ +/* possibly use to describe a DirectMusic object. */ + +typedef struct _DMUS_OBJECTDESC +{ + DWORD dwSize; /* Size of this structure. */ + DWORD dwValidData; /* Flags indicating which fields below are valid. */ + GUID guidObject; /* Unique ID for this object. */ + GUID guidClass; /* GUID for the class of object. */ + FILETIME ftDate; /* Last edited date of object. */ + DMUS_VERSION vVersion; /* Version. */ + WCHAR wszName[DMUS_MAX_NAME]; /* Name of object. */ + WCHAR wszCategory[DMUS_MAX_CATEGORY]; /* Category for object (optional). */ + WCHAR wszFileName[DMUS_MAX_FILENAME]; /* File path. */ + LONGLONG llMemLength; /* Size of Memory data. */ + LPBYTE pbMemData; /* Memory pointer for data. */ + IStream * pStream; /* Stream with data. */ +} DMUS_OBJECTDESC; + +typedef DMUS_OBJECTDESC *LPDMUS_OBJECTDESC; + +/* Flags for dwValidData. When set, a flag indicates that the */ +/* corresponding field in DMUSOBJECTDESC holds valid data. */ + +#define DMUS_OBJ_OBJECT (1 << 0) /* Object GUID is valid. */ +#define DMUS_OBJ_CLASS (1 << 1) /* Class GUID is valid. */ +#define DMUS_OBJ_NAME (1 << 2) /* Name is valid. */ +#define DMUS_OBJ_CATEGORY (1 << 3) /* Category is valid. */ +#define DMUS_OBJ_FILENAME (1 << 4) /* File path is valid. */ +#define DMUS_OBJ_FULLPATH (1 << 5) /* Path is full path. */ +#define DMUS_OBJ_URL (1 << 6) /* Path is URL. */ +#define DMUS_OBJ_VERSION (1 << 7) /* Version is valid. */ +#define DMUS_OBJ_DATE (1 << 8) /* Date is valid. */ +#define DMUS_OBJ_LOADED (1 << 9) /* Object is currently loaded in memory. */ +#define DMUS_OBJ_MEMORY (1 << 10) /* Object is pointed to by pbMemData. */ +#define DMUS_OBJ_STREAM (1 << 11) /* Object is stored in pStream. */ + +/* The DMUS_SCRIPT_ERRORINFO structure describes an error that occurred in a script. + It is returned by methods in IDirectMusicScript. */ +typedef struct _DMUS_SCRIPT_ERRORINFO +{ + DWORD dwSize; /* Size of this structure. */ + HRESULT hr; + ULONG ulLineNumber; + LONG ichCharPosition; + WCHAR wszSourceFile[DMUS_MAX_FILENAME]; + WCHAR wszSourceComponent[DMUS_MAX_FILENAME]; + WCHAR wszDescription[DMUS_MAX_FILENAME]; + WCHAR wszSourceLineText[DMUS_MAX_FILENAME]; +} DMUS_SCRIPT_ERRORINFO; + +/* Track configuration flags, used with IDirectMusicSegment8::SetTrackConfig() */ + +#define DMUS_TRACKCONFIG_OVERRIDE_ALL 1 /* This track should get parameters from this segment before controlling and primary tracks. */ +#define DMUS_TRACKCONFIG_OVERRIDE_PRIMARY 2 /* This track should get parameters from this segment before the primary segment tracks. */ +#define DMUS_TRACKCONFIG_FALLBACK 4 /* This track should get parameters from this segment if the primary and controlling segments don't succeed. */ +#define DMUS_TRACKCONFIG_CONTROL_ENABLED 8 /* GetParam() enabled for this track. */ +#define DMUS_TRACKCONFIG_PLAY_ENABLED 0x10 /* Play() enabled for this track. */ +#define DMUS_TRACKCONFIG_NOTIFICATION_ENABLED 0x20 /* Notifications enabled for this track. */ +#define DMUS_TRACKCONFIG_PLAY_CLOCKTIME 0x40 /* This track plays in clock time, not music time. */ +#define DMUS_TRACKCONFIG_PLAY_COMPOSE 0x80 /* This track should regenerate data each time it starts playing. */ +#define DMUS_TRACKCONFIG_LOOP_COMPOSE 0x100 /* This track should regenerate data each time it repeats. */ +#define DMUS_TRACKCONFIG_COMPOSING 0x200 /* This track is used to compose other tracks. */ +#define DMUS_TRACKCONFIG_CONTROL_PLAY 0x10000 /* This track, when played in a controlling segment, overrides playback of primary segment tracks. */ +#define DMUS_TRACKCONFIG_CONTROL_NOTIFICATION 0x20000 /* This track, when played in a controlling segment, overrides notification of primary segment tracks. */ +/* Additional track config flags for composing transitions */ +#define DMUS_TRACKCONFIG_TRANS1_FROMSEGSTART 0x400 /* Get track info from start of From segment */ +#define DMUS_TRACKCONFIG_TRANS1_FROMSEGCURRENT 0x800 /* Get track info from current place in From segment */ +#define DMUS_TRACKCONFIG_TRANS1_TOSEGSTART 0x1000 /* Get track info from start of To segment */ +#define DMUS_TRACKCONFIG_DEFAULT (DMUS_TRACKCONFIG_CONTROL_ENABLED | DMUS_TRACKCONFIG_PLAY_ENABLED | DMUS_TRACKCONFIG_NOTIFICATION_ENABLED) + +/* #defines for melody fragments */ +/* Note: Melody formulation is not supported in DX8. */ + +#define DMUS_MAX_FRAGMENTLABEL 20 + +#define DMUS_FRAGMENTF_USE_REPEAT 0x1 +#define DMUS_FRAGMENTF_REJECT_REPEAT (0x1 << 1) +#define DMUS_FRAGMENTF_USE_LABEL (0x1 << 2) + +#define DMUS_CONNECTIONF_INTERVALS (0x1 << 1) /* Use transition intervals */ +#define DMUS_CONNECTIONF_OVERLAP (0x1 << 2) /* Use overlapping notes for transitions */ + +/* Get/SetParam structs for commands */ +/* PARAM structures, used by GetParam() and SetParam() */ +typedef struct _DMUS_COMMAND_PARAM +{ + BYTE bCommand; + BYTE bGrooveLevel; + BYTE bGrooveRange; + BYTE bRepeatMode; +} DMUS_COMMAND_PARAM; + +typedef struct _DMUS_COMMAND_PARAM_2 +{ + MUSIC_TIME mtTime; + BYTE bCommand; + BYTE bGrooveLevel; + BYTE bGrooveRange; + BYTE bRepeatMode; +} DMUS_COMMAND_PARAM_2; + +/* Get/SetParam structs for melody fragments */ +/* Note: Melody formulation is not supported in DX8. */ +typedef struct _DMUS_CONNECTION_RULE +{ + DWORD dwFlags; /* DMUS_CONNECTIONF_ flags */ + DWORD dwIntervals; /* Legal transition intervals (first 24 bits; two-octave range) */ +} DMUS_CONNECTION_RULE; + +typedef struct _DMUS_MELODY_FRAGMENT +{ + MUSIC_TIME mtTime; + DWORD dwID; /* This fragment's ID */ + WCHAR wszVariationLabel[DMUS_MAX_FRAGMENTLABEL]; /* Each style translates this into a set of variations (held in part ref) */ + DWORD dwVariationFlags; /* A set of variations */ + DWORD dwRepeatFragmentID; /* ID of a fragment to repeat */ + DWORD dwFragmentFlags; /* DMUS_FRAGMENTF_ flags */ + DWORD dwPlayModeFlags; /* NOT CURRENTLY USED - MUST BE 0 */ + DWORD dwTransposeIntervals; /* Legal transposition intervals (first 24 bits; two-octave range) */ + DMUS_COMMAND_PARAM Command; + DMUS_CONNECTION_RULE ConnectionArc; +} DMUS_MELODY_FRAGMENT; + +typedef IDirectMusicObject __RPC_FAR *LPDMUS_OBJECT; +typedef IDirectMusicLoader __RPC_FAR *LPDMUS_LOADER; +typedef IDirectMusicBand __RPC_FAR *LPDMUS_BAND; + +#define DMUSB_LOADED (1 << 0) /* Set when band has been loaded */ +#define DMUSB_DEFAULT (1 << 1) /* Set when band is default band for a style */ + +/*//////////////////////////////////////////////////////////////////// +// IDirectMusicBand */ +#undef INTERFACE +#define INTERFACE IDirectMusicBand +DECLARE_INTERFACE_(IDirectMusicBand, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicBand */ + STDMETHOD(CreateSegment) (THIS_ IDirectMusicSegment** ppSegment) PURE; + STDMETHOD(Download) (THIS_ IDirectMusicPerformance* pPerformance) PURE; + STDMETHOD(Unload) (THIS_ IDirectMusicPerformance* pPerformance) PURE; +}; + +typedef IDirectMusicBand IDirectMusicBand8; + +/*//////////////////////////////////////////////////////////////////// +// IDirectMusicObject */ +#undef INTERFACE +#define INTERFACE IDirectMusicObject +DECLARE_INTERFACE_(IDirectMusicObject, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicObject */ + STDMETHOD(GetDescriptor) (THIS_ LPDMUS_OBJECTDESC pDesc) PURE; + STDMETHOD(SetDescriptor) (THIS_ LPDMUS_OBJECTDESC pDesc) PURE; + STDMETHOD(ParseDescriptor) (THIS_ LPSTREAM pStream, + LPDMUS_OBJECTDESC pDesc) PURE; +}; + +typedef IDirectMusicObject IDirectMusicObject8; + +/*//////////////////////////////////////////////////////////////////// +// IDirectMusicLoader */ +#undef INTERFACE +#define INTERFACE IDirectMusicLoader +DECLARE_INTERFACE_(IDirectMusicLoader, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicLoader */ + STDMETHOD(GetObject) (THIS_ LPDMUS_OBJECTDESC pDesc, + REFIID riid, + LPVOID FAR *ppv) PURE; + STDMETHOD(SetObject) (THIS_ LPDMUS_OBJECTDESC pDesc) PURE; + STDMETHOD(SetSearchDirectory) (THIS_ REFGUID rguidClass, + WCHAR *pwzPath, + BOOL fClear) PURE; + STDMETHOD(ScanDirectory) (THIS_ REFGUID rguidClass, + WCHAR *pwzFileExtension, + WCHAR *pwzScanFileName) PURE; + STDMETHOD(CacheObject) (THIS_ IDirectMusicObject * pObject) PURE; + STDMETHOD(ReleaseObject) (THIS_ IDirectMusicObject * pObject) PURE; + STDMETHOD(ClearCache) (THIS_ REFGUID rguidClass) PURE; + STDMETHOD(EnableCache) (THIS_ REFGUID rguidClass, + BOOL fEnable) PURE; + STDMETHOD(EnumObject) (THIS_ REFGUID rguidClass, + DWORD dwIndex, + LPDMUS_OBJECTDESC pDesc) PURE; +}; + +/*//////////////////////////////////////////////////////////////////// +// IDirectMusicLoader8 */ +#undef INTERFACE +#define INTERFACE IDirectMusicLoader8 +DECLARE_INTERFACE_(IDirectMusicLoader8, IDirectMusicLoader) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicLoader */ + STDMETHOD(GetObject) (THIS_ LPDMUS_OBJECTDESC pDesc, + REFIID riid, + LPVOID FAR *ppv) PURE; + STDMETHOD(SetObject) (THIS_ LPDMUS_OBJECTDESC pDesc) PURE; + STDMETHOD(SetSearchDirectory) (THIS_ REFGUID rguidClass, + WCHAR *pwzPath, + BOOL fClear) PURE; + STDMETHOD(ScanDirectory) (THIS_ REFGUID rguidClass, + WCHAR *pwzFileExtension, + WCHAR *pwzScanFileName) PURE; + STDMETHOD(CacheObject) (THIS_ IDirectMusicObject * pObject) PURE; + STDMETHOD(ReleaseObject) (THIS_ IDirectMusicObject * pObject) PURE; + STDMETHOD(ClearCache) (THIS_ REFGUID rguidClass) PURE; + STDMETHOD(EnableCache) (THIS_ REFGUID rguidClass, + BOOL fEnable) PURE; + STDMETHOD(EnumObject) (THIS_ REFGUID rguidClass, + DWORD dwIndex, + LPDMUS_OBJECTDESC pDesc) PURE; + + /* IDirectMusicLoader8 */ + STDMETHOD_(void, CollectGarbage) (THIS) PURE; + STDMETHOD(ReleaseObjectByUnknown) (THIS_ IUnknown *pObject) PURE; + STDMETHOD(LoadObjectFromFile) (THIS_ REFGUID rguidClassID, + REFIID iidInterfaceID, + WCHAR *pwzFilePath, + void ** ppObject) PURE; +}; + +/* Stream object supports IDirectMusicGetLoader interface to access loader while file parsing. */ + +#undef INTERFACE +#define INTERFACE IDirectMusicGetLoader +DECLARE_INTERFACE_(IDirectMusicGetLoader, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicGetLoader */ + STDMETHOD(GetLoader) (THIS_ IDirectMusicLoader ** ppLoader) PURE; +}; + +typedef IDirectMusicGetLoader IDirectMusicGetLoader8; + +/*//////////////////////////////////////////////////////////////////// +// IDirectMusicSegment */ +#undef INTERFACE +#define INTERFACE IDirectMusicSegment +DECLARE_INTERFACE_(IDirectMusicSegment, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicSegment */ + STDMETHOD(GetLength) (THIS_ MUSIC_TIME* pmtLength) PURE; + STDMETHOD(SetLength) (THIS_ MUSIC_TIME mtLength) PURE; + STDMETHOD(GetRepeats) (THIS_ DWORD* pdwRepeats) PURE; + STDMETHOD(SetRepeats) (THIS_ DWORD dwRepeats) PURE; + STDMETHOD(GetDefaultResolution) (THIS_ DWORD* pdwResolution) PURE; + STDMETHOD(SetDefaultResolution) (THIS_ DWORD dwResolution) PURE; + STDMETHOD(GetTrack) (THIS_ REFGUID rguidType, + DWORD dwGroupBits, + DWORD dwIndex, + IDirectMusicTrack** ppTrack) PURE; + STDMETHOD(GetTrackGroup) (THIS_ IDirectMusicTrack* pTrack, + DWORD* pdwGroupBits) PURE; + STDMETHOD(InsertTrack) (THIS_ IDirectMusicTrack* pTrack, + DWORD dwGroupBits) PURE; + STDMETHOD(RemoveTrack) (THIS_ IDirectMusicTrack* pTrack) PURE; + STDMETHOD(InitPlay) (THIS_ IDirectMusicSegmentState** ppSegState, + IDirectMusicPerformance* pPerformance, + DWORD dwFlags) PURE; + STDMETHOD(GetGraph) (THIS_ IDirectMusicGraph** ppGraph) PURE; + STDMETHOD(SetGraph) (THIS_ IDirectMusicGraph* pGraph) PURE; + STDMETHOD(AddNotificationType) (THIS_ REFGUID rguidNotificationType) PURE; + STDMETHOD(RemoveNotificationType) (THIS_ REFGUID rguidNotificationType) PURE; + STDMETHOD(GetParam) (THIS_ REFGUID rguidType, + DWORD dwGroupBits, + DWORD dwIndex, + MUSIC_TIME mtTime, + MUSIC_TIME* pmtNext, + void* pParam) PURE; + STDMETHOD(SetParam) (THIS_ REFGUID rguidType, + DWORD dwGroupBits, + DWORD dwIndex, + MUSIC_TIME mtTime, + void* pParam) PURE; + STDMETHOD(Clone) (THIS_ MUSIC_TIME mtStart, + MUSIC_TIME mtEnd, + IDirectMusicSegment** ppSegment) PURE; + STDMETHOD(SetStartPoint) (THIS_ MUSIC_TIME mtStart) PURE; + STDMETHOD(GetStartPoint) (THIS_ MUSIC_TIME* pmtStart) PURE; + STDMETHOD(SetLoopPoints) (THIS_ MUSIC_TIME mtStart, + MUSIC_TIME mtEnd) PURE; + STDMETHOD(GetLoopPoints) (THIS_ MUSIC_TIME* pmtStart, + MUSIC_TIME* pmtEnd) PURE; + STDMETHOD(SetPChannelsUsed) (THIS_ DWORD dwNumPChannels, + DWORD* paPChannels) PURE; +}; + +/*//////////////////////////////////////////////////////////////////// +// IDirectMusicSegment8 */ +#undef INTERFACE +#define INTERFACE IDirectMusicSegment8 +DECLARE_INTERFACE_(IDirectMusicSegment8, IDirectMusicSegment) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicSegment */ + STDMETHOD(GetLength) (THIS_ MUSIC_TIME* pmtLength) PURE; + STDMETHOD(SetLength) (THIS_ MUSIC_TIME mtLength) PURE; + STDMETHOD(GetRepeats) (THIS_ DWORD* pdwRepeats) PURE; + STDMETHOD(SetRepeats) (THIS_ DWORD dwRepeats) PURE; + STDMETHOD(GetDefaultResolution) (THIS_ DWORD* pdwResolution) PURE; + STDMETHOD(SetDefaultResolution) (THIS_ DWORD dwResolution) PURE; + STDMETHOD(GetTrack) (THIS_ REFGUID rguidType, + DWORD dwGroupBits, + DWORD dwIndex, + IDirectMusicTrack** ppTrack) PURE; + STDMETHOD(GetTrackGroup) (THIS_ IDirectMusicTrack* pTrack, + DWORD* pdwGroupBits) PURE; + STDMETHOD(InsertTrack) (THIS_ IDirectMusicTrack* pTrack, + DWORD dwGroupBits) PURE; + STDMETHOD(RemoveTrack) (THIS_ IDirectMusicTrack* pTrack) PURE; + STDMETHOD(InitPlay) (THIS_ IDirectMusicSegmentState** ppSegState, + IDirectMusicPerformance* pPerformance, + DWORD dwFlags) PURE; + STDMETHOD(GetGraph) (THIS_ IDirectMusicGraph** ppGraph) PURE; + STDMETHOD(SetGraph) (THIS_ IDirectMusicGraph* pGraph) PURE; + STDMETHOD(AddNotificationType) (THIS_ REFGUID rguidNotificationType) PURE; + STDMETHOD(RemoveNotificationType) (THIS_ REFGUID rguidNotificationType) PURE; + STDMETHOD(GetParam) (THIS_ REFGUID rguidType, + DWORD dwGroupBits, + DWORD dwIndex, + MUSIC_TIME mtTime, + MUSIC_TIME* pmtNext, + void* pParam) PURE; + STDMETHOD(SetParam) (THIS_ REFGUID rguidType, + DWORD dwGroupBits, + DWORD dwIndex, + MUSIC_TIME mtTime, + void* pParam) PURE; + STDMETHOD(Clone) (THIS_ MUSIC_TIME mtStart, + MUSIC_TIME mtEnd, + IDirectMusicSegment** ppSegment) PURE; + STDMETHOD(SetStartPoint) (THIS_ MUSIC_TIME mtStart) PURE; + STDMETHOD(GetStartPoint) (THIS_ MUSIC_TIME* pmtStart) PURE; + STDMETHOD(SetLoopPoints) (THIS_ MUSIC_TIME mtStart, + MUSIC_TIME mtEnd) PURE; + STDMETHOD(GetLoopPoints) (THIS_ MUSIC_TIME* pmtStart, + MUSIC_TIME* pmtEnd) PURE; + STDMETHOD(SetPChannelsUsed) (THIS_ DWORD dwNumPChannels, + DWORD* paPChannels) PURE; + /* IDirectMusicSegment8 */ + STDMETHOD(SetTrackConfig) (THIS_ REFGUID rguidTrackClassID, /* Class ID of the type of track on which to set the configuration flags. */ + DWORD dwGroupBits, /* Group bits. */ + DWORD dwIndex, /* Nth track (or DMUS_SEG_ALLTRACKS) that matches class id and group id. */ + DWORD dwFlagsOn, /* DMUS_TRACKCONFIG_ flags to enable. */ + DWORD dwFlagsOff) PURE; /* DMUS_TRACKCONFIG_ flags to disable. */ + STDMETHOD(GetAudioPathConfig) (THIS_ IUnknown ** ppAudioPathConfig) PURE; + STDMETHOD(Compose) (THIS_ MUSIC_TIME mtTime, + IDirectMusicSegment* pFromSegment, + IDirectMusicSegment* pToSegment, + IDirectMusicSegment** ppComposedSegment) PURE; + STDMETHOD(Download) (THIS_ IUnknown *pAudioPath) PURE; + STDMETHOD(Unload) (THIS_ IUnknown *pAudioPath) PURE; +}; + +/*///////////////////////////////////////////////////////////////////// +// IDirectMusicSegmentState */ +#undef INTERFACE +#define INTERFACE IDirectMusicSegmentState +DECLARE_INTERFACE_(IDirectMusicSegmentState, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicSegmentState */ + STDMETHOD(GetRepeats) (THIS_ DWORD* pdwRepeats) PURE; + STDMETHOD(GetSegment ) (THIS_ IDirectMusicSegment** ppSegment) PURE; + STDMETHOD(GetStartTime) (THIS_ MUSIC_TIME* pmtStart) PURE; + STDMETHOD(GetSeek) (THIS_ MUSIC_TIME* pmtSeek) PURE; + STDMETHOD(GetStartPoint) (THIS_ MUSIC_TIME* pmtStart) PURE; +}; + +/*///////////////////////////////////////////////////////////////////// +// IDirectMusicSegmentState8 */ +#undef INTERFACE +#define INTERFACE IDirectMusicSegmentState8 +DECLARE_INTERFACE_(IDirectMusicSegmentState8, IDirectMusicSegmentState) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicSegmentState */ + STDMETHOD(GetRepeats) (THIS_ DWORD* pdwRepeats) PURE; + STDMETHOD(GetSegment ) (THIS_ IDirectMusicSegment** ppSegment) PURE; + STDMETHOD(GetStartTime) (THIS_ MUSIC_TIME* pmtStart) PURE; + STDMETHOD(GetSeek) (THIS_ MUSIC_TIME* pmtSeek) PURE; + STDMETHOD(GetStartPoint) (THIS_ MUSIC_TIME* pmtStart) PURE; + + /* IDirectMusicSegmentState8 */ + STDMETHOD(SetTrackConfig) (THIS_ REFGUID rguidTrackClassID, /* Class ID of the type of track on which to set the configuration flags. */ + DWORD dwGroupBits, /* Group bits. */ + DWORD dwIndex, /* Nth track (or DMUS_SEG_ALLTRACKS) that matches class id and group id. */ + DWORD dwFlagsOn, /* DMUS_TRACKCONFIG_ flags to enable. */ + DWORD dwFlagsOff) PURE; /* DMUS_TRACKCONFIG_ flags to disable. */ + STDMETHOD(GetObjectInPath) (THIS_ DWORD dwPChannel, /* PChannel to search. */ + DWORD dwStage, /* Which stage in the path. */ + DWORD dwBuffer, /* Which buffer to address, if more than one. */ + REFGUID guidObject, /* ClassID of object. */ + DWORD dwIndex, /* Which object of that class. */ + REFGUID iidInterface,/* Requested COM interface. */ + void ** ppObject) PURE; /* Pointer to interface. */ +}; + +/*//////////////////////////////////////////////////////////////////// +// IDirectMusicAudioPath */ +#undef INTERFACE +#define INTERFACE IDirectMusicAudioPath +DECLARE_INTERFACE_(IDirectMusicAudioPath, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicAudioPath */ + STDMETHOD(GetObjectInPath) (THIS_ DWORD dwPChannel, /* PChannel to search. */ + DWORD dwStage, /* Which stage in the path. */ + DWORD dwBuffer, /* Which buffer to address, if more than one. */ + REFGUID guidObject, /* ClassID of object. */ + DWORD dwIndex, /* Which object of that class. */ + REFGUID iidInterface,/* Requested COM interface. */ + void ** ppObject) PURE; /* Pointer to interface. */ + STDMETHOD(Activate) (THIS_ BOOL fActivate) PURE;/* True to activate, False to deactivate. */ + STDMETHOD(SetVolume) (THIS_ long lVolume, /* Gain, in 100ths of a dB. This must be negative (0 represents full volume.) */ + DWORD dwDuration) PURE;/* Duration of volume ramp in milliseconds. Note that 0 is more efficient. */ + STDMETHOD(ConvertPChannel) (THIS_ DWORD dwPChannelIn, /* Pchannel of source. */ + DWORD *pdwPChannelOut) PURE; /* Equivalent pchannel on performance. */ +}; + +typedef IDirectMusicAudioPath IDirectMusicAudioPath8; + +/*//////////////////////////////////////////////////////////////////// +// IDirectMusicPerformance */ +#undef INTERFACE +#define INTERFACE IDirectMusicPerformance +DECLARE_INTERFACE_(IDirectMusicPerformance, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicPerformance */ + STDMETHOD(Init) (THIS_ IDirectMusic** ppDirectMusic, + LPDIRECTSOUND pDirectSound, + HWND hWnd) PURE; + STDMETHOD(PlaySegment) (THIS_ IDirectMusicSegment* pSegment, + DWORD dwFlags, + __int64 i64StartTime, + IDirectMusicSegmentState** ppSegmentState) PURE; + STDMETHOD(Stop) (THIS_ IDirectMusicSegment* pSegment, + IDirectMusicSegmentState* pSegmentState, + MUSIC_TIME mtTime, + DWORD dwFlags) PURE; + STDMETHOD(GetSegmentState) (THIS_ IDirectMusicSegmentState** ppSegmentState, + MUSIC_TIME mtTime) PURE; + STDMETHOD(SetPrepareTime) (THIS_ DWORD dwMilliSeconds) PURE; + STDMETHOD(GetPrepareTime) (THIS_ DWORD* pdwMilliSeconds) PURE; + STDMETHOD(SetBumperLength) (THIS_ DWORD dwMilliSeconds) PURE; + STDMETHOD(GetBumperLength) (THIS_ DWORD* pdwMilliSeconds) PURE; + STDMETHOD(SendPMsg) (THIS_ DMUS_PMSG* pPMSG) PURE; + STDMETHOD(MusicToReferenceTime) (THIS_ MUSIC_TIME mtTime, + REFERENCE_TIME* prtTime) PURE; + STDMETHOD(ReferenceToMusicTime) (THIS_ REFERENCE_TIME rtTime, + MUSIC_TIME* pmtTime) PURE; + STDMETHOD(IsPlaying) (THIS_ IDirectMusicSegment* pSegment, + IDirectMusicSegmentState* pSegState) PURE; + STDMETHOD(GetTime) (THIS_ REFERENCE_TIME* prtNow, + MUSIC_TIME* pmtNow) PURE; + STDMETHOD(AllocPMsg) (THIS_ ULONG cb, + DMUS_PMSG** ppPMSG) PURE; + STDMETHOD(FreePMsg) (THIS_ DMUS_PMSG* pPMSG) PURE; + STDMETHOD(GetGraph) (THIS_ IDirectMusicGraph** ppGraph) PURE; + STDMETHOD(SetGraph) (THIS_ IDirectMusicGraph* pGraph) PURE; + STDMETHOD(SetNotificationHandle)(THIS_ HANDLE hNotification, + REFERENCE_TIME rtMinimum) PURE; + STDMETHOD(GetNotificationPMsg) (THIS_ DMUS_NOTIFICATION_PMSG** ppNotificationPMsg) PURE; + STDMETHOD(AddNotificationType) (THIS_ REFGUID rguidNotificationType) PURE; + STDMETHOD(RemoveNotificationType)(THIS_ REFGUID rguidNotificationType) PURE; + STDMETHOD(AddPort) (THIS_ IDirectMusicPort* pPort) PURE; + STDMETHOD(RemovePort) (THIS_ IDirectMusicPort* pPort ) PURE; + STDMETHOD(AssignPChannelBlock) (THIS_ DWORD dwBlockNum, + IDirectMusicPort* pPort, + DWORD dwGroup ) PURE; + STDMETHOD(AssignPChannel) (THIS_ DWORD dwPChannel, + IDirectMusicPort* pPort, + DWORD dwGroup, + DWORD dwMChannel ) PURE; + STDMETHOD(PChannelInfo) (THIS_ DWORD dwPChannel, + IDirectMusicPort** ppPort, + DWORD* pdwGroup, + DWORD* pdwMChannel ) PURE; + STDMETHOD(DownloadInstrument) (THIS_ IDirectMusicInstrument* pInst, + DWORD dwPChannel, + IDirectMusicDownloadedInstrument** ppDownInst, + DMUS_NOTERANGE* pNoteRanges, + DWORD dwNumNoteRanges, + IDirectMusicPort** ppPort, + DWORD* pdwGroup, + DWORD* pdwMChannel ) PURE; + STDMETHOD(Invalidate) (THIS_ MUSIC_TIME mtTime, + DWORD dwFlags) PURE; + STDMETHOD(GetParam) (THIS_ REFGUID rguidType, + DWORD dwGroupBits, + DWORD dwIndex, + MUSIC_TIME mtTime, + MUSIC_TIME* pmtNext, + void* pParam) PURE; + STDMETHOD(SetParam) (THIS_ REFGUID rguidType, + DWORD dwGroupBits, + DWORD dwIndex, + MUSIC_TIME mtTime, + void* pParam) PURE; + STDMETHOD(GetGlobalParam) (THIS_ REFGUID rguidType, + void* pParam, + DWORD dwSize) PURE; + STDMETHOD(SetGlobalParam) (THIS_ REFGUID rguidType, + void* pParam, + DWORD dwSize) PURE; + STDMETHOD(GetLatencyTime) (THIS_ REFERENCE_TIME* prtTime) PURE; + STDMETHOD(GetQueueTime) (THIS_ REFERENCE_TIME* prtTime) PURE; + STDMETHOD(AdjustTime) (THIS_ REFERENCE_TIME rtAmount) PURE; + STDMETHOD(CloseDown) (THIS) PURE; + STDMETHOD(GetResolvedTime) (THIS_ REFERENCE_TIME rtTime, + REFERENCE_TIME* prtResolved, + DWORD dwTimeResolveFlags) PURE; + STDMETHOD(MIDIToMusic) (THIS_ BYTE bMIDIValue, + DMUS_CHORD_KEY* pChord, + BYTE bPlayMode, + BYTE bChordLevel, + WORD *pwMusicValue) PURE; + STDMETHOD(MusicToMIDI) (THIS_ WORD wMusicValue, + DMUS_CHORD_KEY* pChord, + BYTE bPlayMode, + BYTE bChordLevel, + BYTE *pbMIDIValue) PURE; + STDMETHOD(TimeToRhythm) (THIS_ MUSIC_TIME mtTime, + DMUS_TIMESIGNATURE *pTimeSig, + WORD *pwMeasure, + BYTE *pbBeat, + BYTE *pbGrid, + short *pnOffset) PURE; + STDMETHOD(RhythmToTime) (THIS_ WORD wMeasure, + BYTE bBeat, + BYTE bGrid, + short nOffset, + DMUS_TIMESIGNATURE *pTimeSig, + MUSIC_TIME *pmtTime) PURE; +}; + +/*//////////////////////////////////////////////////////////////////// +// IDirectMusicPerformance8 */ +#undef INTERFACE +#define INTERFACE IDirectMusicPerformance8 +DECLARE_INTERFACE_(IDirectMusicPerformance8, IDirectMusicPerformance) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicPerformance */ + STDMETHOD(Init) (THIS_ IDirectMusic** ppDirectMusic, + LPDIRECTSOUND pDirectSound, + HWND hWnd) PURE; + STDMETHOD(PlaySegment) (THIS_ IDirectMusicSegment* pSegment, + DWORD dwFlags, + __int64 i64StartTime, + IDirectMusicSegmentState** ppSegmentState) PURE; + STDMETHOD(Stop) (THIS_ IDirectMusicSegment* pSegment, + IDirectMusicSegmentState* pSegmentState, + MUSIC_TIME mtTime, + DWORD dwFlags) PURE; + STDMETHOD(GetSegmentState) (THIS_ IDirectMusicSegmentState** ppSegmentState, + MUSIC_TIME mtTime) PURE; + STDMETHOD(SetPrepareTime) (THIS_ DWORD dwMilliSeconds) PURE; + STDMETHOD(GetPrepareTime) (THIS_ DWORD* pdwMilliSeconds) PURE; + STDMETHOD(SetBumperLength) (THIS_ DWORD dwMilliSeconds) PURE; + STDMETHOD(GetBumperLength) (THIS_ DWORD* pdwMilliSeconds) PURE; + STDMETHOD(SendPMsg) (THIS_ DMUS_PMSG* pPMSG) PURE; + STDMETHOD(MusicToReferenceTime) (THIS_ MUSIC_TIME mtTime, + REFERENCE_TIME* prtTime) PURE; + STDMETHOD(ReferenceToMusicTime) (THIS_ REFERENCE_TIME rtTime, + MUSIC_TIME* pmtTime) PURE; + STDMETHOD(IsPlaying) (THIS_ IDirectMusicSegment* pSegment, + IDirectMusicSegmentState* pSegState) PURE; + STDMETHOD(GetTime) (THIS_ REFERENCE_TIME* prtNow, + MUSIC_TIME* pmtNow) PURE; + STDMETHOD(AllocPMsg) (THIS_ ULONG cb, + DMUS_PMSG** ppPMSG) PURE; + STDMETHOD(FreePMsg) (THIS_ DMUS_PMSG* pPMSG) PURE; + STDMETHOD(GetGraph) (THIS_ IDirectMusicGraph** ppGraph) PURE; + STDMETHOD(SetGraph) (THIS_ IDirectMusicGraph* pGraph) PURE; + STDMETHOD(SetNotificationHandle)(THIS_ HANDLE hNotification, + REFERENCE_TIME rtMinimum) PURE; + STDMETHOD(GetNotificationPMsg) (THIS_ DMUS_NOTIFICATION_PMSG** ppNotificationPMsg) PURE; + STDMETHOD(AddNotificationType) (THIS_ REFGUID rguidNotificationType) PURE; + STDMETHOD(RemoveNotificationType)(THIS_ REFGUID rguidNotificationType) PURE; + STDMETHOD(AddPort) (THIS_ IDirectMusicPort* pPort) PURE; + STDMETHOD(RemovePort) (THIS_ IDirectMusicPort* pPort ) PURE; + STDMETHOD(AssignPChannelBlock) (THIS_ DWORD dwBlockNum, + IDirectMusicPort* pPort, + DWORD dwGroup ) PURE; + STDMETHOD(AssignPChannel) (THIS_ DWORD dwPChannel, + IDirectMusicPort* pPort, + DWORD dwGroup, + DWORD dwMChannel ) PURE; + STDMETHOD(PChannelInfo) (THIS_ DWORD dwPChannel, + IDirectMusicPort** ppPort, + DWORD* pdwGroup, + DWORD* pdwMChannel ) PURE; + STDMETHOD(DownloadInstrument) (THIS_ IDirectMusicInstrument* pInst, + DWORD dwPChannel, + IDirectMusicDownloadedInstrument** ppDownInst, + DMUS_NOTERANGE* pNoteRanges, + DWORD dwNumNoteRanges, + IDirectMusicPort** ppPort, + DWORD* pdwGroup, + DWORD* pdwMChannel ) PURE; + STDMETHOD(Invalidate) (THIS_ MUSIC_TIME mtTime, + DWORD dwFlags) PURE; + STDMETHOD(GetParam) (THIS_ REFGUID rguidType, + DWORD dwGroupBits, + DWORD dwIndex, + MUSIC_TIME mtTime, + MUSIC_TIME* pmtNext, + void* pParam) PURE; + STDMETHOD(SetParam) (THIS_ REFGUID rguidType, + DWORD dwGroupBits, + DWORD dwIndex, + MUSIC_TIME mtTime, + void* pParam) PURE; + STDMETHOD(GetGlobalParam) (THIS_ REFGUID rguidType, + void* pParam, + DWORD dwSize) PURE; + STDMETHOD(SetGlobalParam) (THIS_ REFGUID rguidType, + void* pParam, + DWORD dwSize) PURE; + STDMETHOD(GetLatencyTime) (THIS_ REFERENCE_TIME* prtTime) PURE; + STDMETHOD(GetQueueTime) (THIS_ REFERENCE_TIME* prtTime) PURE; + STDMETHOD(AdjustTime) (THIS_ REFERENCE_TIME rtAmount) PURE; + STDMETHOD(CloseDown) (THIS) PURE; + STDMETHOD(GetResolvedTime) (THIS_ REFERENCE_TIME rtTime, + REFERENCE_TIME* prtResolved, + DWORD dwTimeResolveFlags) PURE; + STDMETHOD(MIDIToMusic) (THIS_ BYTE bMIDIValue, + DMUS_CHORD_KEY* pChord, + BYTE bPlayMode, + BYTE bChordLevel, + WORD *pwMusicValue) PURE; + STDMETHOD(MusicToMIDI) (THIS_ WORD wMusicValue, + DMUS_CHORD_KEY* pChord, + BYTE bPlayMode, + BYTE bChordLevel, + BYTE *pbMIDIValue) PURE; + STDMETHOD(TimeToRhythm) (THIS_ MUSIC_TIME mtTime, + DMUS_TIMESIGNATURE *pTimeSig, + WORD *pwMeasure, + BYTE *pbBeat, + BYTE *pbGrid, + short *pnOffset) PURE; + STDMETHOD(RhythmToTime) (THIS_ WORD wMeasure, + BYTE bBeat, + BYTE bGrid, + short nOffset, + DMUS_TIMESIGNATURE *pTimeSig, + MUSIC_TIME *pmtTime) PURE; + /* IDirectMusicPerformance8 */ + STDMETHOD(InitAudio) (THIS_ IDirectMusic** ppDirectMusic, /* Optional DMusic pointer. */ + IDirectSound** ppDirectSound, /* Optional DSound pointer. */ + HWND hWnd, /* HWND for DSound. */ + DWORD dwDefaultPathType, /* Requested default audio path type, also optional. */ + DWORD dwPChannelCount, /* Number of PChannels, if default audio path to be created. */ + DWORD dwFlags, /* DMUS_AUDIOF flags, if no pParams structure. */ + DMUS_AUDIOPARAMS *pParams) PURE; /* Optional initialization structure, defining required voices, buffers, etc. */ + STDMETHOD(PlaySegmentEx) (THIS_ IUnknown* pSource, /* Segment to play. Alternately, could be an IDirectMusicSong (not supported in DX8.) */ + WCHAR *pwzSegmentName, /* If song, which segment in the song (not supported in DX8.) */ + IUnknown* pTransition, /* Optional template segment to compose transition with. */ + DWORD dwFlags, /* DMUS_SEGF_ flags. */ + __int64 i64StartTime, /* Time to start playback. */ + IDirectMusicSegmentState** ppSegmentState, /* Returned Segment State. */ + IUnknown *pFrom, /* Optional segmentstate or audiopath to replace. */ + IUnknown *pAudioPath) PURE; /* Optional audioPath to play on. */ + STDMETHOD(StopEx) (THIS_ IUnknown *pObjectToStop, /* Segstate, AudioPath, Segment, or Song. */ + __int64 i64StopTime, + DWORD dwFlags) PURE; + STDMETHOD(ClonePMsg) (THIS_ DMUS_PMSG* pSourcePMSG, + DMUS_PMSG** ppCopyPMSG) PURE; + STDMETHOD(CreateAudioPath) (THIS_ IUnknown *pSourceConfig, /* Source configuration, from AudioPathConfig file. */ + BOOL fActivate, /* TRUE to activate on creation. */ + IDirectMusicAudioPath **ppNewPath) PURE; /* Returns created audiopath. */ + STDMETHOD(CreateStandardAudioPath)(THIS_ DWORD dwType, /* Type of path to create. */ + DWORD dwPChannelCount, /* How many PChannels to allocate for it. */ + BOOL fActivate, /* TRUE to activate on creation. */ + IDirectMusicAudioPath **ppNewPath) PURE; /* Returns created audiopath. */ + STDMETHOD(SetDefaultAudioPath) (THIS_ IDirectMusicAudioPath *pAudioPath) PURE; + STDMETHOD(GetDefaultAudioPath) (THIS_ IDirectMusicAudioPath **ppAudioPath) PURE; + STDMETHOD(GetParamEx) (THIS_ REFGUID rguidType, /* GetParam command ID. */ + DWORD dwTrackID, /* Virtual track ID of caller. */ + DWORD dwGroupBits, /* Group bits of caller. */ + DWORD dwIndex, /* Index to Nth parameter. */ + MUSIC_TIME mtTime, /* Time of requested parameter. */ + MUSIC_TIME* pmtNext, /* Returned delta to next parameter. */ + void* pParam) PURE; /* Data structure to fill with parameter. */ +}; + + + +/*//////////////////////////////////////////////////////////////////// +// IDirectMusicGraph */ +#undef INTERFACE +#define INTERFACE IDirectMusicGraph +DECLARE_INTERFACE_(IDirectMusicGraph, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicGraph */ + STDMETHOD(StampPMsg) (THIS_ DMUS_PMSG* pPMSG) PURE; + STDMETHOD(InsertTool) (THIS_ IDirectMusicTool* pTool, + DWORD* pdwPChannels, + DWORD cPChannels, + LONG lIndex) PURE; + STDMETHOD(GetTool) (THIS_ DWORD dwIndex, + IDirectMusicTool** ppTool) PURE; + STDMETHOD(RemoveTool) (THIS_ IDirectMusicTool* pTool) PURE; +}; + +typedef IDirectMusicGraph IDirectMusicGraph8; + + +/*///////////////////////////////////////////////////////////////////// +// IDirectMusicStyle */ +#undef INTERFACE +#define INTERFACE IDirectMusicStyle +DECLARE_INTERFACE_(IDirectMusicStyle, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicStyle */ + STDMETHOD(GetBand) (THIS_ WCHAR* pwszName, + IDirectMusicBand** ppBand) PURE; + STDMETHOD(EnumBand) (THIS_ DWORD dwIndex, + WCHAR *pwszName) PURE; + STDMETHOD(GetDefaultBand) (THIS_ IDirectMusicBand** ppBand) PURE; + STDMETHOD(EnumMotif) (THIS_ DWORD dwIndex, + WCHAR* pwszName) PURE; + STDMETHOD(GetMotif) (THIS_ WCHAR* pwszName, + IDirectMusicSegment** ppSegment) PURE; + STDMETHOD(GetDefaultChordMap) (THIS_ IDirectMusicChordMap** ppChordMap) PURE; + STDMETHOD(EnumChordMap) (THIS_ DWORD dwIndex, + WCHAR *pwszName) PURE; + STDMETHOD(GetChordMap) (THIS_ WCHAR* pwszName, + IDirectMusicChordMap** ppChordMap) PURE; + STDMETHOD(GetTimeSignature) (THIS_ DMUS_TIMESIGNATURE* pTimeSig) PURE; + STDMETHOD(GetEmbellishmentLength) (THIS_ DWORD dwType, + DWORD dwLevel, + DWORD* pdwMin, + DWORD* pdwMax) PURE; + STDMETHOD(GetTempo) (THIS_ double* pTempo) PURE; +}; + +/*///////////////////////////////////////////////////////////////////// +// IDirectMusicStyle8 */ +#undef INTERFACE +#define INTERFACE IDirectMusicStyle8 +DECLARE_INTERFACE_(IDirectMusicStyle8, IDirectMusicStyle) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicStyle */ + STDMETHOD(GetBand) (THIS_ WCHAR* pwszName, + IDirectMusicBand** ppBand) PURE; + STDMETHOD(EnumBand) (THIS_ DWORD dwIndex, + WCHAR *pwszName) PURE; + STDMETHOD(GetDefaultBand) (THIS_ IDirectMusicBand** ppBand) PURE; + STDMETHOD(EnumMotif) (THIS_ DWORD dwIndex, + WCHAR* pwszName) PURE; + STDMETHOD(GetMotif) (THIS_ WCHAR* pwszName, + IDirectMusicSegment** ppSegment) PURE; + STDMETHOD(GetDefaultChordMap) (THIS_ IDirectMusicChordMap** ppChordMap) PURE; + STDMETHOD(EnumChordMap) (THIS_ DWORD dwIndex, + WCHAR *pwszName) PURE; + STDMETHOD(GetChordMap) (THIS_ WCHAR* pwszName, + IDirectMusicChordMap** ppChordMap) PURE; + STDMETHOD(GetTimeSignature) (THIS_ DMUS_TIMESIGNATURE* pTimeSig) PURE; + STDMETHOD(GetEmbellishmentLength) (THIS_ DWORD dwType, + DWORD dwLevel, + DWORD* pdwMin, + DWORD* pdwMax) PURE; + STDMETHOD(GetTempo) (THIS_ double* pTempo) PURE; + + /* IDirectMusicStyle8 */ + STDMETHOD(EnumPattern) (THIS_ DWORD dwIndex, + DWORD dwPatternType, + WCHAR* pwszName) PURE; +}; + +/*///////////////////////////////////////////////////////////////////// +// IDirectMusicChordMap */ +#undef INTERFACE +#define INTERFACE IDirectMusicChordMap +DECLARE_INTERFACE_(IDirectMusicChordMap, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicChordMap */ + STDMETHOD(GetScale) (THIS_ DWORD* pdwScale) PURE; +}; + +typedef IDirectMusicChordMap IDirectMusicChordMap8; + +/*///////////////////////////////////////////////////////////////////// +// IDirectMusicComposer */ +#undef INTERFACE +#define INTERFACE IDirectMusicComposer +DECLARE_INTERFACE_(IDirectMusicComposer, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicComposer */ + STDMETHOD(ComposeSegmentFromTemplate) (THIS_ IDirectMusicStyle* pStyle, + IDirectMusicSegment* pTemplate, + WORD wActivity, + IDirectMusicChordMap* pChordMap, + IDirectMusicSegment** ppSegment) PURE; + STDMETHOD(ComposeSegmentFromShape) (THIS_ IDirectMusicStyle* pStyle, + WORD wNumMeasures, + WORD wShape, + WORD wActivity, + BOOL fIntro, + BOOL fEnd, + IDirectMusicChordMap* pChordMap, + IDirectMusicSegment** ppSegment ) PURE; + STDMETHOD(ComposeTransition) (THIS_ IDirectMusicSegment* pFromSeg, + IDirectMusicSegment* pToSeg, + MUSIC_TIME mtTime, + WORD wCommand, + DWORD dwFlags, + IDirectMusicChordMap* pChordMap, + IDirectMusicSegment** ppTransSeg) PURE; + STDMETHOD(AutoTransition) (THIS_ IDirectMusicPerformance* pPerformance, + IDirectMusicSegment* pToSeg, + WORD wCommand, + DWORD dwFlags, + IDirectMusicChordMap* pChordMap, + IDirectMusicSegment** ppTransSeg, + IDirectMusicSegmentState** ppToSegState, + IDirectMusicSegmentState** ppTransSegState) PURE; + STDMETHOD(ComposeTemplateFromShape) (THIS_ WORD wNumMeasures, + WORD wShape, + BOOL fIntro, + BOOL fEnd, + WORD wEndLength, + IDirectMusicSegment** ppTemplate) PURE; + STDMETHOD(ChangeChordMap) (THIS_ IDirectMusicSegment* pSegment, + BOOL fTrackScale, + IDirectMusicChordMap* pChordMap) PURE; +}; + +typedef IDirectMusicComposer IDirectMusicComposer8; + +/*///////////////////////////////////////////////////////////////////// +// IDirectMusicPatternTrack */ + +#undef INTERFACE +#define INTERFACE IDirectMusicPatternTrack +DECLARE_INTERFACE_(IDirectMusicPatternTrack, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicPatternTrack */ + STDMETHOD(CreateSegment) (THIS_ IDirectMusicStyle* pStyle, + IDirectMusicSegment** ppSegment) PURE; + STDMETHOD(SetVariation) (THIS_ IDirectMusicSegmentState* pSegState, + DWORD dwVariationFlags, + DWORD dwPart) PURE; + STDMETHOD(SetPatternByName) (THIS_ IDirectMusicSegmentState* pSegState, + WCHAR* wszName, + IDirectMusicStyle* pStyle, + DWORD dwPatternType, + DWORD* pdwLength) PURE; +}; + +typedef IDirectMusicPatternTrack IDirectMusicPatternTrack8; + +/*///////////////////////////////////////////////////////////////////// +// IDirectMusicScript */ + +#undef INTERFACE +#define INTERFACE IDirectMusicScript +DECLARE_INTERFACE_(IDirectMusicScript, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicScript */ + STDMETHOD(Init) (THIS_ IDirectMusicPerformance *pPerformance, + DMUS_SCRIPT_ERRORINFO *pErrorInfo) PURE; + STDMETHOD(CallRoutine) (THIS_ WCHAR *pwszRoutineName, + DMUS_SCRIPT_ERRORINFO *pErrorInfo) PURE; + STDMETHOD(SetVariableVariant) (THIS_ WCHAR *pwszVariableName, + VARIANT varValue, + BOOL fSetRef, + DMUS_SCRIPT_ERRORINFO *pErrorInfo) PURE; + STDMETHOD(GetVariableVariant) (THIS_ WCHAR *pwszVariableName, + VARIANT *pvarValue, + DMUS_SCRIPT_ERRORINFO *pErrorInfo) PURE; + STDMETHOD(SetVariableNumber) (THIS_ WCHAR *pwszVariableName, + LONG lValue, + DMUS_SCRIPT_ERRORINFO *pErrorInfo) PURE; + STDMETHOD(GetVariableNumber) (THIS_ WCHAR *pwszVariableName, + LONG *plValue, + DMUS_SCRIPT_ERRORINFO *pErrorInfo) PURE; + STDMETHOD(SetVariableObject) (THIS_ WCHAR *pwszVariableName, + IUnknown *punkValue, + DMUS_SCRIPT_ERRORINFO *pErrorInfo) PURE; + STDMETHOD(GetVariableObject) (THIS_ WCHAR *pwszVariableName, + REFIID riid, + LPVOID FAR *ppv, + DMUS_SCRIPT_ERRORINFO *pErrorInfo) PURE; + STDMETHOD(EnumRoutine) (THIS_ DWORD dwIndex, + WCHAR *pwszName) PURE; + STDMETHOD(EnumVariable) (THIS_ DWORD dwIndex, + WCHAR *pwszName) PURE; +}; + +typedef IDirectMusicScript IDirectMusicScript8; + +/*///////////////////////////////////////////////////////////////////// +// IDirectMusicContainer */ + +#undef INTERFACE +#define INTERFACE IDirectMusicContainer +DECLARE_INTERFACE_(IDirectMusicContainer, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicContainer */ + STDMETHOD(EnumObject) (THIS_ REFGUID rguidClass, + DWORD dwIndex, + LPDMUS_OBJECTDESC pDesc, + WCHAR *pwszAlias) PURE; +}; + +typedef IDirectMusicContainer IDirectMusicContainer8; + +/*///////////////////////////////////////////////////////////////////// +// IDirectMusicSong */ +/* Note: Songs are not supported in DX8. */ + +#undef INTERFACE +#define INTERFACE IDirectMusicSong +DECLARE_INTERFACE_(IDirectMusicSong, IUnknown) +{ + /* IUnknown */ + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID FAR *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + /* IDirectMusicSong */ + STDMETHOD(Compose) (THIS) PURE; + STDMETHOD(GetParam) (THIS_ REFGUID rguidType, + DWORD dwGroupBits, + DWORD dwIndex, + MUSIC_TIME mtTime, + MUSIC_TIME* pmtNext, + void* pParam) PURE; + STDMETHOD(GetSegment) (THIS_ WCHAR *pwzName, /* Retrieve a specific segment by name. */ + IDirectMusicSegment **ppSegment) PURE; /* Returned segment. */ + STDMETHOD(GetAudioPathConfig) (THIS_ IUnknown ** ppAudioPathConfig) PURE; /* Retrieve embedded audiopath configuration. */ + STDMETHOD(Download) (THIS_ IUnknown *pAudioPath) PURE; /* Download entire song to ports on performance or audiopath. */ + STDMETHOD(Unload) (THIS_ IUnknown *pAudioPath) PURE; /* Unload entire song from port on performance or audiopath. */ + STDMETHOD(EnumSegment) (THIS_ DWORD dwIndex, /* Nth segment to retrieve. */ + IDirectMusicSegment **ppSegment) PURE; /* Pointer to segment. */ +}; + +typedef IDirectMusicSong IDirectMusicSong8; + +/* CLSID's */ +DEFINE_GUID(CLSID_DirectMusicPerformance,0xd2ac2881, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicSegment,0xd2ac2882, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicSegmentState,0xd2ac2883, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicGraph,0xd2ac2884, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicStyle,0xd2ac288a, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicChordMap,0xd2ac288f, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicComposer,0xd2ac2890, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicLoader,0xd2ac2892, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicBand,0x79ba9e00, 0xb6ee, 0x11d1, 0x86, 0xbe, 0x0, 0xc0, 0x4f, 0xbf, 0x8f, 0xef); + +/* New CLSID's for DX8 */ +DEFINE_GUID(CLSID_DirectMusicPatternTrack,0xd2ac2897, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(CLSID_DirectMusicScript,0x810b5013, 0xe88d, 0x11d2, 0x8b, 0xc1, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xb6); /* {810B5013-E88D-11d2-8BC1-00600893B1B6} */ +DEFINE_GUID(CLSID_DirectMusicContainer,0x9301e380, 0x1f22, 0x11d3, 0x82, 0x26, 0xd2, 0xfa, 0x76, 0x25, 0x5d, 0x47); +DEFINE_GUID(CLSID_DirectSoundWave,0x8a667154, 0xf9cb, 0x11d2, 0xad, 0x8a, 0x0, 0x60, 0xb0, 0x57, 0x5a, 0xbc); +/* Note: Songs are not supported in DX8. */ +DEFINE_GUID(CLSID_DirectMusicSong, 0xaed5f0a5, 0xd972, 0x483d, 0xa3, 0x84, 0x64, 0x9d, 0xfe, 0xb9, 0xc1, 0x81); +DEFINE_GUID(CLSID_DirectMusicAudioPathConfig,0xee0b9ca0, 0xa81e, 0x11d3, 0x9b, 0xd1, 0x0, 0x80, 0xc7, 0x15, 0xa, 0x74); + +/* Special GUID for all object types. This is used by the loader. */ +DEFINE_GUID(GUID_DirectMusicAllTypes,0xd2ac2893, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* Notification guids */ +DEFINE_GUID(GUID_NOTIFICATION_SEGMENT,0xd2ac2899, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(GUID_NOTIFICATION_PERFORMANCE,0x81f75bc5, 0x4e5d, 0x11d2, 0xbc, 0xc7, 0x0, 0xa0, 0xc9, 0x22, 0xe6, 0xeb); +DEFINE_GUID(GUID_NOTIFICATION_MEASUREANDBEAT,0xd2ac289a, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(GUID_NOTIFICATION_CHORD,0xd2ac289b, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(GUID_NOTIFICATION_COMMAND,0xd2ac289c, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(GUID_NOTIFICATION_RECOMPOSE, 0xd348372b, 0x945b, 0x45ae, 0xa5, 0x22, 0x45, 0xf, 0x12, 0x5b, 0x84, 0xa5); + +/* Track param type guids */ +/* Use to get/set a DMUS_COMMAND_PARAM param in the Command track */ +DEFINE_GUID(GUID_CommandParam,0xd2ac289d, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* Use to get a DMUS_COMMAND_PARAM_2 param in the Command track */ +DEFINE_GUID(GUID_CommandParam2, 0x28f97ef7, 0x9538, 0x11d2, 0x97, 0xa9, 0x0, 0xc0, 0x4f, 0xa3, 0x6e, 0x58); + +/* Use to get/set a DMUS_COMMAND_PARAM_2 param to be used as the command following all commands in +the Command track (this information can't be saved) */ +DEFINE_GUID(GUID_CommandParamNext, 0x472afe7a, 0x281b, 0x11d3, 0x81, 0x7d, 0x0, 0xc0, 0x4f, 0xa3, 0x6e, 0x58); + +/* Use to get/set a DMUS_CHORD_PARAM param in the Chord track */ +DEFINE_GUID(GUID_ChordParam,0xd2ac289e, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* Use to get a DMUS_RHYTHM_PARAM param in the Chord track */ +DEFINE_GUID(GUID_RhythmParam,0xd2ac289f, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* Use to get/set an IDirectMusicStyle param in the Style track */ +DEFINE_GUID(GUID_IDirectMusicStyle,0xd2ac28a1, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* Use to get a DMUS_TIMESIGNATURE param in the Style and TimeSig tracks */ +DEFINE_GUID(GUID_TimeSignature,0xd2ac28a4, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* Use to get/set a DMUS_TEMPO_PARAM param in the Tempo track */ +DEFINE_GUID(GUID_TempoParam,0xd2ac28a5, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* Use to get the next valid point in a segment at which it may start */ +DEFINE_GUID(GUID_Valid_Start_Time,0x7f6b1760, 0x1fdb, 0x11d3, 0x82, 0x26, 0x44, 0x45, 0x53, 0x54, 0x0, 0x0); + +/* Use to get the next point in the currently playing primary segment at which a new segment may start */ +DEFINE_GUID(GUID_Play_Marker,0xd8761a41, 0x801a, 0x11d3, 0x9b, 0xd1, 0xda, 0xf7, 0xe1, 0xc3, 0xd8, 0x34); + +/* Use to get (GetParam) or add (SetParam) bands in the Band track */ +DEFINE_GUID(GUID_BandParam,0x2bb1938, 0xcb8b, 0x11d2, 0x8b, 0xb9, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xb6); +typedef struct _DMUS_BAND_PARAM +{ + MUSIC_TIME mtTimePhysical; /* Note: If this is a clock-time track, then this field is interpreted in the track's internal time format, which is the number of milliseconds after the beginning of playback. */ + IDirectMusicBand *pBand; +} DMUS_BAND_PARAM; + +/* Obsolete -- doesn't distinguish physical and logical time. Use GUID_BandParam instead. */ +DEFINE_GUID(GUID_IDirectMusicBand,0xd2ac28ac, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* Use to get/set an IDirectMusicChordMap param in the ChordMap track */ +DEFINE_GUID(GUID_IDirectMusicChordMap,0xd2ac28ad, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* Use to get/set a DMUS_MUTE_PARAM param in the Mute track */ +DEFINE_GUID(GUID_MuteParam,0xd2ac28af, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* These guids are used in IDirectMusicSegment::SetParam to tell the band track to perform various actions. + Some of these guids (where noted) also apply to wave tracks. + */ +/* Download bands/waves for the IDirectMusicSegment */ +DEFINE_GUID(GUID_Download,0xd2ac28a7, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* Unload bands/waves for the IDirectMusicSegment */ +DEFINE_GUID(GUID_Unload,0xd2ac28a8, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* Connect segment's bands to an IDirectMusicCollection */ +DEFINE_GUID(GUID_ConnectToDLSCollection, 0x1db1ae6b, 0xe92e, 0x11d1, 0xa8, 0xc5, 0x0, 0xc0, 0x4f, 0xa3, 0x72, 0x6e); + +/* Enable/disable autodownloading of bands/waves */ +DEFINE_GUID(GUID_Enable_Auto_Download,0xd2ac28a9, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(GUID_Disable_Auto_Download,0xd2ac28aa, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* Clear all bands */ +DEFINE_GUID(GUID_Clear_All_Bands,0xd2ac28ab, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* Set segment to manage all program changes, bank selects, etc. for simple playback of a standard MIDI file */ +DEFINE_GUID(GUID_StandardMIDIFile, 0x6621075, 0xe92e, 0x11d1, 0xa8, 0xc5, 0x0, 0xc0, 0x4f, 0xa3, 0x72, 0x6e); +/* For compatibility with beta releases... */ +#define GUID_IgnoreBankSelectForGM GUID_StandardMIDIFile + +/* Disable/enable param guids. Use these in SetParam calls to disable or enable sending + * specific PMsg types. + */ +DEFINE_GUID(GUID_DisableTimeSig, 0x45fc707b, 0x1db4, 0x11d2, 0xbc, 0xac, 0x0, 0xa0, 0xc9, 0x22, 0xe6, 0xeb); +DEFINE_GUID(GUID_EnableTimeSig, 0x45fc707c, 0x1db4, 0x11d2, 0xbc, 0xac, 0x0, 0xa0, 0xc9, 0x22, 0xe6, 0xeb); +DEFINE_GUID(GUID_DisableTempo, 0x45fc707d, 0x1db4, 0x11d2, 0xbc, 0xac, 0x0, 0xa0, 0xc9, 0x22, 0xe6, 0xeb); +DEFINE_GUID(GUID_EnableTempo, 0x45fc707e, 0x1db4, 0x11d2, 0xbc, 0xac, 0x0, 0xa0, 0xc9, 0x22, 0xe6, 0xeb); + +/* Used in SetParam calls for pattern-based tracks. A nonzero value seeds the random number +generator for variation selection; a value of zero reverts to the default behavior of +getting the seed from the system clock. +*/ +DEFINE_GUID(GUID_SeedVariations, 0x65b76fa5, 0xff37, 0x11d2, 0x81, 0x4e, 0x0, 0xc0, 0x4f, 0xa3, 0x6e, 0x58); + +/* Used to get/set melody fragments (pParam points to a DMUS_MELODY_FRAGMENT) */ +/* Note: Melody formulation is not supported in DX8. */ +DEFINE_GUID(GUID_MelodyFragment, 0xb291c7f2, 0xb616, 0x11d2, 0x97, 0xfa, 0x0, 0xc0, 0x4f, 0xa3, 0x6e, 0x58); + +/* Used to clear all melody fragments */ +/* Note: Melody formulation is not supported in DX8. */ +DEFINE_GUID(GUID_Clear_All_MelodyFragments, 0x8509fee6, 0xb617, 0x11d2, 0x97, 0xfa, 0x0, 0xc0, 0x4f, 0xa3, 0x6e, 0x58); + +/* Used to get the variations currently in effect across PChannels */ +DEFINE_GUID(GUID_Variations, 0x11f72cce, 0x26e6, 0x4ecd, 0xaf, 0x2e, 0xd6, 0x68, 0xe6, 0x67, 0x7, 0xd8); +typedef struct _DMUS_VARIATIONS_PARAM +{ + DWORD dwPChannelsUsed; /* number of PChannels in use */ + DWORD* padwPChannels; /* array of PChannels in use */ + DWORD* padwVariations; /* array of variations in effect for each PChannel */ +} DMUS_VARIATIONS_PARAM; + +/* Download bands/waves for the IDirectMusicSegment, passed an IDirectMusicAudioPath instead of an IDirectMusicPerformance */ +DEFINE_GUID(GUID_DownloadToAudioPath,0x9f2c0341, 0xc5c4, 0x11d3, 0x9b, 0xd1, 0x44, 0x45, 0x53, 0x54, 0x0, 0x0); + +/* Unload bands/waves for the IDirectMusicSegment, passed an IDirectMusicAudioPath instead of an IDirectMusicPerformance */ +DEFINE_GUID(GUID_UnloadFromAudioPath,0x9f2c0342, 0xc5c4, 0x11d3, 0x9b, 0xd1, 0x44, 0x45, 0x53, 0x54, 0x0, 0x0); + + +/* Global data guids */ +DEFINE_GUID(GUID_PerfMasterTempo,0xd2ac28b0, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(GUID_PerfMasterVolume,0xd2ac28b1, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(GUID_PerfMasterGrooveLevel,0xd2ac28b2, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(GUID_PerfAutoDownload, 0xfb09565b, 0x3631, 0x11d2, 0xbc, 0xb8, 0x0, 0xa0, 0xc9, 0x22, 0xe6, 0xeb); + +/* GUID for default GM/GS dls collection. */ +DEFINE_GUID(GUID_DefaultGMCollection, 0xf17e8673, 0xc3b4, 0x11d1, 0x87, 0xb, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* GUID to define default synth, placed in AudioPath configuration file. */ +DEFINE_GUID(GUID_Synth_Default,0x26bb9432, 0x45fe, 0x48d3, 0xa3, 0x75, 0x24, 0x72, 0xc5, 0xe3, 0xe7, 0x86); + +/* GUIDs to define default buffer configurations to place in AudioPath configuration file. */ +DEFINE_GUID(GUID_Buffer_Reverb,0x186cc541, 0xdb29, 0x11d3, 0x9b, 0xd1, 0x0, 0x80, 0xc7, 0x15, 0xa, 0x74); +DEFINE_GUID(GUID_Buffer_EnvReverb,0x186cc542, 0xdb29, 0x11d3, 0x9b, 0xd1, 0x0, 0x80, 0xc7, 0x15, 0xa, 0x74); +DEFINE_GUID(GUID_Buffer_Stereo,0x186cc545, 0xdb29, 0x11d3, 0x9b, 0xd1, 0x0, 0x80, 0xc7, 0x15, 0xa, 0x74); +DEFINE_GUID(GUID_Buffer_3D_Dry,0x186cc546, 0xdb29, 0x11d3, 0x9b, 0xd1, 0x0, 0x80, 0xc7, 0x15, 0xa, 0x74); +DEFINE_GUID(GUID_Buffer_Mono,0x186cc547, 0xdb29, 0x11d3, 0x9b, 0xd1, 0x0, 0x80, 0xc7, 0x15, 0xa, 0x74); + +/* IID's */ +DEFINE_GUID(IID_IDirectMusicLoader, 0x2ffaaca2, 0x5dca, 0x11d2, 0xaf, 0xa6, 0x0, 0xaa, 0x0, 0x24, 0xd8, 0xb6); +DEFINE_GUID(IID_IDirectMusicGetLoader,0x68a04844, 0xd13d, 0x11d1, 0xaf, 0xa6, 0x0, 0xaa, 0x0, 0x24, 0xd8, 0xb6); +DEFINE_GUID(IID_IDirectMusicObject,0xd2ac28b5, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(IID_IDirectMusicSegment, 0xf96029a2, 0x4282, 0x11d2, 0x87, 0x17, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(IID_IDirectMusicSegmentState, 0xa3afdcc7, 0xd3ee, 0x11d1, 0xbc, 0x8d, 0x0, 0xa0, 0xc9, 0x22, 0xe6, 0xeb); +DEFINE_GUID(IID_IDirectMusicPerformance,0x7d43d03, 0x6523, 0x11d2, 0x87, 0x1d, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(IID_IDirectMusicGraph,0x2befc277, 0x5497, 0x11d2, 0xbc, 0xcb, 0x0, 0xa0, 0xc9, 0x22, 0xe6, 0xeb); +DEFINE_GUID(IID_IDirectMusicStyle,0xd2ac28bd, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(IID_IDirectMusicChordMap,0xd2ac28be, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(IID_IDirectMusicComposer,0xd2ac28bf, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); +DEFINE_GUID(IID_IDirectMusicBand,0xd2ac28c0, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* Alternate interface IDs, available in DX7 release and after. */ +DEFINE_GUID(IID_IDirectMusicPerformance2,0x6fc2cae0, 0xbc78, 0x11d2, 0xaf, 0xa6, 0x0, 0xaa, 0x0, 0x24, 0xd8, 0xb6); +DEFINE_GUID(IID_IDirectMusicSegment2, 0xd38894d1, 0xc052, 0x11d2, 0x87, 0x2f, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd); + +/* Interface IDs for DX8 */ +/* changed interfaces (GUID only) */ +DEFINE_GUID(IID_IDirectMusicLoader8, 0x19e7c08c, 0xa44, 0x4e6a, 0xa1, 0x16, 0x59, 0x5a, 0x7c, 0xd5, 0xde, 0x8c); +DEFINE_GUID(IID_IDirectMusicPerformance8, 0x679c4137, 0xc62e, 0x4147, 0xb2, 0xb4, 0x9d, 0x56, 0x9a, 0xcb, 0x25, 0x4c); +DEFINE_GUID(IID_IDirectMusicSegment8,0xc6784488, 0x41a3, 0x418f, 0xaa, 0x15, 0xb3, 0x50, 0x93, 0xba, 0x42, 0xd4); +DEFINE_GUID(IID_IDirectMusicSegmentState8, 0xa50e4730, 0xae4, 0x48a7, 0x98, 0x39, 0xbc, 0x4, 0xbf, 0xe0, 0x77, 0x72); +DEFINE_GUID(IID_IDirectMusicStyle8, 0xfd24ad8a, 0xa260, 0x453d, 0xbf, 0x50, 0x6f, 0x93, 0x84, 0xf7, 0x9, 0x85); +/* new interfaces (GUID + alias) */ +DEFINE_GUID(IID_IDirectMusicPatternTrack, 0x51c22e10, 0xb49f, 0x46fc, 0xbe, 0xc2, 0xe6, 0x28, 0x8f, 0xb9, 0xed, 0xe6); +#define IID_IDirectMusicPatternTrack8 IID_IDirectMusicPatternTrack +DEFINE_GUID(IID_IDirectMusicScript, 0x2252373a, 0x5814, 0x489b, 0x82, 0x9, 0x31, 0xfe, 0xde, 0xba, 0xf1, 0x37); /* {2252373A-5814-489b-8209-31FEDEBAF137} */ +#define IID_IDirectMusicScript8 IID_IDirectMusicScript +DEFINE_GUID(IID_IDirectMusicContainer, 0x9301e386, 0x1f22, 0x11d3, 0x82, 0x26, 0xd2, 0xfa, 0x76, 0x25, 0x5d, 0x47); +#define IID_IDirectMusicContainer8 IID_IDirectMusicContainer +/* Note: Songs are not supported in DX8. */ +DEFINE_GUID(IID_IDirectMusicSong, 0xa862b2ec, 0x3676, 0x4982, 0x85, 0xa, 0x78, 0x42, 0x77, 0x5e, 0x1d, 0x86); +#define IID_IDirectMusicSong8 IID_IDirectMusicSong +DEFINE_GUID(IID_IDirectMusicAudioPath,0xc87631f5, 0x23be, 0x4986, 0x88, 0x36, 0x5, 0x83, 0x2f, 0xcc, 0x48, 0xf9); +#define IID_IDirectMusicAudioPath8 IID_IDirectMusicAudioPath +/* unchanged interfaces (alias only) */ +#define IID_IDirectMusicGetLoader8 IID_IDirectMusicGetLoader +#define IID_IDirectMusicChordMap8 IID_IDirectMusicChordMap +#define IID_IDirectMusicGraph8 IID_IDirectMusicGraph +#define IID_IDirectMusicBand8 IID_IDirectMusicBand +#define IID_IDirectMusicObject8 IID_IDirectMusicObject +#define IID_IDirectMusicComposer8 IID_IDirectMusicComposer + + +#ifdef __cplusplus +}; /* extern "C" */ +#endif + +#include <poppack.h> + +#endif /* #ifndef _DMUSICI_ */ diff --git a/Src/Plugins/Input/in_midi/fakedsound.cpp b/Src/Plugins/Input/in_midi/fakedsound.cpp new file mode 100644 index 00000000..4332f757 --- /dev/null +++ b/Src/Plugins/Input/in_midi/fakedsound.cpp @@ -0,0 +1,244 @@ +#include "main.h" +#include "fakedsound.h" + +//DirectMusic output capture hack. + +class FakeDirectSoundBuffer : public IDirectSoundBuffer +{ +private: + ULONG ref; + CPipe* out; + UINT freq; + BYTE * buf; + UINT buf_size; + + bool playing; + DWORD pos_play; + + DWORD samples_played; + DWORD start; + + void do_update(); + +public: + ~FakeDirectSoundBuffer() + { + if (buf) free(buf); + }; + + HRESULT _stdcall QueryInterface(REFIID iid, void** i) + { + if (IsEqualIID(iid,IID_IUnknown) || IsEqualIID(iid,IID_IDirectSoundBuffer)) + { + ref++; + *i = this; + return S_OK; + } + else return E_NOINTERFACE; + } + + ULONG _stdcall AddRef() {return ++ref;}; + ULONG _stdcall Release() + { + UINT r=--ref; + if (!r) + { + delete this; + } + return r; + } + HRESULT _stdcall GetCaps(LPDSBCAPS _caps) + { + DSBCAPS caps= + { + sizeof(DSBCAPS), + DSBCAPS_GLOBALFOCUS|DSBCAPS_GETCURRENTPOSITION2|DSBCAPS_LOCSOFTWARE, + buf_size, + 0,0 //CPU crap + }; + *_caps = caps; + return S_OK; + } + HRESULT _stdcall Initialize(LPDIRECTSOUND, LPCDSBUFFERDESC) {return DSERR_ALREADYINITIALIZED;} + HRESULT _stdcall SetFormat(LPCWAVEFORMATEX) {return DSERR_INVALIDCALL;} + HRESULT _stdcall GetFormat(LPWAVEFORMATEX wfx,DWORD,LPDWORD w) + { + wfx->wFormatTag=WAVE_FORMAT_PCM; + wfx->nChannels=2; + wfx->nSamplesPerSec=freq; + wfx->nAvgBytesPerSec=4*freq; + wfx->nBlockAlign=4; + wfx->wBitsPerSample=16; + wfx->cbSize=0; + if (w) *w=sizeof(WAVEFORMATEX); + return S_OK; + } + + HRESULT _stdcall GetVolume(long* v) {return S_OK;} + HRESULT _stdcall SetVolume(long v) {return S_OK;} + HRESULT _stdcall GetPan(long *p) {return S_OK;} + HRESULT _stdcall SetPan(long p) {return S_OK;} + HRESULT _stdcall GetFrequency(DWORD* f) {*f=freq;return S_OK;} + HRESULT _stdcall SetFrequency(DWORD f) {return S_OK;} + HRESULT _stdcall GetStatus(DWORD* s) + { + *s = DSBSTATUS_PLAYING|DSBSTATUS_LOOPING; + return S_OK; + } + HRESULT _stdcall SetCurrentPosition(DWORD) {return S_OK;} + HRESULT _stdcall Restore() {return S_OK;} + + HRESULT _stdcall Lock(DWORD wr_cur, DWORD wr_b, void** p1, DWORD* s1, void** p2, DWORD* s2, DWORD flagz) + { + if (wr_b>buf_size) + { + return DSERR_INVALIDPARAM; + } + *p1 = buf + wr_cur; + if (wr_cur + wr_b > buf_size) + { + *s1 = buf_size - wr_cur; + *p2 = buf; + *s2 = wr_cur+wr_b - buf_size; + } + else + { + *s1 = wr_b; + *p2 = 0; + *s2 = 0; + } + return S_OK; + } + + + HRESULT _stdcall GetCurrentPosition(LPDWORD p, LPDWORD w) + { + do_update(); + if (p) *p=pos_play; + if (w) *w=pos_play; + return S_OK; + } + + HRESULT _stdcall Play(DWORD, DWORD, DWORD) + { + playing=1; + pos_play=0; + samples_played=0; + start=timeGetTime(); + return S_OK; + } + + HRESULT _stdcall Stop() {do_update();playing=0;return S_OK;} + HRESULT _stdcall Unlock(LPVOID, DWORD, LPVOID, DWORD) + { + do_update(); + return S_OK; + } + + FakeDirectSoundBuffer(UINT _freq,UINT size) + + { + ref=1; + buf_size=size; + buf=(BYTE*)malloc(size); + memset(buf,0,size); + freq=_freq; + out=new CPipe(4,freq); + MIDI_core::player_setSource(out); + playing=0; + pos_play=0; + samples_played=0; + } +}; + + + + + +void FakeDirectSoundBuffer::do_update() +{ + if (playing) + { + int ds=MulDiv(timeGetTime()-start,freq,1000)-samples_played; + + if (ds>0) + { + UINT todo=ds*4; + while(pos_play+todo>buf_size) + { + out->WriteData(buf+pos_play,buf_size-pos_play); + todo-=buf_size-pos_play; + pos_play=0; + } + if (todo) + { + out->WriteData(buf+pos_play,todo); + pos_play+=todo; + //todo=0; + } + samples_played+=ds; + } + } +} + +IDirectSoundBuffer* dhb_create(DWORD s,DWORD f) +{ + return new FakeDirectSoundBuffer(f,s); +} + + +//fake IDirectSound crap. one static instance + +static DSCAPS h_caps= + { + sizeof(DSCAPS), + DSCAPS_SECONDARY16BIT|DSCAPS_SECONDARYSTEREO, + 1000, + 100000, + 1, + 1000, + 1000, + 1000,//streaming buffers + 1000, + 1000, + 1000, + 0,0,0,0,0,0,//3d crap + 1024*1024, + 1024*1024, + 1024*1024, + 0,0, //CPU speed crap + 0,0 //reserved crap + }; + +class FakeDsound : public IDirectSound +{ + ULONG ref:1; + HRESULT _stdcall QueryInterface(REFIID iid,void** i) + { + if (IsEqualIID(iid,IID_IUnknown) || IsEqualIID(iid,IID_IDirectSound)) + { + ref++; + *i = this; + return S_OK; + } + else return E_NOINTERFACE; + } + ULONG _stdcall AddRef() {return ++ref;} + ULONG _stdcall Release() {return --ref;} + HRESULT _stdcall CreateSoundBuffer(LPCDSBUFFERDESC, LPDIRECTSOUNDBUFFER *, LPUNKNOWN) {return DSERR_INVALIDCALL;} + HRESULT _stdcall GetCaps(LPDSCAPS _caps) + { + *_caps = h_caps; + return S_OK; + } + HRESULT _stdcall DuplicateSoundBuffer(LPDIRECTSOUNDBUFFER, LPDIRECTSOUNDBUFFER *) {return DSERR_INVALIDCALL;} + HRESULT _stdcall SetCooperativeLevel(HWND, DWORD) {return S_OK;} + HRESULT _stdcall Compact() {return S_OK;} + HRESULT _stdcall GetSpeakerConfig(LPDWORD moo) {*moo=0;return S_OK;} + HRESULT _stdcall SetSpeakerConfig(DWORD) {return S_OK;} + HRESULT _stdcall Initialize(LPCGUID) {return DSERR_ALREADYINITIALIZED;} +}; + +static FakeDsound HACK; + +IDirectSound * get_ds() {return &HACK;}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/fakedsound.h b/Src/Plugins/Input/in_midi/fakedsound.h new file mode 100644 index 00000000..800c0a58 --- /dev/null +++ b/Src/Plugins/Input/in_midi/fakedsound.h @@ -0,0 +1,3 @@ +IDirectSoundBuffer* dhb_create(DWORD size,DWORD freq); + +IDirectSound * get_ds(); diff --git a/Src/Plugins/Input/in_midi/genres.c b/Src/Plugins/Input/in_midi/genres.c new file mode 100644 index 00000000..c8add185 --- /dev/null +++ b/Src/Plugins/Input/in_midi/genres.c @@ -0,0 +1,103 @@ +#define STRICT +#include <windows.h> + +#include "genres.h" + +static char file_path[MAX_PATH]; + +static void file_init() +{ + char * p; + GetModuleFileName(0,file_path,MAX_PATH); + p=strrchr(file_path,'\\'); + if (p) p++; else p=file_path; + strcpy(p,"genres.txt"); +} + +static char eol[2]={13,10}; + +static char get_char(HANDLE f,BOOL * eof) +{ + DWORD br=0; + char r=0; + ReadFile(f,&r,1,&br,0); + if (!br) *eof=1; + return r; +} + +void genres_read(HWND wnd) +{ + HANDLE f; + char temp[MAX_GENRE] = {0}; + char add[MAX_GENRE] = {0}; + UINT ptr; + BOOL eof=0; + BOOL start; + char c; + + if (!file_path[0]) file_init(); + + + f=CreateFile(file_path,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,0,0); + if (f==INVALID_HANDLE_VALUE) return; + GetWindowText(wnd,add,MAX_GENRE); + while(!eof) + { + ptr=0; + start=1; + while(ptr<MAX_GENRE-1) + { + c=get_char(f,&eof); + if (eof) break; + if (c==10 || c==13) + { + if (start) continue; + else break; + } + start=0; + temp[ptr++]=c; + } + if (ptr) + { + temp[ptr]=0; + SendMessage(wnd,CB_ADDSTRING,0,(LPARAM)temp); + if (add[0]) + { + if (!_stricmp(add,temp)) add[0]=0; + } + } + } + CloseHandle(f); + if (add[0]) SendMessage(wnd,CB_ADDSTRING,0,(LPARAM)add); +} + +void genres_write(HWND wnd) +{ + char temp[MAX_GENRE] = {0}; + UINT max = 0, n = 0; + DWORD bw = 0; + HANDLE f; + { + char add[MAX_GENRE] = {0}; + GetWindowText(wnd,add,MAX_GENRE); + if (!add[0]) return; + max=SendMessage(wnd,CB_GETCOUNT,0,0); + for(n=0;n<max;n++) + { + SendMessage(wnd,CB_GETLBTEXT,n,(LPARAM)temp); + if (!_stricmp(temp,add)) return; + } + SendMessage(wnd,CB_ADDSTRING,0,(LPARAM)add); + } + if (!file_path[0]) file_init(); + f=CreateFile(file_path,GENERIC_WRITE,0,0,CREATE_ALWAYS,0,0); + if (f==INVALID_HANDLE_VALUE) return; + max=SendMessage(wnd,CB_GETCOUNT,0,0); + for(n=0;n<max;n++) + { + SendMessage(wnd,CB_GETLBTEXT,n,(LPARAM)temp); + bw = 0; WriteFile(f,temp,strlen(temp),&bw,0); + bw = 0; WriteFile(f,eol,2,&bw,0); + } + CloseHandle(f); +} diff --git a/Src/Plugins/Input/in_midi/genres.h b/Src/Plugins/Input/in_midi/genres.h new file mode 100644 index 00000000..ceffb3dc --- /dev/null +++ b/Src/Plugins/Input/in_midi/genres.h @@ -0,0 +1,3 @@ +void genres_read(HWND wnd); +void genres_write(HWND wnd); +#define MAX_GENRE 256
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/gmf.cpp b/Src/Plugins/Input/in_midi/gmf.cpp new file mode 100644 index 00000000..0065e60f --- /dev/null +++ b/Src/Plugins/Input/in_midi/gmf.cpp @@ -0,0 +1,27 @@ +#include "main.h" +#include "cvt.h" + +bool is_gmf(const BYTE* p,size_t s) +{ + return s>0x20 && *(DWORD*)p==_rv('GMF\x01'); +} + +bool load_gmf(MIDI_file * mf,const BYTE* buf,size_t siz) +{ + grow_buf wb; + wb.write_dword(_rv('MThd')); + wb.write_dword(_rv(6)); + MIDIHEADER h={0x000,0x100,0xC000}; + wb.write(&h,6); + wb.write_dword(_rv('MTrk')); + int tempo=100000*rev16(*(WORD*)(buf+4)); + wb.write_dword(rev32(siz-9+8+3));//MTrk size + BYTE tempo_event[8]={0,0xFF,0x51,0x03,(BYTE)((tempo>>16)&0xFF),(BYTE)((tempo>>8)&0xFF),(BYTE)(tempo&0xFF),0}; + wb.write(tempo_event,8); + wb.write(buf+8,siz-9); + wb.write("\xFF\x2F\x00",3); + mf->size = wb.get_size(); + mf->data = (BYTE*)wb.finish(); + + return !!mf->data; +} diff --git a/Src/Plugins/Input/in_midi/guids.cpp b/Src/Plugins/Input/in_midi/guids.cpp new file mode 100644 index 00000000..6f48b6eb --- /dev/null +++ b/Src/Plugins/Input/in_midi/guids.cpp @@ -0,0 +1,2 @@ +#define INITGUID +#include "main.h"
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/hmi.cpp b/Src/Plugins/Input/in_midi/hmi.cpp new file mode 100644 index 00000000..07b25523 --- /dev/null +++ b/Src/Plugins/Input/in_midi/hmi.cpp @@ -0,0 +1,206 @@ +#include "main.h" +#include "cvt.h" + +#define _MThd 'dhTM' +#define _MTrk 'krTM' + +#define tempo 0x188000 + +#define Q_MAX 128 + +struct HMI_cvt +{ +public: + struct + { + DWORD tm; + BYTE ch,n; + } q_rel[Q_MAX]; + + void inline q_add(BYTE ch,BYTE nt,DWORD t) + { + UINT n=0; + while(q_rel[n].tm!=-1) n++; + q_rel[n].tm=t; + q_rel[n].ch=ch; + q_rel[n].n=nt; + } + grow_buf buf; + + UINT DoTrack(const BYTE* t,UINT *_bw); + void DoQueue(DWORD ct,DWORD& tw,BYTE& _run); + bool run(MIDI_file * mf,const BYTE* _buf,DWORD sz); +}; + + +#define FixHeader(H) {(H).fmt=rev16((H).fmt);(H).trax=rev16((H).trax);(H).dtx=rev16((H).dtx);} + +void HMI_cvt::DoQueue(DWORD ct,DWORD& tw,BYTE& _run) +{ + UINT n,mt,_n; +_t: + mt=-1; + for(n=0;n<Q_MAX;n++) + { + if (q_rel[n].tm<mt) {_n=n;mt=q_rel[n].tm;} + } + if (mt>ct) return; + gb_write_delta(buf,mt-tw); + tw=mt; + BYTE _e=q_rel[_n].ch|0x90; + if (_e!=_run) buf.write_byte(_run=_e); + buf.write_byte(q_rel[_n].n); + buf.write_byte(0); + q_rel[_n].tm=-1; + goto _t; +} + +extern BYTE ff7loopstart[12]; + +UINT HMI_cvt::DoTrack(const BYTE* t,UINT *_bw) +{ + { + UINT n; + for(n=0;n<Q_MAX;n++) q_rel[n].tm=-1; + } + DWORD pt=0; + DWORD ct=0,tw=0; + BYTE run=0; + BYTE _run=0; + DWORD bw_s=buf.get_size(); + while(1) + { + { + unsigned int _d; + pt+=DecodeDelta(t+pt,&_d); + ct+=_d; + } + DoQueue(ct,tw,_run); + BYTE c=t[pt]; + if (c==0xFF) + { + DoQueue(-2,tw,_run); + if (t[pt+1]==0x2f) + { + pt+=3; + buf.write_dword(0x002FFF00); + break; + } + return -1; + + } + else if (c==0xF0) + { + gb_write_delta(buf,ct-tw); + tw=ct; + UINT _p=pt; + while(t[pt]!=0xF7) pt++; + pt++; + buf.write(t+_p,pt-_p); + } + else if (c==0xFE) + { + c=t[pt+1]; + if (c==0x10) + { + pt+=t[pt+4]+9; + } + else if (c==0x14) + { + pt+=4; + gb_write_delta(buf,ct-tw); + tw=ct; + buf.write(ff7loopstart,12); + } + else if (c==0x15) pt+=8; + else return -1; + } + else + { + gb_write_delta(buf,ct-tw); + tw=ct; + if (c&0x80) {pt++;run=c;} + else c=run; + if (c!=_run) buf.write_byte(_run=c); + buf.write_byte(t[pt++]); + BYTE c1=c&0xF0; + if (c1!=0xC0 && c1!=0xD0) buf.write_byte(t[pt++]); + if (c1==0x90) + { + BYTE b=t[pt-2]; + unsigned int _t; + pt+=DecodeDelta(t+pt,&_t); + q_add(c&0xF,b,_t+ct); + } + } + } + (*_bw)+=buf.get_size()-bw_s; + return pt; +} + +extern BYTE hmp_track0[19]; //hmp.cpp + +bool HMI_cvt::run(MIDI_file* mf,const BYTE* _buf,DWORD sz) +{ + const BYTE *ptr=_buf; + + while(*(DWORD*)ptr!='CART') + { + ptr++; + if (ptr==_buf+sz) {return 0;} + } + + buf.write(0,14); + + ptr-=8; + UINT ntrax=1; + UINT nft=*(DWORD*)(_buf+0xE4); + + buf.write(hmp_track0,sizeof(hmp_track0)); + + UINT n; + for(n=0;n<nft;n++) + { + if (ptr>_buf+sz) return 0; + UINT _b=0; + ntrax++; + buf.write_dword(_rv('MTrk')); + DWORD _s=buf.get_size(); + buf.write(0,4); + { + const BYTE* p1=ptr+ptr[0x4B]; + const BYTE* _p=p1+p1[1]; + p1+=2; + while(_p[-1]==' ') _p--; + _b=(_p-p1)+4; + + BYTE tmp[3]={0,0xFF,1}; + buf.write(tmp,3); + gb_write_delta(buf,_p-p1); + buf.write(p1,_p-p1); + p1=_p; + } + ptr+=ptr[0x57]; + { + DWORD d=DoTrack(ptr,&_b); + if (d==-1) return 0; + ptr+=d; + } + buf.write_dword_ptr(rev32(_b),_s); + } + buf.write_dword_ptr(_rv('MThd'),0); + buf.write_dword_ptr(_rv(6),4); + + MIDIHEADER mhd={0x0100,rev16(ntrax),0xC000}; + buf.write_ptr(&mhd,sizeof(mhd),8); + + mf->size = buf.get_size(); + mf->data = (BYTE*)buf.finish(); + return !!mf->data; +} + +bool load_hmi(MIDI_file* mf,const BYTE* _buf,size_t sz) +{ + HMI_cvt c; + return c.run(mf,_buf,sz); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/hmp.cpp b/Src/Plugins/Input/in_midi/hmp.cpp new file mode 100644 index 00000000..c6c869a7 --- /dev/null +++ b/Src/Plugins/Input/in_midi/hmp.cpp @@ -0,0 +1,149 @@ +#include "main.h" +#include "cvt.h" +#include <intsafe.h> + +#define _MThd 'dhTM' +#define _MTrk 'krTM' + +static DWORD ProcessTrack(const BYTE* track,grow_buf & out,int size) +{ + UINT s_sz=out.get_size(); + const BYTE *pt = track; + BYTE lc1 = 0,lastcom = 0; + DWORD t=0,d; + bool run = 0; + int n1,n2; + while(track < pt + size) + { + if (track[0]&0x80) + { + BYTE b=track[0]&0x7F; + out.write_byte(b); + t+=b; + } + else + { + d = (track[0])&0x7F; + n1 = 0; + while((track[n1]&0x80)==0) + { + n1++; + d+=(track[n1]&0x7F)<<(n1*7); + } + t+=d; + + n1 = 1; + while((track[n1]&0x80)==0) + { + n1++; + if (n1==4) return 0; + } + for(n2=0;n2<=n1;n2++) + { + BYTE b=track[n1-n2]&0x7F; + + if (n2!=n1) b|=0x80; + out.write_byte(b); + } + track+=n1; + } + track++; + if (*track == 0xFF)//meta + { + unsigned int _d; + UINT s=DecodeDelta(track+2,&_d); + UINT result; + if (UIntAdd(2, s, &result) || UIntAdd(result, _d, &result) == S_OK || !out.write(track,result)) + return 0; + if (track[1]==0x2F) break; + } + else + { + lc1=track[0]; + if ((lc1&0x80) == 0) return 0; + switch(lc1&0xF0) + { + case 0x80: + case 0x90: + case 0xA0: + case 0xB0: + case 0xE0: + if (lc1!=lastcom) + { + out.write_byte(lc1); + } + out.write(track+1,2); + track+=3; + break; + case 0xC0: + case 0xD0: + if (lc1!=lastcom) + { + out.write_byte(lc1); + } + out.write_byte(track[1]); + track+=2; + break; + default: + return 0; + } + lastcom=lc1; + } + } + return out.get_size()-s_sz; +} + +#define FixHeader(H) {(H).fmt=rev16((H).fmt);(H).trax=rev16((H).trax);(H).dtx=rev16((H).dtx);} + +BYTE hmp_track0[19]={'M','T','r','k',0,0,0,11,0,0xFF,0x51,0x03,0x18,0x80,0x00,0,0xFF,0x2F,0}; + +bool load_hmp(MIDI_file* mf,const BYTE* buf, size_t br) +{ + MIDIHEADER mhd = {1,0,0xC0}; + const BYTE * max = buf+br; + const BYTE* ptr = buf; + BOOL funky=0; + if (!memcmp(buf,"HMIMIDIR",8)) funky=1; + grow_buf dst; + DWORD n1,n2; + dst.write_dword(_rv('MThd')); + dst.write_dword(_rv(6)); + dst.write(0,sizeof(mhd)); + ptr = buf+(funky ? 0x1a: 0x30); + mhd.trax = *ptr; + if (funky) mhd.dtx=rev16(*(WORD*)(buf+0x4c))/6; + dst.write(hmp_track0,sizeof(hmp_track0)); + + while(*(WORD*)ptr != 0x2FFF && ptr < max - 4-7) ptr++; + ptr+=funky ? 5 : 7; + if (ptr == max-4) return 0; + UINT n; + + + for(n=1;n<mhd.trax;n++) + { + n1 = funky ? *(WORD*)ptr : *(DWORD*)ptr - 12; + if (ptr + n1 > max) + { + mhd.trax=n; + break; + } + dst.write_dword(_rv('MTrk')); + if (!funky) ptr += 8; + + UINT ts_ofs=dst.get_size(); + dst.write_dword(0); + if (!(n2=ProcessTrack(funky ? ptr+4 : ptr,dst,n1))) return 0; + + dst.write_dword_ptr(rev32(n2),ts_ofs); + if (funky) ptr+=n1; + else ptr += n1 + 4; + } + FixHeader(mhd); + dst.write_ptr(&mhd,sizeof(mhd),8); + + + mf->size = dst.get_size(); + mf->data = (BYTE*)dst.finish(); + return !!mf->data; +} diff --git a/Src/Plugins/Input/in_midi/in_midi.rc b/Src/Plugins/Input/in_midi/in_midi.rc new file mode 100644 index 00000000..804d289e --- /dev/null +++ b/Src/Plugins/Input/in_midi/in_midi.rc @@ -0,0 +1,568 @@ +// 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_CONFIG DIALOGEX 0, 0, 354, 174 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_NOFAILCREATE | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Nullsoft Midi Player Preferences" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + CONTROL "Tab1",IDC_TAB,"SysTabControl32",WS_TABSTOP,4,4,346,149 + LTEXT "Note: Most settings take effect after restarting playback.",IDC_STATIC,4,158,214,12,SS_CENTERIMAGE + DEFPUSHBUTTON "OK",IDOK,222,157,40,13 + PUSHBUTTON "Cancel",IDCANCEL,266,157,40,13 + PUSHBUTTON "Reset",IDRESET,310,157,40,13 +END + +IDD_CONFIG1 DIALOGEX 0, 0, 340, 134 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LTEXT "Device:",IDC_STATIC,3,6,30,8 + COMBOBOX IDC_PORT,37,4,300,68,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Device Info",IDC_STATIC,3,20,190,76 + EDITTEXT IDC_DEV_INFO,10,31,177,60,ES_MULTILINE | ES_READONLY | WS_VSCROLL + GROUPBOX "Volume Control",IDC_STATIC,197,20,140,44 + COMBOBOX IDC_VOLMODE,204,31,126,100,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Use as logarithmic",IDC_LOGVOL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,204,49,126,10 + GROUPBOX "MIDI Extensions",IDC_STATIC,3,100,334,29 + LTEXT "Additional reset commands between tracks:",IDC_STATIC,12,113,150,8 + COMBOBOX IDC_HARDWARE_RESET,166,111,164,83,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP +END + +IDD_CONFIG2 DIALOGEX 0, 0, 340, 134 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + CONTROL "Sampling enabled",IDC_SAMPLING_ENABLED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,4,72,10 + LTEXT "Device:",IDC_STATIC,3,20,30,8 + COMBOBOX IDC_WAVEIN,37,18,293,81,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Source:",IDC_STATIC,3,37,30,8 + COMBOBOX IDC_WAVEIN_SRC,37,35,293,81,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Revert to previous source on stop",IDC_SAMP_REVERT, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,52,157,10 + GROUPBOX "Format",IDC_STATIC,3,65,334,31 + COMBOBOX IDC_WAVEIN_SR,10,77,40,70,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP + LTEXT "Hz",IDC_WAVEIN_S2,51,79,10,8 + COMBOBOX IDC_WAVEIN_CH,69,77,37,40,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_WAVEIN_BPS,117,77,38,60,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Advanced",IDC_STATIC,3,100,334,29 + CONTROL "Send to Winamp's output system",IDC_SAMPLING_OUTPUT, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,113,140,10 +END + +IDD_CONFIG3 DIALOGEX 0, 0, 340, 134 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "Device Settings",IDC_STATIC,3,4,160,44 + CONTROL "Reverb",IDC_REVERB,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,17,40,10 + CONTROL "Chorus",IDC_CHORUS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,96,17,40,10 + LTEXT "Mixing frequency:",IDC_STATIC,10,33,64,8 + COMBOBOX IDC_FREQ,74,31,43,50,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Hz",IDC_STATIC,121,33,10,8 + GROUPBOX "DLS Settings",IDC_STATIC,167,4,170,44 + CONTROL "Use custom DLS file",IDC_DLS_CB,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,174,17,100,10 + EDITTEXT IDC_DLS,174,31,141,12,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "...",IDC_DLS_B,316,31,14,12 + GROUPBOX "Compatibility Settings",IDC_STATIC,3,52,334,36 + CONTROL "Keep DirectMusic port active between tracks\nFaster track changes, may cause problems (enable GM reset on device tab, etc)",IDC_DM_KEEP_PORT, + "Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,10,65,320,16 +END + +IDD_CONFIG4 DIALOGEX 0, 0, 340, 134 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "Playback Method",IDC_STATIC,3,4,334,54 + COMBOBOX IDC_PLAYBACK_METHOD,10,16,320,66,CBS_DROPDOWNLIST | WS_TABSTOP + CONTROL "Show realtime MIDI control panel\n(works only with immediate playback method)",IDC_SHOW_PANEL, + "Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,10,33,320,18 + GROUPBOX "Looping",IDC_LOOP_S,3,62,334,32 + COMBOBOX IDC_LOOP,10,75,129,50,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Loop",IDC_LOOP_S2,156,77,18,8 + EDITTEXT IDC_LOOP_T,178,75,28,12,ES_CENTER | ES_AUTOHSCROLL | ES_NUMBER + CONTROL "Spin1",IDC_LOOP_SP,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS,198,74,11,14 + LTEXT "times",IDC_LOOP_S3,210,77,20,8 + CONTROL "Infinite",IDC_INFINITE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,251,76,37,10 +END + +IDD_CONFIG5 DIALOGEX 0, 0, 340, 134 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "Hacks",IDC_STATIC_CLN,3,4,160,69 + CONTROL "Fix missing DLS drum kits",IDC_HACK_DLS_DRUMS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,16,146,10 + CONTROL "Fix missing DLS instruments",IDC_HACK_DLS_INSTRUMENTS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,30,146,10 + CONTROL "Disable sysex commands",IDC_HACK_NO_SYSEX,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,44,146,10 + CONTROL "Remove B9 00 00 / B9 20 00",IDC_HACK_XG_DRUMS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,57,146,10 + GROUPBOX "Misc",IDC_STATIC,167,4,170,45 + CONTROL "Try to recover incomplete tracks",IDC_HACKTRACK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,173,16,117,10 + LTEXT "Extra silence after end of song:",IDC_STATIC,173,32,105,8 + EDITTEXT IDC_EOF_DELAY,282,30,36,12,ES_AUTOHSCROLL | ES_NUMBER + CONTROL "Spin1",IDC_EOF_DELAY_SPIN,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS,310,29,9,14 + LTEXT "ms",IDC_STATIC,320,32,10,8 +END + +IDD_CONFIG6 DIALOGEX 0, 0, 340, 134 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "MIDI Hardware Setup (Advanced Users Only)",IDC_STATIC_MOS,3,4,334,125 + LISTBOX IDC_SYSEX_LIST,9,12,321,86,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "import file",IDC_IMP_F,9,103,42,14 + PUSHBUTTON "export file",IDC_EXP_F,51,103,42,14 + PUSHBUTTON "load preset",IDC_IMP_PR,93,103,46,14 + PUSHBUTTON "save preset",IDC_EXP_PR,139,103,48,14 + PUSHBUTTON "add new",IDC_SYSEX_ADD,187,103,38,14 + PUSHBUTTON "delete",IDC_SYSEX_DELETE,225,103,32,14 + PUSHBUTTON "edit",IDC_SYSEX_EDIT,257,103,24,14 + PUSHBUTTON "up",IDC_SYSEX_UP,282,103,20,14 + PUSHBUTTON "down",IDC_SYSEX_DOWN,302,103,28,14 + CTEXT "Sysex commands are sent before starting playback.",IDC_STATIC_MOS1,8,119,322,8 +END + +IDD_CONFIG7 DIALOGEX 0, 0, 340, 134 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LTEXT "Associate with extensions:",IDC_STATIC,4,4,100,8 + LISTBOX IDC_EXTS_LIST,4,15,86,115,NOT LBS_NOTIFY | LBS_MULTIPLESEL | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + LTEXT "Additional extensions (separate with semicolons):",IDC_STATIC,114,4,223,8 + EDITTEXT IDC_EXTS_ED,114,15,152,12,ES_AUTOHSCROLL + LTEXT "eg. ""MID;RMI;HMP""",IDC_STATIC,114,31,66,8 +END + +IDD_CONFIG8 DIALOGEX 0, 0, 340, 134 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "RMI Info Settings",IDC_STATIC,3,4,158,30 + CONTROL "Display RMI info dialog by default",IDC_RMI_DEF,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,17,145,10 + GROUPBOX "Lyrics Display",IDC_STATIC,165,4,172,30 + CONTROL "Show lyrics window while playing",IDC_LYRICS_ENABLED, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,172,17,158,10 +END + +IDD_EXT_IMM DIALOGEX 0, 0, 488, 212 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "MIDI control panel" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LTEXT "tempo",-1,2,2,20,8 + CONTROL "Slider1",IDC_TEMPO,"msctls_trackbar32",TBS_NOTICKS | WS_TABSTOP,24,2,100,11 + CTEXT "",IDC_TDISP,48,13,60,8 + CONTROL "disable volume commands",IDC_NOVOL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,156,8,99,10 + CONTROL "disable instrument commands",IDC_NOINS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,280,8,108,10 + LTEXT "channel",-1,4,24,26,8 + LTEXT "volume",-1,6,40,24,8 + PUSHBUTTON "all on",IDC_ALL_ON,4,92,28,11 + PUSHBUTTON "all off",IDC_ALL_OFF,4,104,28,11 + LTEXT "mute",-1,12,119,16,8 + GROUPBOX "instruments",-1,0,130,485,46 + LTEXT "patch",-1,2,140,19,8 + LTEXT "bank MSB",-1,2,152,34,8 + LTEXT "bank LSB",-1,2,164,32,8 + GROUPBOX "SysEx",-1,4,180,113,28 + PUSHBUTTON "reset GM",IDC_GMRESET,8,190,34,12 + PUSHBUTTON "reset GS",IDC_GSRESET,44,190,34,12 + PUSHBUTTON "reset XG",IDC_XGRESET,80,190,33,12 + GROUPBOX "",-1,116,180,125,28 + LTEXT "F0",-1,120,193,9,8 + EDITTEXT IDC_SYSEX1,132,190,68,12,ES_AUTOHSCROLL + LTEXT "F7",-1,202,193,9,8 + PUSHBUTTON "send",IDC_SYSEX1_SEND,214,190,21,12 + GROUPBOX "",-1,240,180,125,28 + LTEXT "F0",-1,244,193,9,8 + EDITTEXT IDC_SYSEX2,256,190,68,12,ES_AUTOHSCROLL + LTEXT "F7",-1,326,193,9,8 + PUSHBUTTON "send",IDC_SYSEX2_SEND,338,190,21,12 +END + +#if defined(APSTUDIO_INVOKED) || defined(IN_MIDI) +#if defined(APSTUDIO_INVOKED) +IDD_INFO$(IN_MIDI) DIALOGEX 0, 0, 295, 86 +#else +IDD_INFO DIALOGEX 0, 0, 295, 86 +#endif +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +EXSTYLE WS_EX_TOOLWINDOW +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + LTEXT "Format:",IDC_STATIC,4,2,24,8 + LTEXT "",IDC_FORMAT,32,2,72,8 + EDITTEXT IDC_COPYRIGHT,2,12,126,55,ES_MULTILINE | ES_READONLY | WS_VSCROLL + LTEXT "Length:",IDC_STATIC1,2,68,25,8 + RTEXT "",IDC_MS,26,68,28,8 + RTEXT "",IDC_TIX,26,77,28,8 + LTEXT "ms",IDC_STATIC2,58,68,10,8 + LTEXT "ticks",IDC_STATIC3,58,77,16,8 + CTEXT "",IDC_FSIZE,80,72,44,8 + LTEXT "",IDC_NTRAX,133,0,52,8 + LISTBOX IDC_TRAX,132,8,160,60,NOT LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | LBS_NOSEL | WS_VSCROLL | WS_TABSTOP + LTEXT "DLS data present",IDC_DLS,136,68,56,8,WS_DISABLED + LTEXT "loop start found",IDC_LOOP,136,78,50,8,WS_DISABLED + PUSHBUTTON "RMI info...",IDC_RMI_CRAP,196,72,41,12 + PUSHBUTTON "Save...",IDC_SAVE,240,72,29,12 + DEFPUSHBUTTON "Ok",IDOK,272,72,20,12 +END +#endif + +IDD_LYRICS DIALOGEX 0, 0, 263, 220 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "Lyrics" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_BLAH,0,0,260,216,ES_MULTILINE | ES_READONLY | WS_VSCROLL | WS_HSCROLL +END + +#if defined(APSTUDIO_INVOKED) || defined(IN_MIDI) +#if defined(APSTUDIO_INVOKED) +IDD_RMI_SHIZ$(IN_MIDI) DIALOGEX 0, 0, 316, 151 +#else +IDD_RMI_SHIZ DIALOGEX 0, 0, 316, 151 +#endif +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_NOFAILCREATE | WS_POPUP | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_TOOLWINDOW +CAPTION "RMI info" +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + LTEXT "Display Name:",IDC_STATIC,2,6,46,8 + EDITTEXT IDC_DISP,48,4,128,12,ES_AUTOHSCROLL + LTEXT "Name:",IDC_STATIC,26,22,22,8 + EDITTEXT IDC_NAME,48,20,128,12,ES_AUTOHSCROLL + LTEXT "Artist:",IDC_STATIC,27,38,24,8 + EDITTEXT IDC_ARTIST,48,36,128,12,ES_AUTOHSCROLL + LTEXT "Album:",IDC_STATIC,25,54,22,8 + EDITTEXT IDC_ALBUM,48,52,76,12,ES_AUTOHSCROLL + LTEXT "Track:",IDC_STATIC,130,54,22,8 + EDITTEXT IDC_TRACK,152,52,24,12,ES_AUTOHSCROLL | ES_NUMBER + LTEXT "Genre:",IDC_STATIC,24,70,22,8 + COMBOBOX IDC_GENRE,47,68,129,68,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + LTEXT "Date Created:",IDC_STATIC,1,86,46,8 + EDITTEXT IDC_DATE,47,84,129,12,ES_AUTOHSCROLL + LTEXT "Software:",IDC_STATIC,16,102,31,8 + EDITTEXT IDC_SOFTWARE,47,100,129,12,ES_AUTOHSCROLL + LTEXT "Engineer:",IDC_STATIC,16,118,31,8 + EDITTEXT IDC_ENGINEER,47,116,129,12,ES_AUTOHSCROLL + LTEXT "Composer:",IDC_STATIC,12,134,34,8 + EDITTEXT IDC_COMPOSER,47,132,129,12,ES_AUTOHSCROLL + LTEXT "Copyright:",IDC_STATIC,188,1,34,8 + EDITTEXT IDC_COPYRIGHT,188,9,124,36,ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL + LTEXT "Comment:",IDC_STATIC,188,46,32,8 + EDITTEXT IDC_COMMENT,188,54,124,36,ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL + LTEXT "Subject:",IDC_STATIC,188,91,27,8 + EDITTEXT IDC_SUBJECT,188,99,96,36,ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL + CONTROL "",IDC_BMPVIEW,"BitmapView",WS_TABSTOP,288,100,24,22 + DEFPUSHBUTTON "OK",IDOK,206,138,50,12 + PUSHBUTTON "Cancel",IDCANCEL,262,138,50,12 +END +#endif // APSTUDIO_INVOKED || IN_MIDI + +IDD_SYSEX DIALOGEX 0, 0, 336, 38 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Sysex Event Editor" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LTEXT "F0",IDC_STATIC,4,6,9,8 + EDITTEXT IDC_EDIT1,24,4,290,12,ES_AUTOHSCROLL + LTEXT "F7",IDC_STATIC,323,6,9,8 + LTEXT "wait",IDC_STATIC,4,23,20,8 + EDITTEXT IDC_DELAY,24,21,40,12,ES_AUTOHSCROLL | ES_NUMBER + CONTROL "Spin1",IDC_SPIN1,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS,52,20,11,14 + LTEXT "ms after sending",IDC_STATIC,69,23,70,8 + DEFPUSHBUTTON "OK",IDOK,228,21,50,13 + PUSHBUTTON "Cancel",IDCANCEL,282,21,50,13 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_CONFIG, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 350 + TOPMARGIN, 4 + BOTTOMMARGIN, 170 + END + + IDD_CONFIG1, DIALOG + BEGIN + LEFTMARGIN, 3 + RIGHTMARGIN, 337 + TOPMARGIN, 4 + BOTTOMMARGIN, 130 + END + + IDD_CONFIG2, DIALOG + BEGIN + LEFTMARGIN, 3 + RIGHTMARGIN, 337 + TOPMARGIN, 4 + BOTTOMMARGIN, 130 + END + + IDD_CONFIG3, DIALOG + BEGIN + LEFTMARGIN, 3 + RIGHTMARGIN, 337 + TOPMARGIN, 4 + BOTTOMMARGIN, 130 + END + + IDD_CONFIG4, DIALOG + BEGIN + LEFTMARGIN, 3 + RIGHTMARGIN, 337 + TOPMARGIN, 4 + BOTTOMMARGIN, 130 + END + + IDD_CONFIG5, DIALOG + BEGIN + LEFTMARGIN, 3 + RIGHTMARGIN, 337 + TOPMARGIN, 4 + BOTTOMMARGIN, 130 + END + + IDD_CONFIG6, DIALOG + BEGIN + LEFTMARGIN, 3 + RIGHTMARGIN, 337 + TOPMARGIN, 4 + BOTTOMMARGIN, 129 + END + + IDD_CONFIG7, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 337 + TOPMARGIN, 4 + BOTTOMMARGIN, 130 + END + + IDD_CONFIG8, DIALOG + BEGIN + LEFTMARGIN, 3 + RIGHTMARGIN, 337 + TOPMARGIN, 4 + BOTTOMMARGIN, 130 + END + + "IDD_RMI_SHIZ$(IN_MIDI)", DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 309 + TOPMARGIN, 7 + BOTTOMMARGIN, 144 + END + + IDD_SYSEX, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 332 + TOPMARGIN, 4 + BOTTOMMARGIN, 34 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_MIDI_PLAYER "Nullsoft MIDI Player v%s" + 65535 "{0FED0FEE-C995-4499-AB47-E2482336C046}" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_MIDI_PLAYER_OLD "Nullsoft MIDI Player" + STRING_FILES_OTHER "Other MIDI types (" + IDS_TO_ENABLE_LYRICS_DISPLAY + "To enable lyrics display again, go to MIDI plug-in config / display tab and check ""Show lyrics window while playing""." + IDS_INFORMATION "Information" + STRING_INCOMPLETE " (incomplete)" + STRING_TRACKS_FMT "%u tracks:" + IDS_MIDIS_ARE_NOT_BURNABLE " MIDIs are not burnable." + IDS_NONE "none" + IDS_STREAMED "Streamed - send data to OS as large blocks / segments" + IDS_IMMEDIATE "Immediate - send MIDI events to OS in realtime" + IDS_CONFIGURATION "Configuration" + IDS_TYPE "type : " + IDS_PREFS_DEVICE "Device" +END + +STRINGTABLE +BEGIN + IDS_PREFS_DISPLAY "Display" + IDS_PREFS_SAMPLING "Sampling" + IDS_PREFS_DIRECTMUSIC "DirectMusic" + IDS_PREFS_MISC "Misc" + IDS_PREFS_FILE_TYPES "File Types" + IDS_PREFS_FILES "Files" + IDS_PREFS_HARDWARE_SETUP "Hardware setup" + IDS_UNABLE_TO_LOAD_FILE "Unable to load file." + STRING_RETRIEVING_FILE "Retrieving MIDI file" + STRING_URL_ERROR "URLs only supported in Winamp 2.50+" + STRING_UNKNOWN_MMSYSTEM "Unknown MMSYSTEM error." + STRING_MIDI_INFO_FMT2 "MIDI file info - %s" + STRING_MIDI_INFO_FMT1 "MIDI file info - %s (%s)" + STRING_BYTES_FMT "%u bytes" + STRING_WRITE_ERROR_FMT "Unable to create file: ""%s""" + STRING_INFO_FORMAT_FMT " / format %u" +END + +STRINGTABLE +BEGIN + STRING_RMI_INFO_FMT "RMI info - %s" + STRING_CONFIG_RESET "This will reset all settings to default values. Continue?" + STRING_STEREO "Stereo" + STRING_MONO "Mono" + STRING_VOLUME_AUTO "Autodetect" + STRING_VOLUME_DRIVER_SPECIFIC "Driver-specific" + STRING_VOLUME_NONE "None" + STRING_SAMP_SRC_DEFAULT "(default)" + STRING_LOOP1 "never" + STRING_LOOP2 "when loop start detected" + STRING_LOOP3 "always" + STRING_UNKNOWN "unknown (%u)" + STRING_DIRECT_MIDISTREAM "direct midiStream support" + STRING_MOCAPS_WAVETABLE "hardware wavetable synthesizer" + STRING_MOCAPS_SYNTH "synthesizer" + STRING_MOCAPS_SQUARE "square wave synthesizer" +END + +STRINGTABLE +BEGIN + STRING_MOCAPS_MAPPER "MIDI mapper" + STRING_MOCAPS_HWPORT "MIDI hardware port" + STRING_MOCAPS_FM "FM synthesizer" + STRING_EFFECTS "effects : " + STRING_DEVICE_TYPE "device type : " + STRING_DMCAPS_WDM "WDM driver" + STRING_DMCAPS_USERMODE "User mode synthesizer" + STRING_DMCAPS_WINMM "Windows Multimedia driver" + STRING_CHORUS "chorus" + STRING_REVERB "reverb" + STRING_DMCAPS_SHARE "device is shareable" + STRING_DMCAPS_DSOUND "uses DirectSound" + STRING_DMCAPS_XG "built-in XG sound set" + STRING_DMCAPS_GS "built-in GS sound set" + STRING_DMCAPS_GM "built-in GM sound set" + STRING_DMCAPS_SOFTSYNTH "software synthesizer" +END + +STRINGTABLE +BEGIN + STRING_DMCAPS_DLS2 "supports DLS level 2" + STRING_DMCAPS_DLS1 "supports DLS level 1" + STRING_FILES_SMF "Standard MIDI" + STRING_FILES_CLONE "MIDI Clones" + STRING_FILES_COMPRESSED "Compressed MIDI" + IDS_SYSEX_DATA "Sysex Data" + IDS_MIDI_HARDWARE_PRESETS "MIDI Hardware Presets" + IDS_DLS_FILES "DLS Files" + IDS_MIDI_FILES "MIDI files" + IDS_COMPRESSED_MIDI_FILES "Compressed MIDI files" + IDS_RMI_FILES "RMI files" + IDS_COMPRESSED_RMI_FILES "Compressed RMI files" + IDS_WITH_OUTPUT " (with output)" + IDS_USES_WINAMPS_OUTPUT_PLUGINS "uses Winamp's Output plug-ins" + STRING_MS_FMT "(%u ms)" + STRING_BIT_FMT "%u bit" +END + +STRINGTABLE +BEGIN + IDS_FAMILY_STRING_MIDI "MIDI File Format" + IDS_FAMILY_STRING_KARAOKE_MIDI "Karaoke MIDI File" + IDS_FAMILY_STRING_HMI_MIDI "Human Machine Interfaces MIDI File" + IDS_FAMILY_STRING_EXTENDED_MIDI "Extended MIDI File" + IDS_FAMILY_STRING_MSS_MIDI "MSS MIDI File" + IDS_FAMILY_STRING_FINALE_MIDI "Finale Notation MIDI Music File" + IDS_FAMILY_STRING_CREATIVE_MIDI "Creative Music MIDI Format" + IDS_FAMILY_STRING_GENERAL_MIDI_DUMP "General Midi Dump File" + IDS_FAMILY_STRING_COMPRESSED_MIDI "Compressed MIDI File" + IDS_FAMILY_STRING_COMPRESSED_HMI_MIDI + "Compressed Human Machine Interfaces MIDI File" + IDS_ABOUT_TEXT "%s\n© 2000-2023 Winamp SA\n\nBuild date: %hs" +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_midi/in_midi.sln b/Src/Plugins/Input/in_midi/in_midi.sln new file mode 100644 index 00000000..c440a567 --- /dev/null +++ b/Src/Plugins/Input/in_midi/in_midi.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29613.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_midi", "in_midi.vcxproj", "{880DB5D8-1FFB-4FC8-A625-C44884C773FD}" +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 + {880DB5D8-1FFB-4FC8-A625-C44884C773FD}.Debug|Win32.ActiveCfg = Debug|Win32 + {880DB5D8-1FFB-4FC8-A625-C44884C773FD}.Debug|Win32.Build.0 = Debug|Win32 + {880DB5D8-1FFB-4FC8-A625-C44884C773FD}.Debug|x64.ActiveCfg = Debug|x64 + {880DB5D8-1FFB-4FC8-A625-C44884C773FD}.Debug|x64.Build.0 = Debug|x64 + {880DB5D8-1FFB-4FC8-A625-C44884C773FD}.Release|Win32.ActiveCfg = Release|Win32 + {880DB5D8-1FFB-4FC8-A625-C44884C773FD}.Release|Win32.Build.0 = Release|Win32 + {880DB5D8-1FFB-4FC8-A625-C44884C773FD}.Release|x64.ActiveCfg = Release|x64 + {880DB5D8-1FFB-4FC8-A625-C44884C773FD}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6065B54A-493B-411D-8491-E7A9F2C3A87F} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_midi/in_midi.vcxproj b/Src/Plugins/Input/in_midi/in_midi.vcxproj new file mode 100644 index 00000000..dbe34afc --- /dev/null +++ b/Src/Plugins/Input/in_midi/in_midi.vcxproj @@ -0,0 +1,328 @@ +<?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>{880DB5D8-1FFB-4FC8-A625-C44884C773FD}</ProjectGuid> + <RootNamespace>in_midi</RootNamespace> + <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" 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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|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> + <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>true</VcpkgEnabled> + <VcpkgEnableManifest>false</VcpkgEnableManifest> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>..\..\..\Wasabi;..\..\..\external_dependencies\vcpkg\$(PlatformShortName)-windows-static\include;..\..\..\external_dependencies\vcpkg\$(PlatformShortName)-windows-static\include\minizip;.;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;IN_MIDI_EXPORTS;IN_MIDI;WINVER=0x601;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4018;4133;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;IN_MIDI;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>winmm.lib;comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>winmm.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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;NDEBUG;_WINDOWS;_USRDLL;IN_MIDI_EXPORTS;IN_MIDI;WINVER=0x601;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4018;4133;4244;4267;4302;4311;4312;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;IN_MIDI;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>winmm.lib;comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>winmm.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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> + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>..\..\..\Wasabi;..\..\..\external_dependencies\vcpkg\$(PlatformShortName)-windows-static\include;..\..\..\external_dependencies\vcpkg\$(PlatformShortName)-windows-static\include\minizip;.;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;IN_MIDI_EXPORTS;IN_MIDI;WINVER=0x601;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4018;4133;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;IN_MIDI;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>winmm.lib;comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>winmm.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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> + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>..\..\..\Wasabi;.;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;IN_MIDI_EXPORTS;IN_MIDI;WINVER=0x601;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4018;4133;4244;4267;4302;4311;4312;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;IN_MIDI;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>winmm.lib;comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>winmm.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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="..\..\..\pfc\cfg_var.cpp" /> + <ClCompile Include="..\..\..\pfc\grow_buf.cpp" /> + <ClCompile Include="..\..\..\pfc\string.cpp" /> + <ClCompile Include="..\..\..\pfc\string_unicode.cpp" /> + <ClCompile Include="cleaner.cpp" /> + <ClCompile Include="cmf.cpp" /> + <ClCompile Include="CompressionUtility.cpp" /> + <ClCompile Include="config.cpp" /> + <ClCompile Include="fakedsound.cpp" /> + <ClCompile Include="genres.c" /> + <ClCompile Include="gmf.cpp" /> + <ClCompile Include="guids.cpp" /> + <ClCompile Include="hmi.cpp" /> + <ClCompile Include="hmp.cpp" /> + <ClCompile Include="info.cpp" /> + <ClCompile Include="main.cpp" /> + <ClCompile Include="midifile.cpp" /> + <ClCompile Include="midiinfo.cpp" /> + <ClCompile Include="midi_driver.cpp" /> + <ClCompile Include="mids.cpp" /> + <ClCompile Include="mus.cpp" /> + <ClCompile Include="out_dmusic.cpp" /> + <ClCompile Include="out_midi.cpp" /> + <ClCompile Include="sampling.cpp" /> + <ClCompile Include="seq.cpp" /> + <ClCompile Include="utils.cpp" /> + <ClCompile Include="wa2.cpp" /> + <ClCompile Include="xmi.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="CompressionUtility.h" /> + <ClInclude Include="core_api.h" /> + <ClInclude Include="cvt.h" /> + <ClInclude Include="fakedsound.h" /> + <ClInclude Include="genres.h" /> + <ClInclude Include="In2.h" /> + <ClInclude Include="main.h" /> + <ClInclude Include="midifile.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="seq.h" /> + <ClInclude Include="utils.h" /> + <ClInclude Include="wa2.h" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_midi.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_midi/in_midi.vcxproj.filters b/Src/Plugins/Input/in_midi/in_midi.vcxproj.filters new file mode 100644 index 00000000..01be7416 --- /dev/null +++ b/Src/Plugins/Input/in_midi/in_midi.vcxproj.filters @@ -0,0 +1,143 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="cleaner.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="cmf.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="config.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="fakedsound.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="gmf.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="guids.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="hmi.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="hmp.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="info.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="midi_driver.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="midifile.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="midiinfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="mids.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="mus.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="out_dmusic.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="out_midi.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="sampling.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="seq.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="utils.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="xmi.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="wa2.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="genres.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="CompressionUtility.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\pfc\cfg_var.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\pfc\grow_buf.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\pfc\string.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\pfc\string_unicode.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="wa2.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="utils.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="seq.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="midifile.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="main.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="In2.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="genres.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="fakedsound.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="cvt.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="core_api.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="CompressionUtility.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{76163c49-62a1-40bf-a79e-3a31abcffa0d}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{2b6a02a5-067a-4e25-9414-7d18ebf093d5}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{8debc30d-140a-4e48-861b-21f5e29c3a8b}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_midi.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/info.cpp b/Src/Plugins/Input/in_midi/info.cpp new file mode 100644 index 00000000..6881d507 --- /dev/null +++ b/Src/Plugins/Input/in_midi/info.cpp @@ -0,0 +1,940 @@ +#include "main.h" +#include "resource.h" +#include <shlwapi.h> +#include <commdlg.h> +#include <strsafe.h> +#include "../nu/AutoWide.h" +#include "../nu/AutoCharFn.h" +#include "CompressionUtility.h" + +extern In_Module mod; + +extern "C" +{ +#include "genres.h" +} +#define HAVE_BMPVIEW + +#ifdef HAVE_BMPVIEW +static LRESULT CALLBACK BmpViewProc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp) +{ + switch (msg) + { + case WM_PAINT: + { + RECT r; + GetClientRect(wnd, &r); + HDC wdc = GetDC(wnd); + HDC dc = (HDC)GetWindowLongPtr(wnd, 4); + DrawEdge(wdc, &r, EDGE_SUNKEN, BF_RECT); + if (dc) BitBlt(wdc, 2, 2, r.right - 4, r.bottom - 4, dc, 0, 0, SRCCOPY); + ReleaseDC(wnd, wdc); + ValidateRect(wnd, 0); + return 0; + } + break; + case WM_USER: + { + HDC dc = CreateCompatibleDC(0); + SelectObject(dc, (HBITMAP)lp); + SetWindowLongPtr(wnd, 0, lp); + SetWindowLongPtr(wnd, 4, (LONG_PTR)dc); + //RedrawWindow(wnd,0,0,RDW_INVALIDATE); + } + return 0; + case WM_DESTROY: + { + HBITMAP bmp = (HBITMAP)GetWindowLongPtr(wnd, 0); + HDC dc = (HDC)GetWindowLongPtr(wnd, 4); + if (dc) DeleteDC(dc); + if (bmp) DeleteObject(bmp); + } + break; + } + return DefWindowProc(wnd, msg, wp, lp); +} + +void bmpview_init() +{ + static bool got_class; + if (!got_class) + { + got_class = 1; + WNDCLASS wc = + { + 0, + BmpViewProc, + 0, 8, + MIDI_callback::GetInstance(), 0, LoadCursor(0, IDC_ARROW), 0, + 0, + L"BitmapView" + }; + RegisterClassW(&wc); + } +} +#endif + +BOOL SaveFile(HWND w, MIDI_file* mf, BOOL info); +int SaveAsGZip(string filename, const void* buffer, size_t size); + +UINT align(UINT x, UINT a) +{ + a--; + return (x + a) & ~a; +} + +typedef struct +{ + UINT ctrl_id; + DWORD riff_id; + // char * name; +} +RMI_TAG; + +static void _swap_ptrs(void** a, void* b) +{ + void* _a = *a; + *a = b; + if (_a) free(_a); +} + +#define swap_ptrs(x,y) _swap_ptrs((void**)&x,(void*)(y)) + +static RMI_TAG rmi_tagz[] = +{ + {IDC_DISP, _rv('DISP')}, + {IDC_NAME, _rv('INAM')}, + {IDC_ARTIST, _rv('IART')}, + {IDC_ALBUM, _rv('IALB')}, + {IDC_TRACK, _rv('ITRK')}, + {IDC_GENRE, _rv('IGNR')}, + {IDC_COMPOSER, _rv('ICMP')}, + {IDC_COPYRIGHT, _rv('ICOP')}, + {IDC_COMMENT, _rv('ICMT')}, + {IDC_DATE, _rv('ICRD')}, + {IDC_SOFTWARE, _rv('ISFT')}, + {IDC_ENGINEER, _rv('IENG')}, + {IDC_SUBJECT, _rv('ISBJ')}, +}; + +void SetDlgItemTextSiz(HWND wnd, UINT id, char* text, UINT siz) +{ + if (!text[siz - 1]) SetDlgItemTextA(wnd, id, text); + else + { + char* foo = (char*)alloca(siz + 1); + memcpy(foo, text, siz); + foo[siz] = 0; + SetDlgItemTextA(wnd, id, foo); + } +} + +#define N_RMI_TAGZ (sizeof(rmi_tagz)/sizeof(rmi_tagz[0])) + + +static void set_rmi_dlg_title(HWND wnd, MIDI_file* mf) +{ + char t[MAX_PATH + 100] = { 0 }; + StringCbPrintfA(t, sizeof(t), WASABI_API_LNGSTRING(STRING_RMI_INFO_FMT), (const char*)mf->path); + SetWindowTextA(wnd, t); +} + +static BOOL CALLBACK rmiproc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp) +{ + switch (msg) + { + case WM_INITDIALOG: +#if defined(_WIN64) + SetWindowLong(wnd, DWLP_USER, lp); +#else + SetWindowLong(wnd, DWL_USER, lp); +#endif + { + MIDI_file* mf = (MIDI_file*)lp; + set_rmi_dlg_title(wnd, mf); + if (mf->title && *mf->title) + { + SetDlgItemTextA(wnd, IDC_DISP, mf->title); + } + if (mf->rmi_data) + { + BYTE* rmi = (BYTE*)mf->rmi_data; + int p = 4; + while (p < mf->rmi_size) + { + DWORD id = *(DWORD*)(rmi + p); + UINT n; + for (n = 0; n < N_RMI_TAGZ; n++) + { + if (id == rmi_tagz[n].riff_id) + { + SetDlgItemTextSiz(wnd, rmi_tagz[n].ctrl_id, (char*)(rmi + p + 8), *(DWORD*)(rmi + p + 4)); + break; + } + } + p += 8 + align(*(DWORD*)(rmi + p + 4), 2); + } + } +#ifdef HAVE_BMPVIEW + if (mf->bmp_data) + { + void* pixels; + BITMAPINFOHEADER* foo = (BITMAPINFOHEADER*)mf->bmp_data; + HBITMAP bmp = CreateDIBSection(0, (BITMAPINFO*)foo, DIB_RGB_COLORS, &pixels, 0, 0); + if (bmp) + { + UINT clr_used = foo->biClrUsed; + if (!clr_used) clr_used = 1 << foo->biBitCount; + BYTE* ptr = (BYTE*)foo + foo->biSize + (clr_used << 2); + int max = (BYTE*)mf->bmp_data + mf->bmp_size - ptr; + BITMAP b; + GetObject(bmp, sizeof(b), &b); + int siz = b.bmWidthBytes * b.bmHeight; + if (siz < 0) siz = -siz; + if (siz > max) siz = max; + memcpy(pixels, ptr, siz); + SendDlgItemMessage(wnd, IDC_BMPVIEW, WM_USER, 0, (long)bmp); + } + } +#endif + + } + genres_read(GetDlgItem(wnd, IDC_GENRE)); + return 1; + case WM_COMMAND: + switch (wp) + { + case IDOK: + { +#if defined(_WIN64) + MIDI_file* mf = (MIDI_file*)GetWindowLong(wnd, DWLP_USER); +#else + MIDI_file* mf = (MIDI_file*)GetWindowLong(wnd, DWL_USER); +#endif + char* title = 0; + HWND w = GetDlgItem(wnd, IDC_DISP); + UINT sz = GetWindowTextLength(w); + if (sz) + { + sz++; + title = (char*)malloc(sz); + GetWindowTextA(w, title, sz); + } + swap_ptrs(mf->title, title); + BYTE* rmi_info = 0; + UINT rmi_siz = 0; + UINT n; + for (n = 0; n < N_RMI_TAGZ; n++) + { + UINT d = GetWindowTextLength(GetDlgItem(wnd, rmi_tagz[n].ctrl_id)); + if (d) rmi_siz += align(d + 1, 2) + 8; + } + if (rmi_siz) + { + rmi_siz += 4; //'INFO' + rmi_info = (BYTE*)malloc(rmi_siz); + UINT ptr = 4; + *(DWORD*)rmi_info = _rv('INFO'); + for (n = 0; n < N_RMI_TAGZ; n++) + { + w = GetDlgItem(wnd, rmi_tagz[n].ctrl_id); + if (GetWindowTextLength(w)) + { + *(DWORD*)(rmi_info + ptr) = rmi_tagz[n].riff_id; + ptr += 4; + char* foo = (char*)(rmi_info + ptr + 4); + GetWindowTextA(w, foo, rmi_siz - (ptr + 4)); + UINT s = strlen(foo) + 1; + *(DWORD*)(rmi_info + ptr) = s; + ptr += 4 + align(s, 2); + } + } + rmi_siz = ptr; + } + mf->rmi_size = rmi_siz; + swap_ptrs(mf->rmi_data, rmi_info); + + genres_write(GetDlgItem(wnd, IDC_GENRE)); + + if (SaveFile(wnd, mf, 1)) EndDialog(wnd, 0); + } + break; + case IDCANCEL: + genres_write(GetDlgItem(wnd, IDC_GENRE)); + EndDialog(wnd, 1); + break; + } + break; + } + return 0; +} + +int show_rmi_info(HWND w, MIDI_file* mf) +{ +#ifdef HAVE_BMPVIEW + bmpview_init(); +#endif + return WASABI_API_DIALOGBOXPARAM(IDD_RMI_SHIZ, w, rmiproc, (long)mf); +} + +static bool is_local(const char* url) +{ + if (!_strnicmp(url, "file://", 7) || !strnicmp(url, "partial://", 10)) return 1; + if (url[1] == ':' && url[2] == '\\') return 1; + return strstr(url, "://") ? 0 : 1; +} + +static char* fmt_names[] = { "MIDI", "RIFF MIDI", "HMP", "HMI", "XMIDI", "MUS", "CMF", "GMD", "MIDS", "GMF", "MIDI(?)" }; + +const char* find_tag(MIDI_file* mf, DWORD id, UINT* siz); + +char* getfmtstring(MIDI_file* f, char* s) +{ + const char* z = fmt_names[f->format]; + while (z && *z) *(s++) = *(z++); + if (f->format <= 1) //MID/RMI + { + char foo[32] = { 0 }; + StringCbPrintfA(foo, sizeof(foo), WASABI_API_LNGSTRING(STRING_INFO_FORMAT_FMT), f->data[4 + 4 + 1]); + z = foo; + while (z && *z) *(s++) = *(z++); + } + if (f->info.e_type) + { + *(s++) = ' '; + *(s++) = '('; + z = f->info.e_type; + while (z && *z) *(s++) = *(z++); + *(s++) = ')'; + } + *s = 0; + return s; +} + +void file2title(const char* f, string& t); + +static const char* find_tag(MIDI_file* mf, DWORD id, UINT* siz) +{ + if (!mf->rmi_data) return 0; + char* rmi = (char*)mf->rmi_data; + int ptr = 4; + while (ptr < mf->rmi_size) + { + if (*(DWORD*)(rmi + ptr) == id) + { + UINT s = *(DWORD*)(rmi + ptr + 4); + UINT s1 = 0; + ptr += 8; + while (rmi[ptr + s1] && s1 < s) s1++; + if (siz) *siz = s1; + return rmi + ptr; + } + ptr += align(*(DWORD*)(rmi + ptr + 4), 2) + 8; + } + return 0; +} + +bool KeywordMatch(const char* mainString, const char* keyword) +{ + return !_stricmp(mainString, keyword); +} + + +static const wchar_t* pExtList[] = { L"MID",L"MIDI",L"RMI",L"KAR",L"HMP",L"HMI",L"XMI",L"MSS",L"MUS",L"CMF",L"GMD",L"MIDS",L"MIZ",L"HMZ" }; +static const int pExtDescIdList[] = { 0, 0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 0, 8, 9 }; +static const int pExtDescList[] = +{ + IDS_FAMILY_STRING_MIDI, + IDS_FAMILY_STRING_KARAOKE_MIDI, + IDS_FAMILY_STRING_HMI_MIDI, + IDS_FAMILY_STRING_EXTENDED_MIDI, + IDS_FAMILY_STRING_MSS_MIDI, + IDS_FAMILY_STRING_FINALE_MIDI, + IDS_FAMILY_STRING_CREATIVE_MIDI, + IDS_FAMILY_STRING_GENERAL_MIDI_DUMP, + IDS_FAMILY_STRING_COMPRESSED_MIDI, + IDS_FAMILY_STRING_COMPRESSED_HMI_MIDI +}; + +MIDI_file* wa2_open_file(const char* url); +extern "C" __declspec(dllexport) int winampGetExtendedFileInfoW(const wchar_t* fn, const char* data, wchar_t* dest, int destlen) +{ + MIDI_file* file = 0; + + if (KeywordMatch(data, "type")) + { + dest[0] = L'0'; + dest[1] = 0; + return 1; + } + if (KeywordMatch(data, "BURNABLE")) + { + dest[0] = L'0'; + dest[1] = 0; + return 1; + } + if (KeywordMatch(data, "noburnreason")) // note: this isn't supposed to be any kind of protection - just keeps the burner from misbehaving on protected tracks + { + WASABI_API_LNGSTRINGW_BUF(IDS_MIDIS_ARE_NOT_BURNABLE, dest, destlen); + return 1; + } + if (KeywordMatch(data, "family")) + { + INT index; + LPCWSTR e; + DWORD lcid; + e = PathFindExtensionW(fn); + if (L'.' != *e || 0x00 == *(++e)) return 0; + + lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); + for (index = sizeof(pExtList) / sizeof(wchar_t*) - 1; index >= 0 && CSTR_EQUAL != CompareStringW(lcid, NORM_IGNORECASE, e, -1, pExtList[index], -1); index--); + if (index >= 0 && S_OK == StringCchCopyW(dest, destlen, WASABI_API_LNGSTRINGW(pExtDescList[pExtDescIdList[index]]))) return 1; + return 0; + } + + file = wa2_open_file(AutoCharFn(fn)); + if (!file) + { + return 0; + } + const char* ret = 0; + //else if (!_stricmp(tag,"FILEPATH") || !_stricmp(tag,"PATH")) ret=file->path; + //else if (!_stricmp(tag,"DISPLAY")) ret=file->title; + //else if (!_stricmp(tag,"FIRSTTRACK")) ret=file->info.traxnames[0]; + + if (KeywordMatch(data, "LENGTH")) + { + _itow(file->GetLength(), dest, 10); + file->Free(); + return 1; + } + else if (file->rmi_data) + { + if (KeywordMatch(data, "TITLE")) ret = find_tag(file, _rv('INAM'), 0); + else if (KeywordMatch(data, "ARTIST")) ret = find_tag(file, _rv('IART'), 0); + else if (KeywordMatch(data, "COMPOSER")) ret = find_tag(file, _rv('ICMP'), 0); + else if (KeywordMatch(data, "GENRE")) ret = find_tag(file, _rv('IGNR'), 0); + else if (KeywordMatch(data, "ALBUM")) ret = find_tag(file, _rv('IALB'), 0); + else if (KeywordMatch(data, "COPYRIGHT")) ret = find_tag(file, _rv('ICOP'), 0); + else if (KeywordMatch(data, "COMMENT")) ret = find_tag(file, _rv('ICMT'), 0); + else if (KeywordMatch(data, "TRACK")) ret = find_tag(file, _rv('ITRK'), 0); + else if (KeywordMatch(data, "DATE")) ret = find_tag(file, _rv('ICRD'), 0); + else + { + file->Free(); + return 0; + } + } + else + { + file->Free(); + return 0; + } + + if (ret) + { + lstrcpynW(dest, AutoWide(ret), destlen); + + } + file->Free(); + return !!ret; +} + + +void MIDI_file::GetTitle(char* buf, int maxlen) +{ + string file_title; + file2title(path, file_title); + lstrcpynA(buf, file_title, maxlen); +} + +//int save_gzip(MIDI_file* mf, char* path); + +static void do_ext(string& s, const char* ext) +{ + const char* p = strrchr(s, '.'); + if (p) s.truncate(p - (const char*)s); + s += ext; +} + +void* build_rmi(MIDI_file* mf, UINT* siz) +{ + UINT sz = 0x14 + align(mf->size, 2); + UINT t_sz = 0; + if (mf->title) + { + t_sz = strlen(mf->title); + if (t_sz) + { + t_sz++; //add null; + sz += 12 + align(t_sz, 2); + } + } + if (mf->rmi_data) + { + sz += align(mf->rmi_size + 8, 2); + } + if (mf->bmp_data) + { + sz += align(mf->bmp_size + 12, 2); + } + + if (mf->pDLSdata) sz += align(mf->DLSsize, 2); + + BYTE* block = (BYTE*)malloc(sz); + BYTE* b_ptr = block; + *(DWORD*)b_ptr = _rv('RIFF'); + b_ptr += 4; + *(DWORD*)b_ptr = sz - 8; + b_ptr += 4; + *(DWORD*)b_ptr = _rv('RMID'); + b_ptr += 4; + *(DWORD*)b_ptr = _rv('data'); + b_ptr += 4; + *(DWORD*)b_ptr = mf->size; + b_ptr += 4; + memcpy(b_ptr, mf->data, mf->size); + b_ptr += align(mf->size, 2); + if (t_sz) + { + *(DWORD*)b_ptr = _rv('DISP'); + b_ptr += 4; + *(DWORD*)b_ptr = t_sz + 4; + b_ptr += 4; + *(DWORD*)b_ptr = 1; + b_ptr += 4; + memcpy(b_ptr, mf->title, t_sz); + b_ptr += align(t_sz, 2); + } + if (mf->rmi_data) + { + *(DWORD*)b_ptr = _rv('LIST'); + b_ptr += 4; + *(DWORD*)b_ptr = mf->rmi_size; + b_ptr += 4; + memcpy(b_ptr, mf->rmi_data, mf->rmi_size); + b_ptr += align(mf->rmi_size, 2); + } + if (mf->bmp_data) + { + *(DWORD*)b_ptr = _rv('DISP'); + b_ptr += 4; + *(DWORD*)b_ptr = mf->bmp_size + 4; + b_ptr += 4; + *(DWORD*)b_ptr = 8; + b_ptr += 4; + memcpy(b_ptr, mf->bmp_data, mf->bmp_size); + b_ptr += align(mf->bmp_size, 2); + } + if (mf->pDLSdata) + { + memcpy(b_ptr, mf->pDLSdata, mf->DLSsize); + b_ptr += align(mf->DLSsize, 2); + } + *siz = sz; + return block; +} + +BOOL SaveFile(HWND w, MIDI_file* mf, BOOL info) +{ + BOOL rmi_only = info; + string tmp; + if (is_local(mf->path) && _strnicmp(mf->path, "partial://", 10)) + { + tmp = mf->path; + if (!info) do_ext(tmp, ".mid"); + } + else + { + info = 0; + file2title(mf->path, tmp); + tmp += ".mid"; + } + if (mf->format > 1) info = 0; //not MID/RMI + + UINT fmt = 0; + BOOL do_gzip = 0; + if (!info) + { + char filter[512] = { 0 }; + OPENFILENAMEA ofn = { sizeof(ofn),0 }; + ofn.hwndOwner = w; + ofn.lpstrFile = tmp.buffer_get(MAX_PATH + 1); + ofn.nMaxFile = MAX_PATH; + ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY; + ofn.lpstrDefExt = ""; + ofn.lpstrFilter = filter; + char* pf = filter, * sf = 0; + int len = 0; +#define APPEND(x) {memcpy(pf,x,len);pf+=len;} + + if (!rmi_only) + { + sf = BuildFilterString(IDS_MIDI_FILES, "MID", &len); + APPEND(sf); + + sf = BuildFilterString(IDS_COMPRESSED_MIDI_FILES, "MIZ", &len); + APPEND(sf); + } + sf = BuildFilterString(IDS_RMI_FILES, "RMI", &len); + APPEND(sf); + + sf = BuildFilterString(IDS_COMPRESSED_RMI_FILES, "MIZ", &len); + APPEND(sf); + +#undef APPEND + * pf = 0; + + if (!GetSaveFileNameA(&ofn)) return 0; + + tmp.buffer_done(); + + fmt = ofn.nFilterIndex - 1; + + do_gzip = fmt & 1; + fmt >>= 1; + + if (rmi_only) fmt = 1; + + if (do_gzip) do_ext(tmp, ".miz"); + else if (fmt == 1) do_ext(tmp, ".rmi"); + else do_ext(tmp, ".mid"); + + + } + else + { + fmt = 1; + const char* p = strrchr(tmp, '.'); + if (p && !_stricmp(p, ".miz")) do_gzip = 1; + } + + { + if (fmt > 1) fmt = 0; + + + BOOL local = 0; + const void* buf = 0; + UINT buf_size = 0; + if (fmt == 0) + { + buf = mf->data; + buf_size = mf->size; + } + else //if (fmt==1) + { + local = 1; + buf = build_rmi(mf, &buf_size); + } + int rv; + + if (do_gzip) + { + rv = SaveAsGZip(tmp, buf, buf_size); + } + else + { + HANDLE f = CreateFileA(tmp, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); + if (f != INVALID_HANDLE_VALUE) + { + DWORD bw = 0; + WriteFile(f, buf, buf_size, &bw, 0); + CloseHandle(f); + rv = 1; + } + else rv = 0; + } + if (local) free((void*)buf); + if (!_stricmp(mf->path, tmp)) + { + mf->format = fmt; + } + if (!rv) + { + char _m[320] = { 0 }; + StringCbPrintfA(_m, sizeof(_m), WASABI_API_LNGSTRING(STRING_WRITE_ERROR_FMT), (const char*)tmp); + MessageBoxA(w, _m, ERROR, MB_ICONERROR); + } + return rv; + } +} +/// <summary> +/// Compress given buffer with GZIP format and saves to given filename +/// </summary> +/// <param name="filename"></param> +/// <param name="buffer"></param> +/// <param name="size"></param> +/// <returns></returns> +int SaveAsGZip(string filename, const void* buffer, size_t size) +{ + void* data; + size_t data_len = size; + int ret = CompressionUtility::CompressAsGZip(buffer, size, &data, data_len); + + if (ret >= 0) + { + try + { + HANDLE f = CreateFileA(filename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); + if (f != INVALID_HANDLE_VALUE) + { + DWORD bw = 0; + WriteFile(f, data, data_len, &bw, 0); + CloseHandle(f); + return 1; + } + } + catch (...) + { + DWORD i = GetLastError(); + return 0; + } + } + + return 0; +} + +#define _pr ((MIDI_file*)(lp)) + +static cfg_struct_t<RECT> cfg_infpos("infpos", -1); + +static UINT inf_x_min = 0x80000000, inf_y_min, inf_c_x, inf_c_y; +static RECT r_trax, r_text; + +static void SetWindowRect(HWND w, RECT* r) +{ + SetWindowPos(w, 0, r->left, r->top, r->right - r->left, r->bottom - r->top, SWP_NOZORDER); +} + +void cGetWindowRect(HWND w, RECT* r) +{ + RECT tr, tr1; + GetWindowRect(w, &tr); + SetWindowPos(w, 0, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOSIZE); + GetWindowRect(w, &tr1); + r->left = tr.left - tr1.left; + r->right = tr.right - tr1.left; + r->top = tr.top - tr1.top; + r->bottom = tr.bottom - tr1.top; + SetWindowRect(w, r); +} + + +#define RB_NUM 3 + +static struct +{ + UINT id, dx, dy; +} +rb_dat[RB_NUM] = +{ + {IDC_SAVE, 0, 0}, + {IDOK, 0, 0}, + {IDC_RMI_CRAP, 0, 0}, +}; + +#define BOTTOM_NUM 8 + +static struct +{ + UINT id; + UINT x, dy; +} +b_dat[BOTTOM_NUM] = +{ + {IDC_STATIC1, 0, 0}, + {IDC_STATIC2, 0, 0}, + {IDC_STATIC3, 0, 0}, + {IDC_TIX, 0, 0}, + {IDC_MS, 0, 0}, + {IDC_FSIZE, 0, 0}, + {IDC_DLS, 0, 0}, + {IDC_LOOP, 0, 0} +}; + +static void OnSize(HWND wnd) +{ + RECT cl, t; + GetClientRect(wnd, &cl); + t.left = r_text.left; + t.right = r_text.right; + t.top = r_text.top; + t.bottom = cl.bottom - (inf_c_y - r_text.bottom); + SetWindowRect(GetDlgItem(wnd, IDC_COPYRIGHT), &t); + t.left = r_trax.left; + t.right = cl.right - (inf_c_x - r_trax.right); + t.top = r_trax.top; + t.bottom = cl.bottom - (inf_c_y - r_trax.bottom); + SetWindowRect(GetDlgItem(wnd, IDC_TRAX), &t); + UINT n; + for (n = 0; n < BOTTOM_NUM; n++) + { + SetWindowPos(GetDlgItem(wnd, b_dat[n].id), 0, b_dat[n].x, cl.bottom - b_dat[n].dy, 0, 0, SWP_NOZORDER | SWP_NOSIZE); + } + for (n = 0; n < RB_NUM; n++) + { + SetWindowPos(GetDlgItem(wnd, rb_dat[n].id), 0, cl.right - rb_dat[n].dx, cl.bottom - rb_dat[n].dy, 0, 0, SWP_NOZORDER | SWP_NOSIZE); + } +} + +BOOL WINAPI InfoProc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp) +{ + switch (msg) + { + case WM_INITDIALOG: + if (inf_x_min == 0x80000000) + { + RECT r; + GetWindowRect(wnd, &r); + inf_x_min = r.right - r.left; + inf_y_min = r.bottom - r.top; + GetClientRect(wnd, &r); + inf_c_x = r.right; + inf_c_y = r.bottom; + cGetWindowRect(GetDlgItem(wnd, IDC_COPYRIGHT), &r_text); + cGetWindowRect(GetDlgItem(wnd, IDC_TRAX), &r_trax); + UINT n; + for (n = 0; n < BOTTOM_NUM; n++) + { + cGetWindowRect(GetDlgItem(wnd, b_dat[n].id), &r); + b_dat[n].x = r.left; + b_dat[n].dy = inf_c_y - r.top; + } + for (n = 0; n < RB_NUM; n++) + { + cGetWindowRect(GetDlgItem(wnd, rb_dat[n].id), &r); + rb_dat[n].dx = inf_c_x - r.left; + rb_dat[n].dy = inf_c_y - r.top; + } + } + if (cfg_infpos.get_val().left != -1) + { + int sx = GetSystemMetrics(SM_CXSCREEN), sy = GetSystemMetrics(SM_CYSCREEN); + if (cfg_infpos.get_val().right > sx) + { + cfg_infpos.get_val().left -= cfg_infpos.get_val().right - sx; + cfg_infpos.get_val().right = sx; + } + if (cfg_infpos.get_val().bottom > sy) + { + cfg_infpos.get_val().top -= cfg_infpos.get_val().bottom - sy; + cfg_infpos.get_val().bottom = sy; + } + if (cfg_infpos.get_val().left < 0) + { + cfg_infpos.get_val().right -= cfg_infpos.get_val().left; + cfg_infpos.get_val().left = 0; + } + if (cfg_infpos.get_val().top < 0) + { + cfg_infpos.get_val().bottom -= cfg_infpos.get_val().top; + cfg_infpos.get_val().top = 0; + } + SetWindowRect(wnd, &cfg_infpos.get_val()); + OnSize(wnd); + } +#if defined(_WIN64) + SetWindowLong(wnd, DWLP_USER, lp); +#else + SetWindowLong(wnd, DWL_USER, lp); +#endif + SetDlgItemTextA(wnd, IDC_COPYRIGHT, _pr->info.copyright); + SetDlgItemInt(wnd, IDC_MS, _pr->len, 0); + SetDlgItemInt(wnd, IDC_TIX, _pr->info.tix, 0); + { + char tmp[128] = { 0 }; + getfmtstring(_pr, tmp); + SetDlgItemTextA(wnd, IDC_FORMAT, tmp); + + HANDLE f = CreateFileA(_pr->path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0); + if (f != INVALID_HANDLE_VALUE) + { + StringCbPrintfA(tmp, sizeof(tmp), WASABI_API_LNGSTRING(STRING_BYTES_FMT), GetFileSize(f, 0)); + CloseHandle(f); + SetDlgItemTextA(wnd, IDC_FSIZE, tmp); + } + } + { + char tmp[1024] = { 0 }; + if (_pr->info.traxnames) + { + HWND lb = GetDlgItem(wnd, IDC_TRAX); + UINT n; + for (n = 0; n < _pr->info.ntrax; n++) + { + StringCbPrintfA(tmp, sizeof(tmp), "(%u) %s", n, (const char*)(_pr->info.traxnames[n])); + SendMessageA(lb, LB_ADDSTRING, 0, (LPARAM)tmp); + } + } + + StringCbPrintfA(tmp, sizeof(tmp), WASABI_API_LNGSTRING(STRING_TRACKS_FMT), _pr->info.ntrax); + SetDlgItemTextA(wnd, IDC_NTRAX, tmp); + if (_pr->title) + { + StringCbPrintfA(tmp, sizeof(tmp), WASABI_API_LNGSTRING(STRING_MIDI_INFO_FMT1), (const char*)_pr->title, (const char*)_pr->path); + } + else + { + StringCbPrintfA(tmp, sizeof(tmp), WASABI_API_LNGSTRING(STRING_MIDI_INFO_FMT2), (const char*)_pr->path); + } + if (_pr->flags & FLAG_INCOMPLETE) StringCbCatA(tmp, sizeof(tmp), WASABI_API_LNGSTRING(STRING_INCOMPLETE)); + SetWindowTextA(wnd, tmp); + } + if (_pr->pDLSdata) EnableWindow(GetDlgItem(wnd, IDC_DLS), 1); + if (_pr->loopstart) EnableWindow(GetDlgItem(wnd, IDC_LOOP), 1); + //if (_pr->rmi_data) EnableWindow(GetDlgItem(wnd,IDC_RMI_CRAP),1); + return 1; + case WM_SIZE: + OnSize(wnd); + break; + case WM_SIZING: + if (lp) + { + RECT* r = (RECT*)lp; + if ((UINT)(r->right - r->left) < inf_x_min) + { + if (wp != WMSZ_LEFT && wp != WMSZ_TOPLEFT && wp != WMSZ_BOTTOMLEFT) + r->right = r->left + inf_x_min; + else r->left = r->right - inf_x_min; + } + if ((UINT)(r->bottom - r->top) < inf_y_min) + { + if (wp != WMSZ_TOP && wp != WMSZ_TOPLEFT && wp != WMSZ_TOPRIGHT) + r->bottom = r->top + inf_y_min; + else r->top = r->bottom - inf_y_min; + } + } + break; + case WM_COMMAND: + if (wp == IDOK || wp == IDCANCEL) + { + EndDialog(wnd, /*changed ? 0 : 1*/0); + } + else if (wp == IDC_SAVE) + { +#if defined(_WIN64) + SaveFile(wnd, (MIDI_file*)GetWindowLong(wnd, DWLP_USER), 0); +#else + SaveFile(wnd, (MIDI_file*)GetWindowLong(wnd, DWL_USER), 0); +#endif + } + else if (wp == IDC_RMI_CRAP) + { +#if defined(_WIN64) + MIDI_file* mf = (MIDI_file*)GetWindowLong(wnd, DWLP_USER); +#else + MIDI_file* mf = (MIDI_file*)GetWindowLong(wnd, DWL_USER); +#endif + show_rmi_info(wnd, mf); + } + break; + case WM_CLOSE: + EndDialog(wnd, /*changed ? 0 : 1*/0); + break; + case WM_DESTROY: + GetWindowRect(wnd, &cfg_infpos.get_val()); + break; + } + return 0; +} +#undef _pr diff --git a/Src/Plugins/Input/in_midi/main.cpp b/Src/Plugins/Input/in_midi/main.cpp new file mode 100644 index 00000000..b9012b56 --- /dev/null +++ b/Src/Plugins/Input/in_midi/main.cpp @@ -0,0 +1,612 @@ +#include "../Agave/Language/api_language.h" +#include "main.h" +#include <math.h> +#include "resource.h" +#include "../Winamp/in2.h" +#include "../Winamp/wa_ipc.h" + +extern In_Module mod; +void get_temp_file(char* fn) +{ + static char tmp_path[MAX_PATH]; + if (!tmp_path[0]) GetTempPathA(MAX_PATH,tmp_path); + static DWORD num; + if (num==0) num=GetTickCount(); + wsprintfA(fn,"%sasdf%x.tmp",tmp_path,num++); +} + +void file2title(const char* f,string& t) +{ + const char* p1=strrchr(f,'\\'),*p2=strrchr(f,':'),*p3=strrchr(f,'/'); + if (p2>p1) p1=p2; + if (p3>p1) p1=p3; + if (p1) p1++; + else p1=(char*)f; + t=p1; + p1=strrchr(t,'.'); + if (p1) t.truncate(p1-(const char*)t); +} + +static char* exts[]={"MID","MIDI","RMI","KAR","HMP","HMI","XMI","MSS","MUS","CMF","GMD","MIDS","MIZ","HMZ"}; +#define N_EXTS tabsize(exts) +static char is_def[N_EXTS]={1,1,1,1,0,0,0,0,0,0,0,0,1,0}; + +static int get_def_exts() +{ + int ret=0; + int n; + for(n=0;n<N_EXTS;n++) + { + if (is_def[n]) ret|=1<<n; + } + return ret; +} + +cfg_int cfg_ext_mask("ext_mask",get_def_exts()); + +static char d_smf[128]; +static char d_clo[128]; +static char d_cmp[128]; + +int ext_descs[N_EXTS]={STRING_FILES_SMF,STRING_FILES_SMF,STRING_FILES_SMF,STRING_FILES_SMF,STRING_FILES_CLONE,STRING_FILES_CLONE,STRING_FILES_CLONE,STRING_FILES_CLONE,STRING_FILES_CLONE,STRING_FILES_CLONE,STRING_FILES_CLONE,STRING_FILES_CLONE,STRING_FILES_COMPRESSED,STRING_FILES_COMPRESSED}; + +int MIDI_core::FileTypes_GetNum() {return N_EXTS;} +const char * MIDI_core::FileTypes_GetExtension(int n) {return exts[n];} +char * MIDI_core::FileTypes_GetDescription(int n) { + char* s = d_smf; + if(ext_descs[n] == STRING_FILES_SMF) { + if(!d_smf[0]) { + WASABI_API_LNGSTRING_BUF(ext_descs[n],d_smf,128); + } + s = d_smf; + } + else if(ext_descs[n] == STRING_FILES_CLONE) { + if(!d_clo[0]) { + WASABI_API_LNGSTRING_BUF(ext_descs[n],d_clo,128); + } + s = d_clo; + } + else if(ext_descs[n] == STRING_FILES_COMPRESSED) { + if(!d_cmp[0]) { + WASABI_API_LNGSTRING_BUF(ext_descs[n],d_cmp,128); + } + s = d_cmp; + } + return s; +} + +static int isourext(const char* ext) +{ + UINT n; + for(n=0;n<N_EXTS;n++) + { + if ((cfg_ext_mask&(1<<n)) && !_stricmp(ext,exts[n])) return 1; + } + return 0; +} + +int MIDI_core::IsOurFile(const char *fn) +{ + const char* p=strrchr(fn,'.'); + if (p) + { + if (isourext(p+1)) return 1; + } + + return 0; +} + +extern UINT volmode_detect(); + +static BOOL CALLBACK KarProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp); + +int MIDI_core::Init() +{ + theFile=0; + data_src=0; + plr=0; + eof=0; + mix_dev=0;mix_idx=0; + kwnd=0; + kmap=0; + kmap_size=0;kmap_ptr=0; + kmap_data=0; + format_srate=0;format_nch=0;format_bps=0; + + device=MIDI_driver::find_device(cfg_driver,cfg_device); + if (!device) return 0; + use_out=device->has_output() || (cfg_smp && cfg_sampout); + use_smp=cfg_smp && !device->has_output(); + + if (cfg_volmode>2) volmod=cfg_volmode-1; + else if (cfg_volmode==2) volmod = device->volctrl_happy() ? 1 : volmode_detect()+2; + else volmod=cfg_volmode; + + if (volmod>1) + { + UINT idx=volmod-2; + UINT id=0; + UINT n_devz=mixerGetNumDevs(); + UINT dev=0; + BOOL found=0; + MIXERLINE ml; + while(dev<n_devz) + { + mixerGetID((HMIXEROBJ)dev,&id,MIXER_OBJECTF_MIXER); + + ZeroMemory(&ml,sizeof(ml)); + ml.cbStruct=sizeof(ml); + ml.dwComponentType=MIXERLINE_COMPONENTTYPE_DST_SPEAKERS; + + mixerGetLineInfo((HMIXEROBJ)id,&ml,MIXER_GETLINEINFOF_COMPONENTTYPE|MIXER_OBJECTF_MIXER); + + if (idx<ml.cConnections) + { + found=1; + break; + } + idx-=ml.cConnections; + dev++; + } + if (found) + { + mix_dev=id; + mix_idx=idx; + } + else + { + volmod=0; + } + } + return 1; +} + +CStream * sampling_create(int srate,int nch,int bps); + +cfg_int cfg_lyrics("lyrics",1); + +int MIDI_core::OpenFile(MIDI_file * file) +{ + +#ifdef USE_LOG + log_write("MIDI_core::Open()"); +#endif + + if (!file) return 0; + + format_srate=device->has_freq() ? cfg_freq : 44100; + format_bps=16; + format_nch=2; + + + + theFile=file->AddRef(); +#ifdef USE_LOG + log_write("file loaded"); +#endif + plr=0; + + + if (use_smp) + { +#ifdef USE_LOG + log_write("starting sampling"); +#endif + format_srate=cfg_wavein_sr; + format_bps=cfg_wavein_bps; + format_nch=cfg_wavein_ch; + data_src=sampling_create(format_srate,format_nch,format_bps); + } + + plr=device->create(); + + if (plr) + { +#ifdef USE_LOG + if (data_src) log_write("got PCM data source"); + + log_write("playback started"); +#endif + + if (cfg_lyrics) + { + kmap=kmap_create(theFile,1,&kmap_size,&kmap_data); + if (kmap) + { + kwnd=WASABI_API_CREATEDIALOGPARAMW(IDD_LYRICS,MIDI_callback::GetMainWindow(),KarProc,0); + free(kmap_data); kmap_data=0;//not needed anymore, used only on initdialog to setdlgitemtext + } + } + + return 1; + } + else + { + if (data_src) {delete data_src;data_src=0;} + + theFile->Free(); + theFile=0; + + return 0; + } +} + +int MIDI_core::GetSamples(void *sample_buffer, int bytes, char *killswitch) +{ +#ifdef USE_LOG + log_write("GetSamples"); +#endif + if (data_src) + { + return data_src->ReadData(sample_buffer,bytes,(bool*)killswitch); + } + else return 0; +} + +void MIDI_core::update_vol() +{ + MIXERLINE ml; + ZeroMemory(&ml,sizeof(ml)); + ml.cbStruct=sizeof(ml); + ml.dwSource=mix_idx; + mixerGetLineInfo((HMIXEROBJ)mix_dev,&ml,MIXER_GETLINEINFOF_SOURCE|MIXER_OBJECTF_MIXER); + + MIXERLINECONTROLS cs; + MIXERCONTROL c; + ZeroMemory(&cs,sizeof(cs)); + cs.cbStruct=sizeof(cs); + cs.cControls=1; + cs.dwLineID=ml.dwLineID; + cs.dwControlType=MIXERCONTROL_CONTROLTYPE_VOLUME; + cs.cbmxctrl=sizeof(c); + cs.pamxctrl=&c; + ZeroMemory(&c,sizeof(c)); + c.cbStruct=sizeof(c); + + if (!mixerGetLineControls((HMIXEROBJ)mix_dev,&cs,MIXER_OBJECTF_MIXER|MIXER_GETLINECONTROLSF_ONEBYTYPE)) + { + DWORD val; + if (cfg_logvol) + { + double _vol=volume>0 ? 20*log10((double)volume/255.0) : -60.0;//in negative db + _vol=_vol/60.0+1; + if (_vol<0) _vol=0; + val=c.Bounds.dwMinimum + (int)( _vol * (double)(c.Bounds.dwMaximum-c.Bounds.dwMinimum) ); + } + else val=c.Bounds.dwMinimum + volume * (c.Bounds.dwMaximum-c.Bounds.dwMinimum) / 255; + if (ml.cChannels==1) + { + MIXERCONTROLDETAILS_UNSIGNED ds={val}; + MIXERCONTROLDETAILS d; + d.cbStruct=sizeof(d); + d.dwControlID=c.dwControlID; + d.cChannels=1; + d.cMultipleItems=0; + d.cbDetails=sizeof(ds); + d.paDetails=&ds; + mixerSetControlDetails((HMIXEROBJ)mix_dev,&d,MIXER_SETCONTROLDETAILSF_VALUE|MIXER_OBJECTF_MIXER); + } + else if (ml.cChannels<16) + { + MIXERCONTROLDETAILS_UNSIGNED ds[16]; + UINT n; + for(n=0;n<16;n++) ds[n].dwValue=val; + if (pan<0) + { + ds[1].dwValue=ds[1].dwValue*(128+pan)>>7; + } + else + { + ds[0].dwValue=ds[0].dwValue*(128-pan)>>7; + } + MIXERCONTROLDETAILS d; + d.cbStruct=sizeof(d); + d.dwControlID=c.dwControlID; + d.cChannels=ml.cChannels; + d.cMultipleItems=0; + d.cbDetails=sizeof(ds[0]); + d.paDetails=&ds; + mixerSetControlDetails((HMIXEROBJ)mix_dev,&d,MIXER_SETCONTROLDETAILSF_VALUE|MIXER_OBJECTF_MIXER); + } + } +/* + ZeroMemory(&cs,sizeof(cs)); + cs.cbStruct=sizeof(cs); + cs.cControls=1; + cs.dwLineID=ml.dwLineID; + cs.dwControlType=MIXERCONTROL_CONTROLTYPE_PAN; + cs.cbmxctrl=sizeof(c); + cs.pamxctrl=&c; + ZeroMemory(&c,sizeof(c)); + c.cbStruct=sizeof(c); + + if (!mixerGetLineControls((HMIXEROBJ)mix_dev,&cs,MIXER_OBJECTF_MIXER|MIXER_GETLINECONTROLSF_ONEBYTYPE)) + { + MIXERCONTROLDETAILS_SIGNED ds={c.Bounds.lMinimum + (pan+128) * (c.Bounds.lMaximum-c.Bounds.lMinimum) / 255}; + MIXERCONTROLDETAILS d; + d.cbStruct=sizeof(d); + d.dwControlID=c.dwControlID; + d.cbDetails=sizeof(ds); + d.cChannels=ml.cChannels; + d.cMultipleItems=c.cMultipleItems; + d.paDetails=&ds; + mixerSetControlDetails((HMIXEROBJ)mix_dev,&d,MIXER_SETCONTROLDETAILSF_VALUE|MIXER_OBJECTF_MIXER); + }*/ +} + +int MIDI_core::SetVolume(int _volume) +{ + volume=_volume; + if (volmod==0) return 0; + else + { + if (volmod==1) + { + if ((use_out && !use_smp) || !plr) + { + return 0; + } + else + { + return plr->setvol(player_getVol()); + } + } + update_vol(); + return 1; + } +} + +int MIDI_core::SetPan(int _pan) +{ + pan=_pan; + if (volmod==0) return 0; + else + { + if (volmod==1) + { + if (plr) return plr->setpan(player_getPan()); + else return 0; + } + else + { + update_vol(); + return 1; + } + } +} + +int MIDI_core::SetPosition(int pos) +{ + if (!plr) return 0; + if (!plr->settime(pos)) return 0; + sync.enter(); + kmap_ptr=0; + LeaveCriticalSection(&sync); + + if (data_src) data_src->Flush(); + return 1; +} + +void MIDI_core::Pause(int pause) +{ + if (plr) + { + if (pause) plr->pause(); + else plr->unpause(); + } + if (data_src) data_src->Pause(pause); +} + +int MIDI_core::GetPosition(void) +{ + int i=0; + if (plr) + { + i=plr->gettime(); + if (i<0) i=0; + } + return i; +} + +int MIDI_core::GetLength(void) +{ + if (theFile) return theFile->len; + else return -1; +} + +void MIDI_core::Close() +{ +#ifdef USE_LOG + log_write("shutting down MIDI_core"); +#endif + if (plr) {delete plr;plr=0;} + if (data_src) {delete data_src;data_src=0;} + if (kwnd) {DestroyWindow(kwnd);kwnd=0;} + if (kmap) {free(kmap);kmap=0;} + if (theFile) {theFile->Free();theFile=0;} +} + +void MIDI_core::Eof() +{ + eof=1; + if (data_src) + data_src->Eof(); + else + MIDI_callback::NotifyEOF(); +} + + +static char INI_FILE[MAX_PATH]; + +void MIDI_core::GlobalInit() +{ +#ifdef USE_LOG + log_start(); + log_write("initializing"); + log_write(NAME); +#endif + + char *p; + if (mod.hMainWindow && + (p = (char *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETINIFILE)) + && p!= (char *)1) + { + strcpy(INI_FILE, p); + } + else + { + GetModuleFileNameA(NULL,INI_FILE,sizeof(INI_FILE)); + p = INI_FILE + strlen(INI_FILE); + while (p >= INI_FILE && *p != '.') p--; + strcpy(++p,"ini"); + } + cfg_var::config_read(INI_FILE,"in_midi"); +} + +void MIDI_core::GlobalQuit() +{ + MIDI_driver::shutdown(); + log_quit(); +} + +void MIDI_core::WriteConfig() +{ + cfg_var::config_write(INI_FILE,"in_midi"); +} + +void MIDI_core::MM_error(DWORD code) +{ + string temp; + if (!mciGetErrorStringA(code,string_buffer_a(temp,256),256)) + { + temp=WASABI_API_LNGSTRING(STRING_UNKNOWN_MMSYSTEM); + } + MIDI_callback::Error(temp); + +} + + +static void fix_size(HWND wnd) +{ + RECT r; + GetClientRect(wnd,&r); + SetWindowPos(GetDlgItem(wnd,IDC_BLAH),0,0,0,r.right,r.bottom,SWP_NOZORDER|SWP_NOACTIVATE); +} + +static cfg_struct_t<RECT> cfg_lyrics_pos("lyrics_pos",-1); + +static void SetWindowRect(HWND w,RECT* r) +{ + SetWindowPos(w,0,r->left,r->top,r->right-r->left,r->bottom-r->top,SWP_NOZORDER); +} + + +static cfg_int cfg_lyrics_min("lyrics_min",0),cfg_lyrics_max("lyrics_max",0); + +BOOL CALLBACK MIDI_core::KarProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) +{ + switch(msg) + { + case WM_INITDIALOG: + SetDlgItemTextA(wnd,IDC_BLAH,MIDI_core::kmap_data); + + if (cfg_lyrics_pos.get_val().left!=-1) + { + int sx=GetSystemMetrics(SM_CXSCREEN),sy=GetSystemMetrics(SM_CYSCREEN); + if (cfg_lyrics_pos.get_val().right>sx) + { + cfg_lyrics_pos.get_val().left-=cfg_lyrics_pos.get_val().right-sx; + cfg_lyrics_pos.get_val().right=sx; + } + if (cfg_lyrics_pos.get_val().bottom>sy) + { + cfg_lyrics_pos.get_val().top-=cfg_lyrics_pos.get_val().bottom-sy; + cfg_lyrics_pos.get_val().bottom=sy; + } + if (cfg_lyrics_pos.get_val().left<0) + { + cfg_lyrics_pos.get_val().right-=cfg_lyrics_pos.get_val().left; + cfg_lyrics_pos.get_val().left=0; + } + if (cfg_lyrics_pos.get_val().top<0) + { + cfg_lyrics_pos.get_val().bottom-=cfg_lyrics_pos.get_val().top; + cfg_lyrics_pos.get_val().top=0; + } + SetWindowRect(wnd,&cfg_lyrics_pos.get_val()); + } + if (cfg_lyrics_min) + { + ShowWindow(wnd,SW_MINIMIZE); + } + else if (cfg_lyrics_max) + { + ShowWindow(wnd,SW_MAXIMIZE); + } + fix_size(wnd); + SetTimer(wnd,1,100,0); + return 1; + case WM_TIMER: + { + sync.enter(); + UINT time=GetPosition(); + KAR_ENTRY * set=0; + UINT ptr=kmap_ptr; + while(ptr<kmap_size && kmap[ptr].time<time) + { + if (!kmap[ptr].foo) set=&kmap[ptr]; + ptr++; + } + kmap_ptr=ptr; + sync.leave(); + if (set) + { + SendDlgItemMessage(wnd,IDC_BLAH,EM_SETSEL,set->start,set->end); + } + + } + break; + case WM_DESTROY: + KillTimer(wnd,1); + kwnd=0; + GetWindowRect(wnd,&cfg_lyrics_pos.get_val()); + cfg_lyrics_max=!!IsZoomed(wnd); + cfg_lyrics_min=!!IsIconic(wnd); + break; + case WM_CLOSE: + + cfg_lyrics=0; + + if (!((int)cfg_bugged & BUGGED_BLAH)) + { + char title[32] = {0}; + cfg_bugged = (int)cfg_bugged | BUGGED_BLAH; + MessageBoxA(wnd,WASABI_API_LNGSTRING(IDS_TO_ENABLE_LYRICS_DISPLAY), + WASABI_API_LNGSTRING_BUF(IDS_INFORMATION,title,32),MB_ICONINFORMATION); + } + DestroyWindow(wnd); + break; + case WM_SIZE: + fix_size(wnd); + break; + } + return 0; +} + +//MIDI_core static crap +bool MIDI_core::use_out; +MIDI_file* MIDI_core::theFile; +CStream* MIDI_core::data_src; +player_base* MIDI_core::plr; +int MIDI_core::format_srate,MIDI_core::format_nch,MIDI_core::format_bps; +int MIDI_core::volume=255,MIDI_core::pan=0; +bool MIDI_core::eof; +UINT MIDI_core::volmod; +UINT MIDI_core::mix_dev,MIDI_core::mix_idx; +MIDI_device * MIDI_core::device; +bool MIDI_core::use_smp; +HWND MIDI_core::kwnd; +KAR_ENTRY* MIDI_core::kmap; +UINT MIDI_core::kmap_size,MIDI_core::kmap_ptr; +char * MIDI_core::kmap_data; +critical_section MIDI_core::sync;
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/main.h b/Src/Plugins/Input/in_midi/main.h new file mode 100644 index 00000000..ee73606e --- /dev/null +++ b/Src/Plugins/Input/in_midi/main.h @@ -0,0 +1,230 @@ +#ifndef STRICT +#define STRICT +#endif +#include "../Agave/Language/api_language.h" +#include <windows.h> +#include <malloc.h> + +#ifndef NOVTABLE +#define NOVTABLE _declspec(novtable) +#endif + +#include <mmsystem.h> +#include "core_api.h" +#include "../pfc/string_unicode.h" +#include "locale.h" + +#define VER L"3.57" + +#include "utils.h" + +//#define USE_LOG + +#ifdef USE_LOG +void log_write(char*); +void log_start(); +void log_quit(); +#else +#define log_write(X) +#define log_start() +#define log_quit() +#endif + +class CStream; +struct CTempoMap; +struct CSysexMap; + +class player_base; + +class NOVTABLE MIDI_device +{ + friend class MIDI_driver; +private: + MIDI_device * next; + MIDI_driver * driver; + string_w dev_name,dev_info; +protected: + void set_name(const wchar_t * src) {dev_name=src;} + void set_info(const wchar_t * src) {dev_info=src;} +public: + //override me + virtual player_base * create()=0; + virtual GUID get_guid()=0; + virtual ~MIDI_device() {}; + virtual bool is_default() {return 0;} + virtual bool has_output() {return 0;} + virtual bool has_dls() {return 0;} + virtual bool has_freq() {return 0;} + virtual bool volctrl_happy() {return 0;} + + const wchar_t * get_name() {return dev_name;} + const wchar_t * get_info() {return dev_info;} + + MIDI_driver * get_driver() {return driver;} +}; + +class NOVTABLE MIDI_driver//ONLY for static objects !!!!! +{ +private: + static MIDI_driver * driver_list; + MIDI_driver * next; + MIDI_device * device_list; + bool inited; + void init() {if (!inited) {do_init();inited=1;};} + void deinit() {if (inited) {do_deinit();reset_devices();inited=0;};} +protected: + MIDI_driver(); + ~MIDI_driver(); + + void reset_devices(); + + void add_device(MIDI_device * dev);//call this to add new device + + //override me + virtual void do_init() {}; + virtual void do_deinit() {} + +public: + + static MIDI_driver * driver_enumerate(int n); + static int driver_count(); + + MIDI_device * device_enumerate(int n); + int device_count(); + + static MIDI_device * find_device(GUID guid_driver,GUID guid_device); + static MIDI_driver * find_driver(GUID guid_driver); + static MIDI_device * find_device_default(); + + static void shutdown(); + + //override me + virtual const wchar_t * get_name()=0; + virtual GUID get_guid()=0; + virtual bool is_default() {return 0;} +}; + + +#include "midifile.h" + +class NOVTABLE player_base +{ +public: + virtual ~player_base() {} + virtual int gettime()=0; + virtual int settime(int)=0; + virtual void pause()=0; + virtual void unpause()=0; + virtual int setvol(int) {return 0;}; + virtual int setpan(int) {return 0;}; +}; + +class MIDI_core +{ +public: + static int Init(); + static int UsesOutput() {return use_out;} + static int OpenFile(MIDI_file * file); + static void Close(); + static int GetSamples(void *sample_buffer, int bytes, char *killswitch); + static void GetPCM(int * srate,int * nch,int * bps) {*srate=format_srate;*nch=format_nch;*bps=format_bps;} + static int SetPosition(int); + static void Pause(int pause); + static int GetPosition(void); + static int GetLength(void); + static void Eof(); + + static int SetVolume(int volume); + static int SetPan(int pan); + //setvolune/setpan safe to call at any moment + + static int player_getVol() {return volume;} + static int player_getPan() {return pan;} + + static inline void player_setSource(CStream *s) {data_src=s;} + + + static void MM_error(DWORD code); + + + + static inline MIDI_file * getFile() {return theFile;} + static inline MIDI_device * getDevice() {return device;} + static inline bool HavePCM() {return !!data_src;} + static inline bool HavePlayer() {return !!plr;} + + static int IsOurFile(const char *fn); + + static void GlobalInit(); + static void GlobalQuit(); + static int Config(HWND wnd); + static void WriteConfig(); + + static int FileTypes_GetNum(); + static const char * FileTypes_GetExtension(int); + static char * FileTypes_GetDescription(int); + +private: + static BOOL CALLBACK KarProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp); + static void update_vol(); + + static bool use_out,use_smp; + static MIDI_file* theFile; + static CStream* data_src; + static player_base* plr; + static int format_srate,format_nch,format_bps; + static int volume,pan; + static bool eof; + static UINT volmod; + static UINT mix_dev,mix_idx; + + static HWND kwnd; + static KAR_ENTRY *kmap; + static UINT kmap_size,kmap_ptr; + static char * kmap_data; + static critical_section sync; + + static MIDI_device * device; +}; + +namespace MIDI_callback //per-winamp implementations +{ + HWND GetMainWindow(); + HINSTANCE GetInstance(); + void NotifyEOF(); + void Error(const char *); + void Idle(int ms=0); +}; + +//#pragma warning(disable:4800) + +void get_temp_file(char* fn); + +extern cfg_int cfg_hardware_reset; +extern cfg_int cfg_smp,cfg_reverb,cfg_chorus,cfg_nosysex; +extern cfg_int cfg_sampout,cfg_dm_imm; +extern cfg_int cfg_loop_type,cfg_loop_count,cfg_loop_infinite; +extern cfg_int cfg_wavein_dev,cfg_wavein_sr,cfg_wavein_ch,cfg_wavein_bps,cfg_wavein_src; +extern cfg_int cfg_ctrl_x,cfg_ctrl_y; +extern cfg_int cfg_ext_mask; +extern cfg_string cfg_extra_exts; +extern cfg_int cfg_volmode; +extern cfg_int cfg_recover_tracks; +extern cfg_int cfg_quick_seek; +extern cfg_int cfg_rmi_def; +extern cfg_int cfg_logvol; +extern cfg_struct_t<GUID> cfg_driver,cfg_device; +extern cfg_int cfg_playback_mode; +extern cfg_int cfg_eof_delay; +extern cfg_int cfg_bugged; +extern cfg_int cfg_freq; +extern cfg_int cfg_cur_tab; + +enum{BUGGED_BLAH=0x10}; + +extern sysex_table cfg_sysex_table; + +void ReleaseObject(IUnknown* o); + +#include "in2.h" +extern In_Module mod;
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/midi_driver.cpp b/Src/Plugins/Input/in_midi/midi_driver.cpp new file mode 100644 index 00000000..1fd63d15 --- /dev/null +++ b/Src/Plugins/Input/in_midi/midi_driver.cpp @@ -0,0 +1,121 @@ +#include "main.h" + +MIDI_driver * MIDI_driver::driver_list=0; + +MIDI_driver::MIDI_driver() +{ + next=driver_list; + driver_list=this; + inited=0; + device_list=0; +} + +void MIDI_driver::reset_devices() +{ + while(device_list) + { + MIDI_device * ptr = device_list->next; + delete device_list; + device_list = ptr; + } +} + +MIDI_driver::~MIDI_driver() +{ + reset_devices(); +} + + +void MIDI_driver::add_device(MIDI_device * dev) +{ + MIDI_device **ptr = &device_list; + while(ptr && *ptr) ptr = &(*ptr)->next; + *ptr=dev; + dev->next=0; + dev->driver=this; +} + +MIDI_driver * MIDI_driver::driver_enumerate(int n) +{ + MIDI_driver * ptr = driver_list; + while(ptr && n>0) {ptr=ptr->next;n--;} + return ptr; +} + +int MIDI_driver::driver_count() +{ + int n=0; + MIDI_driver * ptr = driver_list; + while(ptr) {ptr=ptr->next;n++;} + return n; +} + +MIDI_device * MIDI_driver::device_enumerate(int n) +{ + init(); + MIDI_device * ptr = device_list; + while(ptr && n>0) {ptr=ptr->next;n--;} + return ptr; +} + +int MIDI_driver::device_count() +{ + init(); + int n=0; + MIDI_device * ptr = device_list; + while(ptr) {ptr=ptr->next;n++;} + return n; +} + +MIDI_device * MIDI_driver::find_device(GUID guid_driver,GUID guid_device) +{ + MIDI_driver * driver_ptr = find_driver(guid_driver); + if (!driver_ptr) return 0; + MIDI_device * device_ptr; + int idx=0; + while(device_ptr = driver_ptr->device_enumerate(idx++)) + { + if (device_ptr->get_guid()==guid_device) return device_ptr; + } + return 0; +} + +MIDI_driver * MIDI_driver::find_driver(GUID guid_driver) +{ + MIDI_driver * driver_ptr = driver_list; + while(driver_ptr) + { + if (driver_ptr->get_guid()==guid_driver) break; + driver_ptr = driver_ptr->next; + } + return driver_ptr; +} + +MIDI_device * MIDI_driver::find_device_default() +{ + MIDI_driver * driver_ptr = driver_list; + while(driver_ptr) + { + if (driver_ptr->is_default()) + { + MIDI_device * device_ptr; + int idx=0; + while(device_ptr = driver_ptr->device_enumerate(idx++)) + { + if (device_ptr->is_default()) return device_ptr; + } + } + driver_ptr = driver_ptr->next; + } + return 0; +} + +void MIDI_driver::shutdown() +{ + MIDI_driver * driver_ptr = driver_list; + while(driver_ptr) + { + driver_ptr->deinit(); + driver_ptr = driver_ptr->next; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/midifile.cpp b/Src/Plugins/Input/in_midi/midifile.cpp new file mode 100644 index 00000000..b7bc7f0d --- /dev/null +++ b/Src/Plugins/Input/in_midi/midifile.cpp @@ -0,0 +1,603 @@ +#include "main.h" +#include <intsafe.h> + +//#define HUNT_LEAKS +#ifdef MF_USE_DMCRAP +extern IDirectMusicCollection *pCDLS; +#endif + +#ifdef HUNT_LEAKS +static UINT n_files; +#endif + +MIDI_file::MIDI_file(const char * fn) : path(fn) +{ + flags=0; + format=0; + len=0;tix=0; + size=0; + data=0; +#ifdef MF_USE_DMCRAP + pSeg=0; + pDLS=0; + pDLSdata=0; + DLSsize=0; +#endif + info.fmt=info.ntrax=info.tix=0; + info.channels=0; + info.e_type=0; + loopstart=0;loopend=0; + loopstart_t=0; + rmi_data=0; + rmi_size=0; + bmp_data=0; + bmp_size=0; + kar_track=0; + tmap=0; + smap=0; +#ifdef HUNT_LEAKS + n_files++; +#endif + refcount=1; + info.traxnames=0; +} + +#ifdef MF_USE_DMCRAP +extern IDirectMusicCollection *pCDLS; +extern IDirectMusicLoader* pLoader; +#endif + + +static bool is_gmd(const BYTE* b,size_t s) +{ + return s>12 && *(DWORD*)b==_rv('MIDI') && *(DWORD*)(b+8)==_rv('MDpg'); +} + +static bool is_hmi(const BYTE* b,size_t s) +{ + return s>12 && *(DWORD*)b==_rv('HMI-') && *(DWORD*)(b+4)==_rv('MIDI') && *(DWORD*)(b+8)==_rv('SONG'); +} + +static bool is_hmp(const BYTE* b,size_t s) +{ + if (s>8 && ((DWORD*)b)[0]==_rv('HMIM') && (((DWORD*)b)[1]==_rv('IDIP') || ((DWORD*)b)[1]==_rv('IDIR')) ) + { + //DWORD d=*(DWORD*)(b+0x30); + //return (d<0x40 && d); + return 1; + } + else return 0; +} + +static bool is_xmidi(const BYTE* b,size_t s) +{ + return s>0x20 && *(DWORD*)b==_rv('FORM') && *(DWORD*)(b+8)==_rv('XDIR') && *(DWORD*)(b+0x1e)==_rv('XMID'); +} + +static bool is_rmi(const BYTE* b,size_t s) +{ + return s>20+8+6+8 && *(DWORD*)b==_rv('RIFF') && *(DWORD*)(b+8)==_rv('RMID') && *(DWORD*)(b+12)==_rv('data'); +} + +static bool is_midi(const BYTE* b,size_t s) +{ + return s>8+6+8 && *(DWORD*)b==_rv('MThd') && *(DWORD*)(b+4)==0x06000000 && *(DWORD*)(b+14)==_rv('MTrk'); +} + +static bool is_midi_scan(const BYTE* b,size_t s) +{ + int x,m=s; + if (m>256) m=256; + m-=8+6+8; + for(x=0;x<m;x++) + if (is_midi(b+x,s-x)) return 1; + return 0; +} + +#define REM (unsigned int)(sz-ptr) + +static bool load_midi_fix(MIDI_file* mf,const BYTE* buf,size_t sz,int n_track,size_t p_ofs) +{ + if (!cfg_recover_tracks) + return 0; + + size_t malloc_sz; + if (SizeTAdd(sz, 0x10, &malloc_sz) != S_OK) + return false; + + BYTE* outbuf=(BYTE*)malloc(malloc_sz); + if (!outbuf) return 0; + size_t ptr=p_ofs; + size_t bp=ptr; + BYTE lc=0; + while(1) + { + bp=ptr; + if (REM<4) break; + while(buf[ptr]&0x80) + { + if (ptr==bp+4) break; + ptr++; + } + ptr++; + if (REM<3) break; + BYTE b=buf[ptr]; + if (b==0xFF) + { + ptr+=2; + if (REM<4) break; + unsigned int d; + unsigned int l=DecodeDelta(buf+ptr,&d, sz-ptr); + if (l+d>REM) break; + ptr+=l+d; + } + else if (b==0xF0) + { + ptr++; + if (REM<4) break; + unsigned int d; + unsigned int l=DecodeDelta(buf+ptr,&d, sz-ptr); + if (l+d>REM) break; + ptr+=l+d; + } + else + { + if (b&0x80) + { + lc=b&0xF0; + if (lc==0xF0) break; + ptr++; + } + else if (!lc) break; + if (lc==0xC0 || lc==0xD0) ptr++; + else ptr+=2; + } + } + memcpy(outbuf,buf,ptr); + ptr=bp; + outbuf[ptr++]=0; + outbuf[ptr++]=0xFF; + outbuf[ptr++]=0x2F; + outbuf[ptr++]=0; + *(DWORD*)(outbuf+p_ofs-4)=rev32(ptr-p_ofs); + mf->data=outbuf; + mf->size=ptr; + + return 1; +} + +#undef REM + +static bool load_midi(MIDI_file* mf,const BYTE* buf,size_t sz) +{ + int trax=rev16(*(WORD*)(buf+4+4+2)); + size_t ofs=6+8; + int n; + for(n=0;n<trax;n++) + { + if (ofs>(sz-12) || *(DWORD*)(buf+ofs)!=_rv('MTrk')) + { + mf->flags|=FLAG_INCOMPLETE; + *(WORD*)(buf+4+4+2)=rev16(n); + sz=ofs; + break; + } + + if (SizeTAdd(ofs, 8, &ofs) != S_OK) + return false; + + size_t p_ofs=ofs; + DWORD next = rev32(*(DWORD*)(buf+ofs-4)); + if (SizeTAdd(ofs, next, &ofs) != S_OK) + return false; + + if (ofs>sz) + { + mf->flags|=FLAG_INCOMPLETE; + *(WORD*)(buf+4+4+2)=rev16(n+1); + if (!load_midi_fix(mf,buf,sz,n,p_ofs)) + { + *(WORD*)(buf+4+4+2)=rev16(n); + sz=p_ofs-8; + break; + } + else return 1; + } + } + + BYTE * out = (BYTE*)malloc(sz); + if (!out) + return 0; + memcpy(out,buf,sz); + mf->data=out; + mf->size=sz; + return 1; +} + +static bool load_gmd(MIDI_file* mf,const BYTE* buf,size_t sz) +{ + if (sz<=0x10) return 0; + DWORD s=rev32(*(DWORD*)(buf+4)); + if ((sz-8)<s) return 0; + DWORD ofs=rev32(*(DWORD*)(buf+12))+0x10; + s-=ofs; + BYTE * out=(BYTE*)malloc(s); + if (!out) return 0; + mf->size=s; + memcpy(out,buf+ofs,s); + mf->data=out; + return 1; +} + +#ifdef MF_USE_DMCRAP +void ReleaseObject(IUnknown* o); +#endif + +static bool load_rmi(MIDI_file* mf,const BYTE* source,size_t source_size) +{ + if (source_size < 8) + return 0; + + unsigned int sx=*(DWORD*)(source+4); + size_t _p=0; + BYTE * out; + if (sx>source_size-8) goto _er; + mf->size=*(DWORD*)(source+16); + if (mf->size+20>source_size) goto _er; + out=(BYTE*)malloc(mf->size); + if (!out) goto _er; + memcpy(out,source+20,mf->size); + mf->data = out; + + _p=20+mf->size; + if (_p&1) _p++; + while(_p<source_size) + { + if (! mf->bmp_data && *(DWORD*)(source+_p)==_rv('DISP') && *(DWORD*)(source+_p+8)==8)//bitmap + { + DWORD s=*(DWORD*)(source+_p+4)-4; + void * r=malloc(s); + if (r) + { + memcpy(r,source+_p+12,s); + mf->bmp_size=s; + mf->bmp_data=r; + } + } + else if (! mf->title && *(DWORD*)(source+_p)==_rv('DISP') && *(DWORD*)(source+_p+8)==1) + { + DWORD s=*(DWORD*)(source+_p+4)-4; + char * src=(char*)(source+_p+12); //remove eol's + char * dst=mf->title.buffer_get(s+1); + char * src_b=src; + while(src && *src && (UINT)(src-src_b)<s) + { + if (*src!=10 && *src!=13) *(dst++)=*src; + src++; + } + *dst=0; + mf->title.buffer_done(); + } + else if (! mf->rmi_data && *(DWORD*)(source+_p)==_rv('LIST') && *(DWORD*)(source+_p+8)==_rv('INFO')) + { + DWORD s=*(DWORD*)(source+_p+4); + void * r=malloc(s); + if (r) + { + memcpy(r,source+_p+8,s); + mf->rmi_size=s; + mf->rmi_data=r; + } + } +#ifdef MF_USE_DMCRAP + else if (!mf->pDLSdata && *(DWORD*)(source+_p)==_rv('RIFF') && *(DWORD*)(source+_p+8)==_rv('DLS ')) + { + int rs=*(long*)(source+_p+4)+8; + if (rs+_p>source_size) break; + + mf->DLSsize=rs; + mf->pDLSdata=(BYTE*)malloc(rs); + memcpy(mf->pDLSdata,source+_p,rs); + + } +#endif + _p+=*(DWORD*)(source+_p+4)+8; + if (_p&1) _p++; + } + return 1; +_er: + return 0; +} + +bool load_xmi(MIDI_file* mf,const BYTE*,size_t); +bool load_hmp(MIDI_file* mf,const BYTE*,size_t); +bool load_hmi(MIDI_file* mf,const BYTE*,size_t); +bool load_mus(MIDI_file* mf,const BYTE*,size_t); +bool load_cmf(MIDI_file* mf,const BYTE*,size_t); +bool load_mids(MIDI_file* mf,const BYTE*,size_t); +bool load_gmf(MIDI_file* mf,const BYTE*,size_t); +bool is_mus(const BYTE*,size_t); +bool is_cmf(const BYTE*,size_t); +bool is_mids(const BYTE*,size_t); +bool is_gmf(const BYTE*,size_t); + +static bool load_midi_scan(MIDI_file* mf,const BYTE* ptr,size_t size) +{ + int max = size-3; + if (max>256) max=256; + int x; + for(x=0;x<256;x++) + { + if (*(DWORD*)(ptr+x)==_rv('MThd') && *(DWORD*)(ptr+x+4)==_rv(6)) + { + size-=x; + ptr+=x; + void * buf=malloc(size); + if (!buf) return 0; + memcpy(buf,ptr,size); + bool r=load_midi(mf,(BYTE*)buf,size); + if (!r) free(buf); + return r; + } + } + return 0; +} + + +struct +{ + bool ( * test ) (const BYTE* b,size_t s); + bool ( * load ) (MIDI_file* mf,const BYTE* ptr,size_t size); +} format_list[] = +{ + {is_midi,load_midi}, + {is_rmi,load_rmi}, + {is_hmp,load_hmp}, + {is_hmi,load_hmi}, + {is_xmidi,load_xmi}, + {is_mus,load_mus}, + {is_cmf,load_cmf}, + {is_gmd,load_gmd}, + {is_mids,load_mids}, + {is_gmf,load_gmf}, + {is_midi_scan,load_midi_scan} +}; + +//static fmtfunc fmts[]={is_midi,is_rmi,is_hmp,is_hmi,is_xmidi,is_mus,is_cmf,is_gmd,is_mids,is_gmf,is_midi_scan}; +//loadfunc loaders[]={load_midi,load_rmi,load_hmp,load_hmi,load_xmi,load_mus,load_cmf,load_gmd,load_mids,load_gmf,load_midi_scan}; + +#ifdef MF_USE_DMCRAP +void LoadDLS(MIDI_file* mf) +{ + if (mf->pDLSdata && ! mf->pDLS) + { + DMUS_OBJECTDESC ObjDesc; + ZeroMemory(&ObjDesc,sizeof(ObjDesc)); + ObjDesc.dwSize = sizeof(DMUS_OBJECTDESC); + ObjDesc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_MEMORY; + ObjDesc.llMemLength = mf->DLSsize; + ObjDesc.pbMemData = mf->pDLSdata; + ObjDesc.guidClass = CLSID_DirectMusicCollection; + + pLoader->GetObject(&ObjDesc,IID_IDirectMusicCollection,(void**)&mf->pDLS); + if (mf->pDLS) + { + ReleaseObject(mf->pDLS); + } + } +} + +void _LoadSegment(MIDI_file* mf) +{ +#ifdef USE_LOG + log_write("_LoadSegment()"); +#endif + mf->pSeg=0; + int data_size=0; + void * data_ptr=0; + + + if ( + !DoCleanUp(mf,CLEAN_DM|CLEAN_DLS|(cfg_nosysex ? CLEAN_NOSYSEX : 0),&data_ptr,&data_size) + ) + return; + + + IDirectMusicSegment* pSeg=0; + DMUS_OBJECTDESC ObjDesc; + ZeroMemory(&ObjDesc,sizeof(ObjDesc)); + ObjDesc.dwSize=sizeof(ObjDesc); + ObjDesc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_MEMORY; + ObjDesc.llMemLength = data_size; + ObjDesc.pbMemData = (BYTE*)data_ptr; + ObjDesc.guidClass=CLSID_DirectMusicSegment; +#ifdef USE_LOG + log_write("pLoader->EnableCache(GUID_DirectMusicAllTypes,1);"); +#endif + pLoader->EnableCache(GUID_DirectMusicAllTypes,1); +// pLoader->ClearCache(CLSID_DirectMusicSegment); //%$%&%@! this->sucks = TRUE +#ifdef USE_LOG + log_write("pLoader->GetObject(&ObjDesc,IID_IDirectMusicSegment,(void**)&pSeg);"); +#endif + pLoader->GetObject(&ObjDesc,IID_IDirectMusicSegment,(void**)&pSeg); + + if (!pSeg) + { +#ifdef USE_LOG + log_write("attempting memdump"); +#endif + char tmpf[MAX_PATH] = {0}; + get_temp_file(tmpf); + HANDLE f=CreateFileA(tmpf,GENERIC_WRITE,0,0,CREATE_ALWAYS,0,0); + if (f!=INVALID_HANDLE_VALUE) + { + DWORD bw = 0; + WriteFile(f,data_ptr,data_size,&bw,0); + CloseHandle(f); + mbstowcs(ObjDesc.wszFileName,tmpf,MAX_PATH); + ObjDesc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_FULLPATH | DMUS_OBJ_FILENAME; + pLoader->GetObject(&ObjDesc,IID_IDirectMusicSegment,(void**)&pSeg); + DeleteFileA(tmpf); + } + } + if (pSeg) + { +#ifdef USE_LOG + log_write("got IDirectMusicSegment"); + log_write("pSeg->SetParam(GUID_StandardMIDIFile,-1,0,0,0);"); +#endif + pSeg->SetParam(GUID_StandardMIDIFile,-1,0,0,0); +#ifdef USE_LOG + log_write("pSeg->SetStartPoint(0);"); +#endif + pSeg->SetStartPoint(0); +#ifdef USE_LOG + log_write("pSeg->SetLength();"); +#endif + { + bool ok=0; + if (cfg_eof_delay) + { + MUSIC_TIME mnt; + DMUS_TEMPO_PARAM tp; + if (SUCCEEDED(pSeg->GetParam(GUID_TempoParam,-1,0,mf->tix,&mnt,&tp))) + { + pSeg->SetLength((MUSIC_TIME)(mf->tix+(double)cfg_eof_delay*78.0/tp.dblTempo)); + ok=1; + } + } + if (!ok) pSeg->SetLength(mf->tix); + } + + mf->pSeg=pSeg; + + LoadDLS(mf); + + if (pCDLS) pSeg->SetParam(GUID_ConnectToDLSCollection,0xFFFFFFFF,0,0,(void*)pCDLS); + if (mf->pDLS) pSeg->SetParam(GUID_ConnectToDLSCollection,0xFFFFFFFF,0,0,(void*)mf->pDLS); + + if (mf->loopstart) + { + pSeg->SetLoopPoints(mf->loopstart,mf->loopend); + } + } + free(data_ptr); + pLoader->EnableCache(GUID_DirectMusicAllTypes,0); +} + +IDirectMusicSegment* LoadSegment(MIDI_file* mf) +{ +#ifdef USE_LOG + log_write("LoadSegment()"); +#endif + if (!pLoader) return 0; + IDirectMusicSegment* pSeg=0; + if (!mf->pSeg) _LoadSegment(mf); + if (mf->pSeg) + { +#ifdef USE_LOG + log_write("LoadSegment() : got IDirectMusicSegment"); +#endif + pSeg=mf->pSeg; +#ifdef USE_LOG + log_write("pSeg->AddRef()"); +#endif + pSeg->AddRef(); +#ifdef USE_LOG + log_write("pSeg->AddRef() returned"); +#endif + } +#ifdef USE_LOG + log_write("LoadSegment() returning"); +#endif + return pSeg; +} +#endif + +MIDI_file::~MIDI_file() +{ +#ifdef MF_USE_DMCRAP + if (pSeg) pSeg->Release(); + if (pDLS) pDLS->Release(); + if (pDLSdata) free(pDLSdata); +#endif + if (data) free((BYTE*)data); + if (tmap) delete tmap; + if (smap) delete smap; + if (info.traxnames) delete[] info.traxnames; + if (rmi_data) free(rmi_data); + if (bmp_data) free(bmp_data); +#ifdef HUNT_LEAKS + n_files--; +#endif +} + +bool GetMidiInfo(MIDI_file*); + +static bool try_format(const void * data,int size,int idx) +{ + bool rv; + try { + rv = format_list[idx].test((const BYTE*)data,size); + } catch(...) + { + rv = 0; + } + return rv; +} + +int MIDI_file::HeaderTest(const void * data,int size) +{ + int n; + for(n=0;n<tabsize(format_list);n++) + { + if (try_format(data,size,n)) return 1; + } + return 0; +} + +int MIDI_file::Load(const void * data,int size) +{ +#ifdef USE_LOG + log_write("Load()"); +#endif + + { + int n; + int fmt=-1; + for(n=0;n<tabsize(format_list);n++) + { + if (try_format(data,size,n)) {fmt=n;break;} + } + if (fmt==-1) return 0; + format = fmt; + } + + + + { + bool r; + try { + r=format_list[format].load(this,(const BYTE*)data,size); + } catch(...) { +#ifdef USE_LOG + log_write("midi loader crashed"); +#endif + r=0; + } + if (!r) return 0; + } + + return GetMidiInfo(this); +} + +MIDI_file* MIDI_file::Create(const char* fn,const void * data, size_t size) +{ + MIDI_file* mf=new MIDI_file(fn); + if (!mf->Load(data,size)) + { + delete mf; + mf=0; + } + return mf; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/midifile.h b/Src/Plugins/Input/in_midi/midifile.h new file mode 100644 index 00000000..af2b8a78 --- /dev/null +++ b/Src/Plugins/Input/in_midi/midifile.h @@ -0,0 +1,89 @@ +#ifndef NULLSOFT_IN_MIDI_MIDIFILE_H +#define NULLSOFT_IN_MIDI_MIDIFILE_H +#include <dsound.h> + +#ifndef MF_NO_DMCRAP +#define MF_USE_DMCRAP +#endif + +#ifdef MF_USE_DMCRAP +#include <dmusici.h> +#include <dmusicf.h> +#endif + +typedef struct +{ + UINT fmt,ntrax,tix; + UINT channels; + const char* e_type; + string copyright; + string * traxnames; +} MIDIINFO; + +#define FLAG_INCOMPLETE 1 + +typedef struct tagINSDESC +{ + tagINSDESC * next; + UINT bank_hi,bank_lo,patch,count,note_max,note_min,channels,user; + BOOL drum; +} INSTRUMENT_DESC; + +class MIDI_file +{ +public: + string path; + string title; + int flags; + int format; + int len,tix; + int size; + const BYTE* data; +#ifdef MF_USE_DMCRAP + IDirectMusicSegment *pSeg; + IDirectMusicCollection *pDLS; + BYTE* pDLSdata; + int DLSsize; +#endif + MIDIINFO info; + int loopstart,loopend; + int loopstart_t; + void * rmi_data;//extra RMI crap + int rmi_size; + void * bmp_data;//RMI-style bitmap data w/o BITMAPFILEHEADER + int bmp_size; + int kar_track; + CTempoMap * tmap; + CSysexMap * smap; + + void GetTitle(char *buf, int maxlen); + inline int GetLength(void) {return len;} + + static MIDI_file* Create(const char* fn,const void * data, size_t size); + + void Free() {if (--refcount==0) delete this;} + MIDI_file * AddRef() {refcount++;return this;} + + static int HeaderTest(const void * data,int total_size);//test first 256 bytes of file + +private: + int refcount; + MIDI_file(const char * fn); + int Load(const void * data,int size); + ~MIDI_file(); +}; + +#define CLEAN_DM 1 +#define CLEAN_1TRACK 2 +#define CLEAN_NOSYSEX 4 +#define CLEAN_NOTEMPO 8 +#define CLEAN_DLS 16 + +int DoCleanUp(MIDI_file*,DWORD,void** out_data,int * out_size); +INSTRUMENT_DESC* GetInstruments(MIDI_file*,BOOL do_lsb); + +#ifdef MF_USE_DMCRAP +IDirectMusicSegment * LoadSegment(MIDI_file*); +void LoadDLS(MIDI_file* mf); +#endif +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/midiinfo.cpp b/Src/Plugins/Input/in_midi/midiinfo.cpp new file mode 100644 index 00000000..e14b0563 --- /dev/null +++ b/Src/Plugins/Input/in_midi/midiinfo.cpp @@ -0,0 +1,326 @@ +#include "main.h" +#include <intsafe.h> +#define _RIFF 'FFIR' +#define _MThd 'dhTM' +#define _MTrk 'krTM' +#define _RMID 'DIMR' +#define _data 'atad' + + + +//#define BLAH + +static cfg_int cfg_loop_ctrl("cfg_loop_ctrl",255),cfg_loop_meta("cfg_loop_meta",255); + +class CGetInfo +{ +public: + MIDI_file * mf; + int loop; + int nch; + int got_notes; + int cur_track_start; + int c_track,s_track; + bool is_blah,f2; + int max_ff_track,max_ff_num; + + CTempoMap *tmap,*ttmap; + CSysexMap *smap; + void CleanTempo(); + int DoTrack(const BYTE* track,size_t size,string& name,int); + bool Run(MIDI_file* mf); +}; + +static bool memicmp(char* b1,char* b2,int s) +{ + for(int n = 0; n < s; n++) + { + if (tolower(b1[n]) != tolower(b2[n])) + return 1; + } + return 0; +} + +static bool is_kar(char* ptr,int siz) //hack +{ + siz -= 7;//strlen("karaoke"); + for(int n = 0; n <= siz; n++) + { + // lameness to just prevent a crash on broken + // files no idea what else it'll break though + if ((int)(ptr+n) > siz) return 0; + if (!memicmp(ptr+n,"karaoke",7)) return 1; + } + return 0; +} + +extern cfg_int cfg_ff7loopz; + +BYTE ff7loopstart[12]={0xFF,6,9,'l','o','o','p','S','t','a','r','t'}; +BYTE ff7loopend[10]={0xFF,6,7,'l','o','o','p','E','n','d'}; + +int CGetInfo::DoTrack(const BYTE* track,size_t size,string& name,int cpos) +{ + int res=0,_res=0; + size_t n=0; + BYTE lc1=0,lastcom=0; + bool run=0; + int ff_num=0; + while(n<size) + { + { + unsigned int d=0; + unsigned int _n=DecodeDelta(track+n,&d); + if (_n<4) res+=d; + n+=_n; + } + if (track[n]==0xFF) //meta-events + { + if (f2) _res=res; + if (cfg_ff7loopz + && (size-n)>=sizeof(ff7loopstart) // bounds check + && !memcmp(&track[n],ff7loopstart,sizeof(ff7loopstart))) + { + if (loop==-1) loop=res; + } + if ((UINT)track[n+1]==(UINT)cfg_loop_meta && loop==-1) loop=res; + if (track[n+1]==0x51 && track[n+2]==0x03) //tempo + { + if (ttmap) ttmap->AddEntry(cpos+res,((DWORD)track[n+3]<<16)+((DWORD)track[n+4]<<8)+((DWORD)track[n+5])); + n+=6; + } + else if (track[n+1]==0x2F && track[n+2]==0x00) + { + if (ff_num>max_ff_num) + { + max_ff_num=ff_num; + max_ff_track=cur_track_start; + } + return _res; + } + else + { + DWORD _l=0,l1; + UINT n1=0; + { + do + { + _l=(_l<<7)|(track[n+2+n1++]&0x7F); + } + while((n+1+n1< size) && track[n+1+n1]&0x80); + } + if (_l>255) l1=255; + else l1=_l; + if (track[n+1]<0x10) ff_num++; + + switch(track[n+1]) + { + case 6: +// if (!cpr || *cpr) break; + case 2: + if (n + 1 + n1 + l1 >= size) + return -1; + mf->info.copyright.add_string_n((char*)(track+n+n1+2),l1); + mf->info.copyright.add_string("\x0d\x0a"); + break; + case 5: + is_blah=1; + break; + case 3: + case 1: + if (is_kar((char*)track+n+n1+2,_l)) is_blah=1; + case 4: + if (name.length()==0) + { + name.add_string_n((char*)(track+n+n1+2),l1); + } + break; + } + size_t n_increment; + if (SizeTAdd(2, n1, &n_increment) != S_OK || SizeTAdd(n_increment, _l, &n_increment) != S_OK || SizeTAdd(n_increment, n, &n) != S_OK) + return -1; + } + } + else if ((track[n]&0xF0)==0xF0) + { + if (track[n]==0xF0) + { + _res=res; + UINT s=ReadSysex(&track[n],size-n); + smap->AddEvent(&track[n],s,cpos+res); + n+=s; + if (s_track==-1) s_track=c_track; + else if (s_track!=c_track) s_track=-2; + + } + else //hack... + if (track[n]==0xF7) n++; + else + { +#ifdef BLAH + char tmp[32] = {0}; + wsprintf(tmp,"invalid Fx event at %x",n); + MessageBox(0,tmp,0,0); +#endif + return -1; + } + } + else + { + lc1=track[n]; +// if (lc1 == 0) return -1; + if ((lc1&0x80)==0) + { + if (lastcom==0) + return -1; + run=1; + lc1=lastcom; + n--; + + } else run=0; + _res=res; + switch(lc1&0xF0) + { + case 0x80: + case 0x90: + if (!(got_notes&(1<<(lc1&0xF)))) + { + nch++; + got_notes|=1<<(lc1&0xF); + } + case 0xB0: + if (track[n+1]==cfg_loop_ctrl && loop==-1) + loop=res; + case 0xA0: + case 0xE0: + n+=3; + lastcom=lc1; + break; + case 0xC0: + case 0xD0: + n+=2; + lastcom=lc1; + break; + default: + return -1; + } + } + } + return _res; +} + +bool GetMidiInfo(MIDI_file* mf) +{ + CGetInfo i; + return i.Run(mf); +} + +bool CGetInfo::Run(MIDI_file* _mf) +{ + mf=_mf; + nch=0; + s_track=-1; + is_blah=0; + max_ff_track=max_ff_num=0; + + MIDIHEADER hd = *(MIDIHEADER*)(mf->data+8); + tmap=tmap_create(); + if (!tmap) return 0; + smap=smap_create(); + ttmap=0; + + mf->tmap=tmap;//avoid stupid memleaks + mf->smap=smap; + + loop=-1; + tmap->AddEntry(0,500000); + + DWORD sz = mf->size-14; + FixHeader(hd); + + got_notes=0; + nch=0; + + const BYTE* trax=mf->data+14; + const BYTE* ntrak=trax; + if (hd.trax>0x100 || hd.fmt>2) return 0; + f2=hd.fmt==2; + + int n,tmp; + int size=0; + mf->info.traxnames = new string[hd.trax]; + + + for(c_track=0;c_track<hd.trax;c_track++) + { + if (!ttmap) ttmap=tmap_create(); + if ((UINT)(ntrak-trax)>=(UINT)sz || *((DWORD*)ntrak)!='krTM' || (tmp=rev32(*((DWORD*)ntrak+1)))+ntrak>sz+trax) return 0; + cur_track_start=ntrak-mf->data; + tmp=DoTrack(ntrak+8,tmp,mf->info.traxnames[c_track],f2 ? size : 0); + if (tmp==-1) + { +/* ntrak[8]=0; + ntrak[9]=0xFF; + ntrak[10]=0x2F; + ntrak[11]=0;*/ +#ifdef BLAH + { + char e[128] = {0}; + wsprintf(e,"Bad track #%u",c_track); + MessageBox(0,e,ERROR,0); + } +#endif + } + else + { + if (f2) size+=tmp; + else if (tmp>size) size=tmp; + if (ttmap->pos) + { + mf->tmap=tmap=tmap_merge(tmap,ttmap); + ttmap=0; + } + + } + ntrak+=rev32(*((DWORD*)ntrak+1))+8; + } + if (ttmap) delete ttmap; + + if (!tmap) return 0; + + mf->tix=MulDiv(size+50,768,hd.dtx); + + DWORDLONG res=0; + for(n=0;n<tmap->pos-1 && tmap->data[n].pos<size;n++) + { + res+=UInt32x32To64(tmap->data[n].tm,tmap->data[n+1].pos-tmap->data[n].pos); + } + if (tmap->data[n].pos<size) res+=UInt32x32To64(tmap->data[n].tm,size-tmap->data[n].pos); + mf->len=(DWORD)(res/(hd.dtx*1000)); + + if (loop!=-1 && loop<size) + { + mf->loopstart=loop; + } + mf->info.channels=nch; + mf->info.fmt=hd.fmt; + mf->info.ntrax=hd.trax; + mf->info.tix=size; + if (mf->loopstart) + { + mf->loopstart_t=mf->loopstart; + mf->loopstart=MulDiv(mf->loopstart,768,hd.dtx); + mf->loopend=MulDiv(size+15,768,hd.dtx); + } + else mf->loopstart_t=-1; + if (!f2 && smap && s_track==-2) smap->CleanUp(); //todo: optimize this shit... +/* mf->tmap=tmap; + mf->smap=smap;*/ + mf->info.e_type=smap->GetType(); + + if (is_blah) + { + mf->kar_track=max_ff_track; + } + return 1; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/midiout_helper.cpp b/Src/Plugins/Input/in_midi/midiout_helper.cpp new file mode 100644 index 00000000..ada2e3d8 --- /dev/null +++ b/Src/Plugins/Input/in_midi/midiout_helper.cpp @@ -0,0 +1,2 @@ +#include "main.h" + diff --git a/Src/Plugins/Input/in_midi/mids.cpp b/Src/Plugins/Input/in_midi/mids.cpp new file mode 100644 index 00000000..456da30f --- /dev/null +++ b/Src/Plugins/Input/in_midi/mids.cpp @@ -0,0 +1,96 @@ +#include "main.h" +#include "cvt.h" + +bool is_mids(const BYTE* buf,size_t s) +{ + return s>0x20 && *(DWORD*)buf==_rv('RIFF') && *(DWORD*)(buf+8)==_rv('MIDS') && *(DWORD*)(buf+12)==_rv('fmt '); +} + +typedef struct +{ + DWORD dwTimeFormat; + DWORD cbMaxBuffer; + DWORD dwFlags; +} MIDSFMT; + + +#define WRITE(X,Y) out.write(X,Y) + +#define WRITE_DELTA(X) gb_write_delta(out,X) + +#define D_WRITE {WRITE_DELTA(ct-tw);tw=ct;} + +bool load_mids(MIDI_file* mf,const BYTE* buf,size_t sz) +{ + if (sz<*(long*)(buf+4)+8) return 0; + MIDSFMT* fmt=(MIDSFMT*)(buf+0x14); + DWORD ofs; + ofs=*(DWORD*)(buf+0x10)+0x14; + if (*(DWORD*)(buf+ofs)!=_rv('data')) return 0; + //ofs+=8+*(DWORD*)(buf+ofs+4); + ofs+=8; + DWORD ss=*(DWORD*)(buf+ofs-4); + DWORD nc=*(DWORD*)(buf+ofs); + DWORD* ptr=(DWORD*)(buf+ofs); + grow_buf out; + ss>>=2; + DWORD mhdr[2]; + mhdr[0]=_rv('MThd'); + mhdr[1]=_rv(6); + WRITE(mhdr,8); + WORD w=0; + WRITE(&w,2); + w=0x100; + WRITE(&w,2); + w=rev16((WORD)fmt->dwTimeFormat); + WRITE(&w,2); + mhdr[0]=_rv('MTrk'); + WRITE(mhdr,8); + DWORD tw=0,ct=0; + DWORD cc=0; + DWORD cs; + DWORD pos=1; + while(cc<nc) + { + cs = (ptr[pos+1]>>2)+pos; + if (cs>ss) break; + pos+=2; + while(pos<cs) + { + ct+=ptr[pos]; + pos+=2; + DWORD e=ptr[pos]; + if (e&MEVT_F_LONG) + { + pos+=e&0xFFFFFF; + } + else + { + if (e>>24==MEVT_TEMPO) + { + D_WRITE; + BYTE tmp[6]={0xFF,0x51,0x03,(BYTE)((e>>16)&0xFF),(BYTE)((e>>8)&0xFF),(BYTE)(e&0xFF)}; + WRITE(tmp,6); + } + else if (!(e>>24)) + { + BYTE c=(BYTE)(e&0xF0); + if (c!=0xF0) + { + D_WRITE; + DWORD l=(c==0xC0 || c==0xD0) ? 2 : 3; + WRITE(&e,l); + } + } + pos++; + } + } + } + WRITE("\x00\xFF\x2F",4); + + out.write_dword_ptr(rev32(out.get_size()-(8+6+8)),8+6+4); + + mf->size = out.get_size(); + mf->data = (BYTE*)out.finish(); + return !!mf->data; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/mus.cpp b/Src/Plugins/Input/in_midi/mus.cpp new file mode 100644 index 00000000..18aeb4f4 --- /dev/null +++ b/Src/Plugins/Input/in_midi/mus.cpp @@ -0,0 +1,184 @@ +#include "main.h" +#include "cvt.h" + +bool is_mus(const BYTE* buf,size_t s) +{ + if (s>0x20 && *(DWORD*)buf == '\x1ASUM') + { + int ofs = ((WORD*)buf)[3]; + int n_ins = ((WORD*)buf)[6]; + if (ofs>=16+(n_ins<<1) && ofs<16+(n_ins<<2) && ofs<s) + { + return 1; + } + } + return 0; +} + +static BYTE tempodat[] = {0x00,0xFF,0x51,0x03,0x09,0xA3,0x1A}; + +static BYTE controllers[15] = {0,0,1,7,10,11,91,93,64,67,120,123,126,127,121}; + +#define abort _abort_ + +struct MUS_cvt +{ +public: + bool abort; + DWORD ct; + DWORD lt; + grow_buf out; + + void AddEvent(DWORD ev,int l); + bool run(MIDI_file* mf,const BYTE* ptr,DWORD sz); +}; + +void MUS_cvt::AddEvent(DWORD ev,int l) +{ + DWORD dt = ct - lt; + int tl=3; + gb_write_delta(out,dt); + lt = ct; + BYTE ec=(BYTE)(ev&0xF0); + out.write(&ev,l); +} + +bool MUS_cvt::run(MIDI_file* mf,const BYTE* ptr,DWORD sz) +{ +#pragma pack(push) +#pragma pack(1) + struct + { + char id[4]; + WORD len; + WORD ofs; + WORD ch1,ch2; + WORD n_ins; + WORD dummy; + } hdr; +#pragma pack(pop) + DWORD _pt=0; + memcpy(&hdr,ptr,sizeof(hdr)); + const BYTE* score = ptr+sizeof(hdr)+2*hdr.n_ins; + long x; + + static BYTE _hd_[]={'M','T','h','d',0,0,0,6, 0,0,0,1,0,0x59,'M','T','r','k'}; + out.write(_hd_,sizeof(_hd_)); + DWORD ts_ofs=out.get_size(); + out.write_dword(0); + + lt=0; + abort = 0; + ct = 0; + out.write(tempodat,sizeof(tempodat)); + + x=0; + bool t; + BYTE ch; + BYTE vols[16]; + ZeroMemory(vols,sizeof(vols)); + union + { + BYTE b[4]; + DWORD dw; + } ev; + while(x<hdr.len && score[x]!=0x60) + { + ev.dw = 0; + t=(score[x]&0x80)?1:0; + ch = score[x]&0xF; + if (ch == 0xF) ch = 9;//hdr.ch1+1; + else if (ch>=9) ch++; + switch(score[x]&0x70) + { + case 0: //release note + ev.b[0]=0x80|ch; + ev.b[1]=score[x+1]; + ev.b[2]=0;//vols[ch]; + AddEvent(ev.dw,3); + x+=2; + break; + case 0x10: //play note + ev.b[0]=0x90|ch; + ev.b[1]=score[x+1]&0x7F; + if (score[x+1]&0x80) + { + vols[ch]=score[x+2]; + x+=3; + } + else + { + x+=2; + } + ev.b[2]=vols[ch]; + AddEvent(ev.dw,3); + break; + case 0x20: //pitch wheel + ev.b[0]=0xE0|ch; + ev.b[1]=0; + ev.b[2]=score[x+1]>>1; + AddEvent(ev.dw,3); + x+=2; + break; + case 0x30: //system event + if (score[x+1]>=10 && score[x+1]<=14) + { + ev.b[0]=0xB0|ch; + ev.b[1]=controllers[score[x+1]]; + ev.b[2]=1; + AddEvent(ev.dw,3); + x+=2; + break; + } + else return 0; + case 0x40: //change controller + if (score[x+1]) + { + if (score[x+1]<10) + { + ev.b[0]=0xB0|ch; + ev.b[1]=controllers[score[x+1]]; + ev.b[2]=score[x+2]; + AddEvent(ev.dw,3); + x+=3; + } + else return 0; + } + else + { + ev.b[0]=0xC0|ch; + ev.b[1]=score[x+2]; + AddEvent(ev.dw,2); + x+=3; + }; + break; + case 0x50: + case 0x70: + case 0x60: + return 0; + } + if (abort) return 0; + if (t) + { + DWORD dt=0; + do + { + dt = (dt<<7) + (score[x]&0x7F); + } while(score[x++]&0x80); + ct+=dt; + } + } + + out.write_dword(0x002FFF00); + out.write_dword_ptr(rev32(out.get_size()-(ts_ofs+4)),ts_ofs); + + mf->size = out.get_size(); + mf->data = (BYTE*)out.finish(); + return !!mf->data; +} + +bool load_mus(MIDI_file* mf,const BYTE* ptr,size_t sz) +{ + MUS_cvt c; + return c.run(mf,ptr,sz); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/out_dmusic.cpp b/Src/Plugins/Input/in_midi/out_dmusic.cpp new file mode 100644 index 00000000..51267141 --- /dev/null +++ b/Src/Plugins/Input/in_midi/out_dmusic.cpp @@ -0,0 +1,962 @@ +#include "main.h" +#include <math.h> +#include "seq.h" +#include "fakedsound.h" +#include "resource.h" + +// {B84EB58A-29F5-410b-880A-EB473BF34291} +static const GUID guid_output = +{ 0xb84eb58a, 0x29f5, 0x410b, { 0x88, 0xa, 0xeb, 0x47, 0x3b, 0xf3, 0x42, 0x91 } }; + +// {DF0800B6-D1E1-4b53-9C1E-642AF4CB7136} +static const GUID dmusic_driver_guid = +{ 0xdf0800b6, 0xd1e1, 0x4b53, { 0x9c, 0x1e, 0x64, 0x2a, 0xf4, 0xcb, 0x71, 0x36 } }; + +enum +{ + MDD_OUT=1, +}; + + +extern cfg_int cfg_dls_active,cfg_dm_keep_port; +extern cfg_string cfg_dls_file; + +class MIDI_device_dmusic : public MIDI_device +{ +private: + GUID guid,guid_dmusic; + DWORD dmFlags; + bool f_has_output; + + virtual player_base * create(); + virtual bool is_default() {return 0;} + virtual bool has_freq() {return !!(dmFlags&DMUS_PC_DIRECTSOUND);} + virtual bool volctrl_happy() {return (dmFlags&DMUS_PC_DIRECTSOUND) || (dmFlags&DMUS_PC_SOFTWARESYNTH);} +public: + MIDI_device_dmusic(GUID p_guid,bool p_has_output,DWORD p_dmFlags,const wchar_t * p_name,const wchar_t * p_info) + { + guid = p_guid; + guid_dmusic = p_guid; + dmFlags = p_dmFlags; + set_name(p_name); + set_info(p_info); + f_has_output = p_has_output; + if (f_has_output) + { + const BYTE * src = (const BYTE*) &guid_output; + BYTE * dst = (BYTE*) &guid; + int n; + for(n=0;n<sizeof(GUID);n++) dst[n]^=src[n]; + } + + } + virtual GUID get_guid() {return guid;} + GUID get_dm_guid() {return guid_dmusic;} + virtual bool has_output() {return f_has_output;} + virtual bool has_dls() {return !!(dmFlags&DMUS_PC_DLS);} +}; + +//bool IsDrumBankOK(BYTE n); + +IDirectMusicLoader* pLoader=0; +IDirectMusicPerformance* pPerf=0; +static IDirectMusicCollection *pGM=0; +static IDirectMusic* pDM; +static IDirectMusicPort *pPort; +static IDirectSoundBuffer* pHack; +IDirectMusicCollection *pCDLS=0; + + +static void SendMsg(IDirectMusicPerformance *pPerf,DWORD msg) +{ + DMUS_MIDI_PMSG *pMSG; + if(SUCCEEDED(pPerf->AllocPMsg(sizeof(DMUS_MIDI_PMSG),(DMUS_PMSG**)&pMSG))) + { + ZeroMemory(pMSG, sizeof(DMUS_MIDI_PMSG)); + pMSG->dwSize = sizeof(DMUS_MIDI_PMSG); + pMSG->dwPChannel = msg&0xF; + pMSG->dwVirtualTrackID = 0; + pMSG->dwType = DMUS_PMSGT_MIDI; + pMSG->dwVoiceID = 0; + pMSG->dwGroupID = 0xFFFFFFFF; + pMSG->mtTime=0; + pMSG->dwFlags = DMUS_PMSGF_REFTIME|DMUS_PMSGF_TOOL_IMMEDIATE;//pMSG->dwFlags = DMUS_PMSGF_REFTIME|DMUS_PMSGF_TOOL_IMMEDIATE; + pMSG->bStatus=(BYTE)(msg&0xFF); + pMSG->bByte1=(BYTE)((msg>>8)&0xFF); + pMSG->bByte2=(BYTE)((msg>>16)&0xFF); + if (FAILED(pPerf->SendPMsg((DMUS_PMSG*)pMSG))) + { + pPerf->FreePMsg((DMUS_PMSG*)pMSG); + } + } +} + +static void SendSysex(IDirectMusicPerformance *pPerf,BYTE* data,UINT len) +{ + DMUS_SYSEX_PMSG *pMSG; + if(SUCCEEDED(pPerf->AllocPMsg(sizeof(DMUS_SYSEX_PMSG) + len,(DMUS_PMSG**)&pMSG))) + { + ZeroMemory(pMSG, sizeof(DMUS_SYSEX_PMSG)+len); + pMSG->dwSize = sizeof(DMUS_SYSEX_PMSG); + pMSG->dwPChannel = 0; + pMSG->dwVirtualTrackID = 0; + pMSG->dwType = DMUS_PMSGT_SYSEX; + pMSG->dwVoiceID = 0; + pMSG->dwGroupID = 0xFFFFFFFF; + pMSG->dwLen = len; + memcpy(pMSG->abData, (void*)data, len); + pMSG->mtTime=0; + pMSG->dwFlags = DMUS_PMSGF_REFTIME|DMUS_PMSGF_TOOL_IMMEDIATE;//pMSG->dwFlags = |DMUS_PMSGF_TOOL_IMMEDIATE; + if (FAILED(pPerf->SendPMsg((DMUS_PMSG*)pMSG))) + { + pPerf->FreePMsg((DMUS_PMSG*)pMSG); + } + } +} + +static void PortKill() +{ +#ifdef USE_LOG + log_write("portkill()"); +#endif + if (pPort) + { + pPort->Activate(0); + if (pPerf) pPerf->RemovePort(pPort); + pPort->Release(); + pPort=0; + } +} + +static int PortInit(MIDI_device_dmusic * dev) +{ +#ifdef USE_LOG + log_write("portinit()"); +#endif +static int _act_freq; +static int _cfg_reverb,_cfg_chorus; +static GUID last_port; + + if (!pPort || last_port!=dev->get_guid() || _act_freq!=cfg_freq || _cfg_reverb!=cfg_reverb || _cfg_chorus!=cfg_chorus) + { +#ifdef USE_LOG + log_write("port settings changed"); +#endif + if (pPort) PortKill(); + + DMUS_PORTPARAMS dmpp; + ZeroMemory(&dmpp,sizeof(dmpp)); + dmpp.dwSize=sizeof(dmpp); + dmpp.dwValidParams=DMUS_PORTPARAMS_EFFECTS|DMUS_PORTPARAMS_SAMPLERATE|DMUS_PORTPARAMS_CHANNELGROUPS; + dmpp.dwChannelGroups=1; + dmpp.dwSampleRate=cfg_freq; + if (cfg_reverb) dmpp.dwEffectFlags=DMUS_EFFECT_REVERB; + if (cfg_chorus) dmpp.dwEffectFlags|=DMUS_EFFECT_CHORUS; + if (FAILED( pDM->CreatePort(dev->get_dm_guid(),&dmpp,&pPort,0) )) return 0; + + pPerf->AddPort(pPort); + pPerf->AssignPChannelBlock(0,pPort,1); + last_port = dev->get_guid(); + + _act_freq=cfg_freq; + _cfg_reverb=cfg_reverb; + _cfg_chorus=cfg_chorus; + } + if ((dev->has_output())) + { +#ifdef USE_LOG + log_write("initializing output hack"); +#endif + DWORD buf_s=0,blah=0; + pPort->GetFormat(0,&blah,&buf_s); + pHack=dhb_create(buf_s,cfg_freq); + if (FAILED(pPort->SetDirectSound(get_ds(),pHack))) + {//BORK + PortKill(); + return 0; + } + } + return 1; +} + +/* + int lastvol1=(vol==0)?0x80000000:((int)(2000.0*log((double)vol/256.0))); + if (pPerf) + { + return SUCCEEDED(pPerf->SetGlobalParam(GUID_PerfMasterVolume,&lastvol1,4)); + } +*/ + +static int DM_setvol(int vol) +{ + int lastvol1=(vol==0)?0x80000000:((int)(2000.0*log10((double)vol/255.0))); + if (pPerf) + { + return SUCCEEDED(pPerf->SetGlobalParam(GUID_PerfMasterVolume,&lastvol1,4)); + } + + return 0; +} + +class player_dmusic_imm : public seq_base +{ +private: + MIDI_device_dmusic * dev; + UINT n_ins,s_ins; + IDirectMusicDownloadedInstrument ** ins; + IDirectMusicCollection *edls; + +protected: + virtual void seq_shortmsg(DWORD msg) {SendMsg(pPerf,msg);} + virtual void seq_sysex(BYTE* d,UINT l) {SendSysex(pPerf,d,l);} + virtual void eof() {MIDI_core::Eof();} + int setvol(int t) {return DM_setvol(t);} +public: + player_dmusic_imm(MIDI_device_dmusic * p_dev) + { + dev=p_dev; + s_ins=n_ins=0; + ins=0; + edls=0; + if (dev->has_dls()) + { + s_ins=0x100; + ins=(IDirectMusicDownloadedInstrument**)malloc(s_ins*sizeof(void*)); + } + + } + ~player_dmusic_imm(); + int play(); + +}; + + +int player_dmusic_imm::play() +{ + if (!PortInit(dev)) return 0; + + MIDI_file * mf=MIDI_core::getFile(); + if (ins) + { + + if (mf->pDLSdata) + { + LoadDLS(mf); + if (mf->pDLS) + { + edls=mf->pDLS; + } + } + + if (!edls) edls=pCDLS; + + { + INSTRUMENT_DESC* instr=GetInstruments(mf,1); + + while(instr) + { + DWORD i=instr->patch | (instr->bank_lo<<8) | (instr->bank_hi<<16); + if (instr->drum) i|=0x80000000; + if (n_ins>=s_ins) + { + s_ins<<=1; + void *t=realloc(ins,s_ins); +// if (!t) {s_ins>>=1;return ;} + ins=(IDirectMusicDownloadedInstrument**)t; + } + IDirectMusicInstrument * pi=0; +start: + if (edls) + { + edls->GetInstrument(i,&pi); + } + if (!pi && pGM) + { + pGM->GetInstrument(i,&pi); + } + if (!pi) //cleaner's hacks don't work here + { + if (i&0x80000000) + { + if (i&0xFFFF00) {i&=0x800000FF;goto start;} + } + else + { + if (i&0xFF00) {i&=0xFF00FF;goto start;} + if (i&0xFF0000) {i&=0xFF;goto start;} + } + } +#if 0 + if (!pi) + { + char tmp[128] = {0}; + if (i&0x80000000) + wsprintf(tmp,"missing drum kit: %u",i&0xFF); + else + wsprintf(tmp,"missing instrument: bank %x:%x / patch %x",(i>>16)&0xFF,(i>>8)&0xFF,i&0xFF); + Warning(tmp); + } +#endif + if (pi) + { +// DMUS_NOTERANGE nr = {instr->note_min,instr->note_max}; +// pPort->DownloadInstrument(pi,&ins[n_ins++],&nr,1); + pPort->DownloadInstrument(pi,&ins[n_ins++],0,0); + pi->Release(); + } + + { + INSTRUMENT_DESC * d=instr->next; + delete instr; + instr=d; + } + } + } + + } +/* UINT n; + for(n=0;n<16;n++) + { + pPort->SetChannelPriority(0,n,DAUD_CRITICAL_VOICE_PRIORITY); + }*/ + pPort->Activate(1); + + DM_setvol(MIDI_core::player_getVol()); + + return seq_cmd_start(CLEAN_DM); +} + +player_dmusic_imm::~player_dmusic_imm() +{ + seq_cmd_stop(); + if (ins) + { + if (pPort) + { + pPort->Activate(0); + UINT n; + for(n=0;n<n_ins;n++) + { + if (ins[n]) + { + pPort->UnloadInstrument(ins[n]); + ins[n]=0; + } + } + } + free(ins); + } + + if (pHack) + { + pPort->SetDirectSound(0,0); + pHack->Release(); + pHack=0; + } + if (!cfg_dm_keep_port) PortKill(); +} + + +static void CALLBACK TimerProc(HWND,UINT,UINT id,DWORD) +{ + DMUS_NOTIFICATION_PMSG* pMsg; + while(pPerf->GetNotificationPMsg(&pMsg)==S_OK) + { + if (IsEqualGUID(pMsg->guidNotificationType,GUID_NOTIFICATION_SEGMENT)) + { + if (MIDI_core::HavePlayer() && pMsg->dwNotificationOption == DMUS_NOTIFICATION_SEGEND) + { + MIDI_core::Eof(); + } + } + pPerf->FreePMsg((DMUS_PMSG*)pMsg); + } +} + +class player_dmusic : public player_base +{ +public: + ~player_dmusic(); + int gettime(); + int settime(int); + int setvol(int vol) {return DM_setvol(vol);} + void pause(); + void unpause(); + int play(); + player_dmusic(MIDI_device_dmusic * p_dev) + { + dev = p_dev; + pSeg=0; + pSS=0; + rtStart=rtOffset=0; + mtStart=mtOffset=0; + } + +private: + MIDI_device_dmusic * dev; + IDirectMusicSegment* pSeg; + IDirectMusicSegmentState* pSS; + + REFERENCE_TIME rtStart,rtOffset; + MUSIC_TIME mtOffset,mtStart; + bool dloaded,paused; + UINT timer_id; +}; + +player_base * MIDI_device_dmusic::create() +{ +#ifdef USE_LOG + log_write("DM_create"); +#endif + + CoInitialize(0); + if (!pLoader) + { + try { + + CoCreateInstance(CLSID_DirectMusicLoader,0,CLSCTX_INPROC,IID_IDirectMusicLoader,(void**)&pLoader); + if (!pLoader) return 0; + pLoader->EnableCache(GUID_DirectMusicAllTypes,0); + } catch(...) { + return 0; + } + } + + if (!pPerf) + { + try { + CoCreateInstance(CLSID_DirectMusicPerformance,0,CLSCTX_INPROC,IID_IDirectMusicPerformance,(void**)&pPerf); + if (!pPerf) return 0; + pPerf->Init(&pDM,0,0); + pPerf->AddNotificationType(GUID_NOTIFICATION_SEGMENT); + } catch(...) { + return 0; + } + } + + if (!pGM) + { + DMUS_OBJECTDESC desc; + ZeroMemory(&desc,sizeof(desc)); + desc.dwSize=sizeof(desc); + desc.dwValidData=DMUS_OBJ_OBJECT|DMUS_OBJ_CLASS; + desc.guidObject=GUID_DefaultGMCollection; + desc.guidClass=CLSID_DirectMusicCollection; + try { + pLoader->GetObject(&desc,IID_IDirectMusicCollection,(void**)&pGM); + } catch(...) { + return 0; + } + } + + + if (has_dls()) + { + static string current_dls; + if (!cfg_dls_active) + { + if (pCDLS) {pCDLS->Release();pCDLS=0;} + } + else + { + if (pCDLS && _stricmp(current_dls,cfg_dls_file)) {pCDLS->Release();pCDLS=0;} + if (!pCDLS) + { + DMUS_OBJECTDESC desc; + ZeroMemory(&desc,sizeof(desc)); + desc.dwSize=sizeof(desc); + desc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME; + desc.guidClass = CLSID_DirectMusicCollection; + mbstowcs(desc.wszFileName,cfg_dls_file,DMUS_MAX_FILENAME); + if (FAILED(pLoader->GetObject(&desc,IID_IDirectMusicCollection,(void**)&pCDLS))) + { +// ErrorBox(Can't load DLS file.); + pCDLS=0; + cfg_dls_active=0; + } + else + { + ReleaseObject(pCDLS); + current_dls = cfg_dls_file; + } + } + } + } + + if (cfg_playback_mode) + { + player_dmusic_imm * p=new player_dmusic_imm(this); + if (p) + { + if (!p->play()) + { + delete p; + p=0; + } + } + return p; + } + else + { + player_dmusic* p=new player_dmusic(this); + if (p) + { + if (!p->play()) {delete p;p=0;} + } + return p; + } +} + +MUSIC_TIME GetMTforMS(IDirectMusicSegment* pS,DWORD ms) +{ + MUSIC_TIME mtSeg,mct=0,mnt; + DMUS_TEMPO_PARAM tp; + pS->GetLength(&mtSeg); + double _r=0,r1; + while(mct<mtSeg) + { + if (FAILED(pS->GetParam(GUID_TempoParam,-1,0,mct,&mnt,&tp))) break; + if (!mnt) mnt=mtSeg-mct; + r1=_r; + _r+=(mnt)/tp.dblTempo*78; + if (_r>ms) + { + return (MUSIC_TIME)(mct+mnt*((double)ms-r1)/(_r-r1)); + } + mct+=mnt; + } + return mtSeg; +} + + + +DWORD GetSegLen(IDirectMusicSegment* pS) +{ + MUSIC_TIME mtSeg,mct=0,mnt; + DMUS_TEMPO_PARAM tp; + pS->GetLength(&mtSeg); + double _r=0; + while(mct<mtSeg) + { + pS->GetParam(GUID_TempoParam,-1,0,mct,&mnt,&tp); + if (!mnt) mnt=mtSeg-mct; + _r+=(double)mnt/tp.dblTempo*78; + mct+=mnt; + } + return (DWORD)_r; +} + +void ReleaseObject(IUnknown* o) +{ + IDirectMusicObject* pObject=0; + if (pLoader && SUCCEEDED(o->QueryInterface(IID_IDirectMusicObject,(void**)&pObject))) + { + pLoader->ReleaseObject(pObject); + pObject->Release(); + } +} + +player_dmusic::~player_dmusic() +{ + pPerf->Stop(0,0,0,0); + + if (pPort) pPort->Activate(0); +// pPerf->Invalidate(0,0); + + mtOffset=0; + rtOffset=0; + mtStart=0; + rtStart=0; + if (pSS) {pSS->Release();pSS=0;} + + if (pSeg) + { + if (dloaded) pSeg->SetParam(GUID_Unload,0xFFFFFFFF,0,0,(void*)pPerf); + pSeg->Release(); + pSeg=0; + } + + if (pHack) + { + pPort->SetDirectSound(0,0); + pHack->Release(); + pHack=0; + } + if (!cfg_dm_keep_port) PortKill(); + if (timer_id) KillTimer(0,timer_id); +} + + +void player_dmusic::pause() +{ + MUSIC_TIME mt; + REFERENCE_TIME rt; + pPerf->Stop(0,0,0,0); + if (pSS) + { + pSS->Release(); + pSS=0; + } + pPerf->GetTime(&rt,&mt); + mtOffset+=mt-mtStart; + rtOffset+=rt-rtStart; + pSeg->SetStartPoint(mtOffset); + paused=1; +} + +void player_dmusic::unpause() +{ + if (pSS) + { + pSS->Release(); + pSS=0; + } + if (SUCCEEDED(pPerf->PlaySegment(pSeg,0,0,&pSS))) + { + pSS->GetStartTime(&mtStart); + pPerf->MusicToReferenceTime(mtStart,&rtStart); + } + paused=0; +} + +int player_dmusic::gettime() +{ + static DWORD tm; + if (pSS) + { + REFERENCE_TIME rt; + pPerf->GetTime(&rt,0); + tm=(int)((rt-rtStart+rtOffset)/10000); + } + return tm; +} + +int player_dmusic::play() +{ +#ifdef USE_LOG + log_write("player_dmusic::play()"); +#endif + if (!PortInit(dev)) return 0; + pSeg=LoadSegment(MIDI_core::getFile()); + if (!pSeg) + { +#ifdef USE_LOG + log_write("LoadSegment() failed"); +#endif +// Error("Unable to get IDirectMusicSegment."); + return 0; + } +#ifdef USE_LOG + log_write("player_dmusic::play() : got IDirectMusicSegment"); +#endif + pSeg->SetRepeats( (cfg_loop_type==2 || (cfg_loop_type==1 && MIDI_core::getFile()->loopstart)) ? (cfg_loop_infinite ? -1 : cfg_loop_count-1) : 0); + + dloaded=0; + if (dev->has_dls()) + if (SUCCEEDED(pSeg->SetParam(GUID_Download,-1,0,0,(void*)pPerf))) + dloaded=1; + + pSeg->SetStartPoint(0); +#ifdef USE_LOG + log_write("Activating port..."); +#endif + pPort->Activate(1); +#ifdef USE_LOG + log_write("IDirectMusicPort::Activate() returned"); +#endif + sysex_startup((SYSEXFUNC)SendSysex,pPerf); +#ifdef USE_LOG + log_write("IDirectMusicPerformance::PlaySegment()"); +#endif + pSS=0; + + DM_setvol(MIDI_core::player_getVol()); + + if (FAILED(pPerf->PlaySegment(pSeg,DMUS_SEGF_DEFAULT,0,&pSS))) + { +// Error("IDirectMusicPerformance::PlaySegment() failed."); + return 0; + } +#ifdef USE_LOG + log_write("IDirectMusicPerformance::PlaySegment() returned OK"); +#endif + + rtOffset=0; + if (pSS) + { + pSS->GetStartTime(&mtStart); + } + else + { +#ifdef USE_LOG + log_write("no segment starte. WTF ?"); +#endif + mtStart=0; + } + + pPerf->MusicToReferenceTime(mtStart,&rtStart); + + timer_id=SetTimer(0,0,33,(TIMERPROC)TimerProc); + + return 1; +} + + +static struct +{ + int name; + UINT flag; +} DMCAPZ[]= +{ +/* +#define DMUS_PC_DLS (0x00000001) // Supports DLS downloading and DLS level 1. +#define DMUS_PC_EXTERNAL (0x00000002) // External MIDI module. +#define DMUS_PC_SOFTWARESYNTH (0x00000004) // Software synthesizer. +#define DMUS_PC_MEMORYSIZEFIXED (0x00000008) // Memory size is fixed. +#define DMUS_PC_GMINHARDWARE (0x00000010) // GM sound set is built in, no need to download. +#define DMUS_PC_GSINHARDWARE (0x00000020) // GS sound set is built in. +#define DMUS_PC_XGINHARDWARE (0x00000040) // XG sound set is built in. +#define DMUS_PC_DIRECTSOUND (0x00000080) // Connects to DirectSound via a DSound buffer. +#define DMUS_PC_SHAREABLE (0x00000100) // Synth can be actively shared by multiple apps at once. +#define DMUS_PC_DLS2 (0x00000200) // Supports DLS2 instruments. +#define DMUS_PC_AUDIOPATH (0x00000400) // Multiple outputs can be connected to DirectSound for audiopaths. +#define DMUS_PC_WAVE (0x00000800) // Supports streaming and one shot waves. +*/ + + {STRING_DMCAPS_DLS1,DMUS_PC_DLS}, + {STRING_DMCAPS_DLS2,DMUS_PC_DLS2}, + {STRING_DMCAPS_SOFTSYNTH,DMUS_PC_SOFTWARESYNTH}, + {STRING_DMCAPS_GM,DMUS_PC_GMINHARDWARE}, + {STRING_DMCAPS_GS,DMUS_PC_GSINHARDWARE}, + {STRING_DMCAPS_XG,DMUS_PC_XGINHARDWARE}, +// {STRING_DMCAPS_DSOUND,DMUS_PC_DIRECTSOUND}, + {STRING_DMCAPS_SHARE,DMUS_PC_SHAREABLE}, +}; + +#define N_DMCAPZ (sizeof(DMCAPZ)/sizeof(DMCAPZ[0])) + +static struct +{ + int name; + UINT flag; +} DMCAPZ1[]= //effects +{ + {STRING_REVERB,DMUS_EFFECT_REVERB}, + {STRING_CHORUS,DMUS_EFFECT_CHORUS}, +}; + +#define N_DMCAPZ1 (sizeof(DMCAPZ1)/sizeof(DMCAPZ1[0])) + +int player_dmusic::settime(int tm) +{ + int rv; +#ifdef USE_LOG + log_write("player_dmusic::settime"); +#endif + rtOffset=UInt32x32To64(tm,10000); +#ifdef USE_LOG + log_write("calling IDirectMusicPerformance::Stop()"); +#endif + + if (!paused) + { +#ifdef USE_LOG + log_write("IDirectMusicPerformance::Stop() returned"); +#endif + if (pSS) {pSS->Release();pSS=0;} + } + + // not ideal but a pause, seek and unpause seems to resolve a failed seek issue + // with the 'Direct Music / Microsoft Synthesizer' and 'streamed' output mode + pause(); + + MUSIC_TIME time=GetMTforMS(pSeg,tm); + rv = SUCCEEDED( pSeg->SetStartPoint(time) ); + if (rv) mtOffset=time; + + unpause(); + + if (!paused) + { +#ifdef USE_LOG + log_write("calling IDirectMusicPerformance::PlaySegment()"); +#endif + pSS=0; + pPerf->PlaySegment(pSeg,0,0,&pSS); + if (pSS) pSS->GetStartTime(&mtStart); + pPerf->MusicToReferenceTime(mtStart,&rtStart); + } + return rv; +} + +BOOL test_ins_dls(DWORD patch,IDirectMusicCollection* pDLS) +{ + IDirectMusicInstrument *pi=0; + BOOL rv=0; + if (SUCCEEDED(pDLS->GetInstrument(patch,&pi))) + { + pi->Release(); + rv=1; + } + return rv; +} + +int test_drum_kit(DWORD no,IDirectMusicCollection* dls) +{ + DWORD p=no|0x80000000; + + if (pGM) + if (test_ins_dls(p,pGM)) return 1; + + if (pCDLS) + if (test_ins_dls(p,pCDLS)) return 1; + if (dls) + if (test_ins_dls(p,dls)) return 1; + + return 0; +} + +void do_dls_check(DWORD * i,IDirectMusicCollection * dls) +{ + +start: + if (pGM) + if (test_ins_dls(*i,pGM)) return; + if (pCDLS) + if (test_ins_dls(*i,pCDLS)) return; + if (dls) + if (test_ins_dls(*i,dls)) return; + //hack hack hack + + if (*i&0xFF00) + { + *i&=0xFF00FF; + goto start; + } + if (*i&0xFF0000) + { + *i&=0xFF; + return; + } +} + +static cfg_int cfg_show_all("dmusic_show_all",0); + +class MIDI_driver_dmusic : MIDI_driver +{ + bool dm_inited; + + virtual void do_init() + { + dm_inited=1; + try { +#ifdef USE_LOG + log_write("CoInitialize()"); +#endif + CoInitialize(0); +#ifdef USE_LOG + log_write("CoCreateInstance / IDirectMusic"); +#endif + IDirectMusic* pDM=0; + if (SUCCEEDED(CoCreateInstance(CLSID_DirectMusic,0,CLSCTX_INPROC,IID_IDirectMusic,(void**)&pDM)) && pDM) + { +#ifdef USE_LOG + log_write("IDirectMusic created OK"); +#endif + DMUS_PORTCAPS dmpc; + memset(&dmpc,0,sizeof(dmpc)); + dmpc.dwSize=sizeof(dmpc); + UINT np=0; + GUID def; + pDM->GetDefaultPort(&def); + + while(1) + { + if (pDM->EnumPort(np++,&dmpc)==S_FALSE) break; + if (dmpc.dwClass==DMUS_PC_OUTPUTCLASS && (cfg_show_all || (dmpc.dwType!=DMUS_PORT_WINMM_DRIVER && dmpc.dwType==DMUS_PORT_KERNEL_MODE) || (dmpc.dwFlags&DMUS_PC_DLS) )) + { + wchar_t name_mbs[2*DMUS_MAX_DESCRIPTION] = {0}; + wcsncpy(name_mbs,dmpc.wszDescription,256); + + string_w info; + { + if (dmpc.dwType<3) + { + int dmport_types[3]={STRING_DMCAPS_WINMM,STRING_DMCAPS_USERMODE,STRING_DMCAPS_WDM}; + info+=WASABI_API_LNGSTRINGW(STRING_DEVICE_TYPE); + info+=WASABI_API_LNGSTRINGW(dmport_types[dmpc.dwType]); + info+=L"\x0d\x0a"; + } + + UINT z; + for(z=0;z<N_DMCAPZ;z++) + { + if (dmpc.dwFlags & DMCAPZ[z].flag) + { + info+=WASABI_API_LNGSTRINGW(DMCAPZ[z].name); + info+=L"\x0d\x0a"; + } + } + UINT n_effects=0; + for(z=0;z<N_DMCAPZ1;z++) + { + if (dmpc.dwEffectFlags&DMCAPZ1[z].flag) + { + info+=n_effects ? L", " : WASABI_API_LNGSTRINGW(STRING_EFFECTS); + info+=WASABI_API_LNGSTRINGW(DMCAPZ1[z].name); + n_effects++; + } + } + if (n_effects) info+=L"\x0d\x0a"; + } + + add_device(new MIDI_device_dmusic(dmpc.guidPort,0,dmpc.dwFlags,name_mbs,info)); + if ((dmpc.dwFlags&DMUS_PC_DIRECTSOUND)&&(dmpc.dwFlags&DMUS_PC_SOFTWARESYNTH)) + { + wcscat(name_mbs,WASABI_API_LNGSTRINGW(IDS_WITH_OUTPUT)); + info+=WASABI_API_LNGSTRINGW(IDS_USES_WINAMPS_OUTPUT_PLUGINS); + add_device(new MIDI_device_dmusic(dmpc.guidPort,1,dmpc.dwFlags,name_mbs,info)); + } + } + } + pDM->Release(); + } + } catch(...) { + // bewm. + reset_devices(); + } + + } + virtual const wchar_t * get_name() {return L"DirectMusic";} + virtual GUID get_guid() {return dmusic_driver_guid;} +public: + MIDI_driver_dmusic() {dm_inited=0;} +protected: + void do_deinit() + { + if (!dm_inited) return; + if (pGM) + { + pGM->Release(); + pGM=0; + } + if (pCDLS) {pCDLS->Release();pCDLS=0;} + if (pLoader) {pLoader->Release();pLoader=0;} + if (pPort) PortKill(); + if (pDM) + { + pDM->Release(); + pDM=0; + } + if (pPerf) + { + pPerf->CloseDown(); + pPerf->Release(); + pPerf=0; + } + + CoUninitialize(); + } +}; + +static MIDI_driver_dmusic midi_driver_dmusic;
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/out_midi.cpp b/Src/Plugins/Input/in_midi/out_midi.cpp new file mode 100644 index 00000000..4308c292 --- /dev/null +++ b/Src/Plugins/Input/in_midi/out_midi.cpp @@ -0,0 +1,888 @@ +#include "main.h" +#include "seq.h" +#include <math.h> +#include "resource.h" + +// {76B6A32D-99A9-4d51-B1F5-DAD3F47FE75D} +static const GUID midiout_guid = +{ 0x76b6a32d, 0x99a9, 0x4d51, { 0xb1, 0xf5, 0xda, 0xd3, 0xf4, 0x7f, 0xe7, 0x5d } }; + +// {7F00BC9C-AEA3-472a-BBB9-D74ABD4FCA58} +static const GUID midiout_driver_guid = +{ 0x7f00bc9c, 0xaea3, 0x472a, { 0xbb, 0xb9, 0xd7, 0x4a, 0xbd, 0x4f, 0xca, 0x58 } }; + +static MMRESULT midiOutOpen_wrap(HMIDIOUT * hMo,int id) +{ + try { + return midiOutOpen(hMo,(UINT)id,0,0,CALLBACK_NULL); + } + catch(...) + { + return -1; + } +} + +class MIDI_device_midiout : public MIDI_device +{ +private: + GUID guid; + UINT id; + DWORD flags; + UINT type; + + virtual player_base * create(); + virtual GUID get_guid() {return guid;} + virtual bool is_default() {return id==(UINT)(-1);} + virtual bool volctrl_happy() {return (type==7) && (flags & MIDICAPS_VOLUME);} +public: + MIDI_device_midiout(UINT p_id,DWORD p_flags,UINT p_type,const wchar_t * p_name,const wchar_t * p_info) + { + id=p_id; + guid = midiout_guid; + *(DWORD*)&guid+=id; + flags = p_flags; + type = p_type; + set_name(p_name); + set_info(p_info); + } + inline DWORD get_flags() {return flags;} + inline DWORD get_id() {return id;} +}; + + +static void midiout_sysex(HMIDIOUT hMo,BYTE* p,UINT len) +{ + MIDIHDR h; + ZeroMemory(&h,sizeof(h)); + h.dwBytesRecorded=h.dwBufferLength=len; + h.lpData=(char*)p; + if (FAILED(midiOutPrepareHeader(hMo,&h,sizeof(h)))) { +#ifdef USE_LOG + log_write("unable to send sysex"); +#endif + return; + } + if (SUCCEEDED(midiOutLongMsg(hMo,&h,sizeof(h)))) + { + while(!(h.dwFlags&MHDR_DONE)) MIDI_callback::Idle(); + } + midiOutUnprepareHeader(hMo,&h,sizeof(h)); + + //log_write("sysex sent OK"); +} + +void midiout_sysex(HMIDIOUT hMo,BYTE* p,UINT len); + +static void sysex_startup_midiout(UINT m_id) +{ + if (need_sysex_start()) + { +// MessageBox(GetActiveWindow(),"blah",0,0); + HMIDIOUT hMo; + MMRESULT r=midiOutOpen_wrap(&hMo,m_id); + if (!r) + { + sysex_startup((SYSEXFUNC)midiout_sysex,hMo); + midiOutClose(hMo); + } + } +} + +class midiout_volctrl +{ +private: + HMIDIOUT hMo; + int vol,pan; + void _setvol(); + static UINT map_vol(UINT volume,UINT scale); + +public: + void volctrl_init(HMIDIOUT,MIDI_device_midiout*); + int volctrl_setvol(int); + int volctrl_setpan(int); +}; + +class player_midiout : public seq_base, private midiout_volctrl +{ +public: + virtual ~player_midiout(); + virtual int setvol(int i) {return volctrl_setvol(i);}; + virtual int setpan(int i) {return volctrl_setpan(i);}; + + player_midiout(MIDI_device_midiout * p_dev) + { + dev=p_dev; + hMo=0; + } + + int play(); +private: + MIDI_device_midiout * dev; + + HMIDIOUT hMo; + + virtual void seq_shortmsg(DWORD msg) {midiOutShortMsg(hMo,msg);} + virtual void seq_sysex(BYTE* ptr,UINT len) {midiout_sysex(hMo,ptr,len);} + virtual int seq_play_start(); + virtual void seq_play_stop(); +}; + +int player_midiout::seq_play_start() +{ + return 1; +} + +void player_midiout::seq_play_stop() +{ + if (hMo) + { +#ifdef USE_LOG + log_write("midiOutClose"); +#endif + DWORD r=midiOutClose(hMo); + if (r==MIDIERR_STILLPLAYING) + { + log_write("still playing (?), calling midiOutReset"); + midiOutReset(hMo); + r=midiOutClose(hMo); + } +#ifdef USE_LOG + if (r) log_write("warning: unable to close midiOut"); + else log_write("midiOut closed OK"); +#endif + } + hMo=0; +} + +int player_midiout::play() +{ + DWORD r=midiOutOpen_wrap(&hMo,dev->get_id()); + if (r) + { + if (r!=-1) MIDI_core::MM_error(r); + return 0; + } + + + volctrl_init(hMo,dev); + + if (!seq_cmd_start(0)) + { + midiOutClose(hMo); + hMo=0; + return 0; + } + return 1; +} + +player_midiout::~player_midiout() +{ +#ifdef USE_LOG + log_write("shutting down midiOut"); +#endif + seq_cmd_stop(); +} + + +class MIDI_driver_midiout : MIDI_driver +{ + virtual void do_init() + { + MIDIOUTCAPSW caps; + UINT n_mo_dev=midiOutGetNumDevs()+1; + UINT n; + for(n=0;n<n_mo_dev;n++) + { + midiOutGetDevCapsW(n-1,&caps,sizeof(MIDIOUTCAPSW)); + //d.id = TYPE_MIDIOUT | n; + //d.name=(char*)_strdup(caps.szPname); + string_w info; + { + wchar_t moo[128], *t=0; + switch(caps.wTechnology) + { + case MOD_FMSYNTH: + t=WASABI_API_LNGSTRINGW_BUF(STRING_MOCAPS_FM,moo,128); + break; + case MOD_MAPPER: + t=WASABI_API_LNGSTRINGW_BUF(STRING_MOCAPS_MAPPER,moo,128); + break; + case MOD_MIDIPORT: + t=WASABI_API_LNGSTRINGW_BUF(STRING_MOCAPS_HWPORT,moo,128); + break; + case MOD_SQSYNTH: + t=WASABI_API_LNGSTRINGW_BUF(STRING_MOCAPS_SQUARE,moo,128); + break; + case MOD_SYNTH: + t=WASABI_API_LNGSTRINGW_BUF(STRING_MOCAPS_SYNTH,moo,128); + break; + case 6: + t=WASABI_API_LNGSTRINGW_BUF(STRING_MOCAPS_WAVETABLE,moo,128); + break; + case 7: + t=WASABI_API_LNGSTRINGW_BUF(STRING_DMCAPS_SOFTSYNTH,moo,128); + break; + default: + wsprintfW(moo,WASABI_API_LNGSTRINGW(STRING_UNKNOWN),caps.wTechnology); + t=moo; + break; + } + if (t) + { + info+=WASABI_API_LNGSTRINGW(STRING_DEVICE_TYPE); + info+=t; + info+=L"\x0d\x0a"; + } + if (caps.dwSupport & MIDICAPS_STREAM) + { + info+=WASABI_API_LNGSTRINGW(STRING_DIRECT_MIDISTREAM); + info+=L"\x0d\x0a"; + } + } + + add_device(new MIDI_device_midiout(n-1,caps.dwSupport,caps.wTechnology,caps.szPname,info)); + } + } + virtual const wchar_t * get_name() {return L"midiOut";} + virtual bool is_default() {return 1;} + virtual GUID get_guid() {return midiout_driver_guid;} +}; + +static MIDI_driver_midiout midi_driver_midiout; + +#define WM_SEEK (WM_USER+4) + +#define GET_TIME timeGetTime() + +int IS_SPEC_C(int x); + +#define BUF_MAX 0x1000//0x3C00 +#define BUF_MAX_F (BUF_MAX+0x40) +#define N_BUFS 4 +#define BUF_MASK (N_BUFS-1) + +typedef struct +{ + MIDIHDR h; + DWORD data[BUF_MAX_F]; +} MIDIBUF; + +class player_midistream : public player_base, private midiout_volctrl +{ +public: + int gettime(); + int settime(int); + void pause(); + void unpause(); + virtual int setvol(int i) {return volctrl_setvol(i);}; + virtual int setpan(int i) {return volctrl_setpan(i);}; + int play(); + player_midistream(MIDI_device_midiout *); + ~player_midistream(); +private: + UINT renderbuf(MIDIHDR* buf,DWORD ts,UINT start,UINT end); + UINT renderbuf_seek(UINT start,UINT end); + void renderbuf_wait(MIDIHDR* buf,UINT len); + DWORD pos4time(DWORD t); + + MIDI_device_midiout * dev; + UINT m_id; + UINT c_loop; + CSysexMap* smap; + HMIDISTRM hMo; + UINT n_events; + MIDI_EVENT* events; + DWORD tm_ofs,p_time; + UINT loop_start,total_len; + + MIDIBUF hdrs[N_BUFS]; + MIDIBUF seekbuf; + UINT cur_buf; + UINT in_mm,n_free; + DWORD ct,cp; + bool got_eof,paused,quitting; + UINT seek_to; + + HWND wnd; + DWORD trd_id; + + void do_bufs(); + void buf_done(MIDIHDR*); + static LRESULT WINAPI midiOutProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp); +}; +/* +static void _sysex(player_midistream* pl,BYTE* p,UINT len) +{//assumes thread to be OK + HMIDISTRM hMo=pl->hMo; + MIDIHDR h; + ZeroMemory(&h,sizeof(h)); + h.dwUser=1; + DWORD l=12+len; + if (l&3) l=(l+4)&~3; + DWORD* ev=(DWORD*)alloca(l); + ev[0]=ev[1]=0; + ev[2]=MEVT_F_LONG|len; + memcpy(ev+3,p,len); + h.dwBytesRecorded=h.dwBufferLength=l; + h.lpData=(char*)ev; + if (FAILED(midiOutPrepareHeader((HMIDIOUT)hMo,&h,sizeof(h)))) return; + if (FAILED(midiStreamOut(hMo,&h,sizeof(h)))) + { + midiOutUnprepareHeader((HMIDIOUT)hMo,&h,sizeof(h)); + return; + } + pl->in_mm++; + do_messages(pl->wnd,(bool*)&h.dwUser); + log_write("sysex sent OK"); +}*/ + +DWORD player_midistream::pos4time(DWORD t) +{ + DWORD r=0; + while(r<n_events && events[r].tm<t) r++; + return r; +} + +static cfg_int cfg_midistream_quick_seek("midistream_quick_seek",0); + +void player_midistream::do_bufs() +{ + if (seek_to!=-1) + { + UINT sp=pos4time(ct=seek_to); + if (!cfg_midistream_quick_seek) + { + UINT st=cp; + if (sp<cp) st=0; + if (renderbuf_seek(st,sp)!=-1) + { + if (!midiOutPrepareHeader((HMIDIOUT)hMo,&seekbuf.h,sizeof(MIDIHDR))) + { + if (!midiStreamOut(hMo,&seekbuf.h,sizeof(MIDIHDR))) + { + in_mm++; + } + else midiOutUnprepareHeader((HMIDIOUT)hMo,&seekbuf.h,sizeof(MIDIHDR)); + } + } + } + cp=sp; + seek_to=-1; + } + while(n_free && !got_eof) + { + MIDIHDR* hdr=&hdrs[cur_buf].h; + cp=renderbuf(hdr,ct,cp,-1); + if (cp==-1) + { + if (loop_start!=-1 && c_loop>1) + { + c_loop--; + cp=pos4time(ct=loop_start); + continue; + } + else + { + got_eof=1; + if (cfg_eof_delay) + { + renderbuf_wait(hdr,cfg_eof_delay); + } + else break; + + } + } + if (midiOutPrepareHeader((HMIDIOUT)hMo,hdr,sizeof(MIDIHDR))) + { + got_eof=1; + break; + } + if (midiStreamOut(hMo,hdr,sizeof(MIDIHDR))) + { + got_eof=1; + break; + } + cur_buf=(cur_buf+1)&BUF_MASK; + in_mm++; + n_free--; + if (!got_eof) + ct=cp ? events[cp-1].tm : 0; + } +} + + +UINT player_midistream::renderbuf(MIDIHDR* buf,DWORD ts,UINT start,UINT end) +{ + UINT n=start; + UINT p=0; + DWORD* pEv=(DWORD*)buf->lpData; + UINT c_t=ts; + while(n<end && n<n_events && p<(BUF_MAX-3)) + { + int dt=events[n].tm-c_t; + if (dt<0) dt=0; + pEv[p++]=dt; + c_t+=dt; + pEv[p++]=0; + if (events[n].ev&0x80000000) + { + SYSEX_ENTRY* se=&smap->events[events[n].ev&0x7FFFFFFF]; + if (p+(se->len>>2)>=BUF_MAX) + { + p-=2; + break; + } + pEv[p++]=MEVT_F_LONG|se->len; + DWORD d=se->len>>2; + if (se->len&3) + { + pEv[p+(d++)]=0; + } + memcpy(pEv+p,smap->data+se->ofs,se->len); + p+=d; + } + else + { + pEv[p++]=events[n].ev; + } + n++; + } + if (p==0) + return -1; + buf->dwBufferLength=p<<2; + buf->dwBytesRecorded=p<<2; + buf->dwFlags=0; + return n; +} + +void player_midistream::renderbuf_wait(MIDIHDR* buf,UINT len) +{ + UINT p=0; + DWORD* pEv=(DWORD*)buf->lpData; + pEv[p++]=len<<3; + pEv[p++]=0; + pEv[p++]=MEVT_NOP<<24; + buf->dwBufferLength=p<<2; + buf->dwBytesRecorded=p<<2; + buf->dwFlags=0; +} + +UINT player_midistream::renderbuf_seek(UINT start,UINT end) +{ + BYTE ins_tab[16] = {0}; + memset(ins_tab,-1,sizeof(ins_tab)); + BYTE ctrl_tab[16][128] = {0}; + memset(ctrl_tab,-1,sizeof(ctrl_tab)); + UINT n=start; + DWORD* pEv=(DWORD*)seekbuf.h.lpData; + while(n<end) + { + DWORD ec=events[n].ev; + if (ec&0x80000000) {n++;continue;} + UINT ch,cd; + + ch=ec&0xF; + cd=ec&0xF0; + if (cd==0xB0) ctrl_tab[ch][(ec>>8)&0x7F]=(BYTE)(ec>>16); + else if (cd==0xC0) ins_tab[ch]=(BYTE)(ec>>8); + n++; + } + UINT c; + UINT p=0; + for(c=0;c<16;c++) + { + for(n=0;n<128;n++) + { + if (!(ctrl_tab[c][n]&0x80)) + { + pEv[p++]=0; + pEv[p++]=0; + pEv[p++]=0xB0|c|(n<<8)|(ctrl_tab[c][n]<<16); + } + if (p>=BUF_MAX) + goto q; + } + if (!(ins_tab[c]&0x80)) + { + pEv[p++]=0; + pEv[p++]=0; + pEv[p++]=0xC0|c|(ins_tab[c]<<8); + } + if (p>=BUF_MAX) + goto q; + } +q: + + if (p==0) return -1; + + seekbuf.h.dwBufferLength=p<<2; + seekbuf.h.dwBytesRecorded=p<<2; + seekbuf.h.dwFlags=0; + return n; +} + +void player_midistream::buf_done(MIDIHDR* h) +{ + in_mm--; + midiOutUnprepareHeader((HMIDIOUT)hMo,h,sizeof(MIDIHDR)); + if (h->dwUser) + { + h->dwUser=0; + return; + } + if (h==&seekbuf.h) return; + n_free++; + if (quitting) return; + + if (!in_mm && got_eof) + MIDI_core::Eof(); + else if (!got_eof) + { + do_bufs(); + } +} + +LRESULT WINAPI player_midistream::midiOutProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) +{ + if (msg==MM_MOM_DONE) + { + player_midistream* p=(player_midistream*)GetWindowLong(wnd,0); + if (p) p->buf_done((MIDIHDR*)lp); + } + else if (msg==WM_SEEK) + { + player_midistream* p=(player_midistream*)GetWindowLong(wnd,0); + if (p) p->settime(lp); + } + return DefWindowProc(wnd,msg,wp,lp); +} + +player_midistream::player_midistream(MIDI_device_midiout * p_dev) +{ + dev=p_dev; + m_id=0; + c_loop=0; + smap=0; + hMo=0; + n_events=0; + events=0; + tm_ofs=0;p_time=0; + loop_start=0; + total_len=0; + memset(&hdrs,0,sizeof(hdrs)); + memset(&seekbuf,0,sizeof(seekbuf)); + cur_buf=0; + in_mm=0;n_free=0; + ct=0;cp=0; + got_eof=0; + paused=0; + quitting=0; + seek_to=0; + + wnd=0; + trd_id=0; + + + static ATOM cb_class; + if (!cb_class) cb_class=do_callback_class(midiOutProc); + + wnd=create_callback_wnd(cb_class,this); + c_loop=cfg_loop_infinite ? -1 : cfg_loop_count; +} + +player_base* MIDI_device_midiout::create() +{ + if (cfg_playback_mode) + { + player_midiout *p=new player_midiout(this); + if (p) + { + if (!p->play()) {delete p;p=0;} + } + return p; + } + else + { + player_midistream *p=new player_midistream(this); + if (p) + { + if (!p->play()) {delete p;p=0;} + } + return p; + } +} + +//extern bool cfg_alt_sysex; + +int player_midistream::play() +{ + trd_id=GetCurrentThreadId(); + UINT n; + for(n=0;n<N_BUFS;n++) + { + hdrs[n].h.lpData=(char*)hdrs[n].data; + } + seekbuf.h.lpData=(char*)seekbuf.data; + + //bool alt_sysex=cfg_alt_sysex; + + //if (alt_sysex) + { + sysex_startup_midiout(dev->get_id()); + } +#ifdef USE_LOG + log_write("starting midiOut / streamed"); +#endif + { + UINT id=dev->get_id(); + DWORD err=midiStreamOpen(&hMo,&id,1,(DWORD)wnd,0,CALLBACK_WINDOW); + if (err) + { + MIDI_core::MM_error(err); + return 0; + } + MIDIPROPTIMEDIV td; + td.cbStruct=sizeof(td); + td.dwTimeDiv=1*8;//tix / q + err=midiStreamProperty(hMo,(BYTE*)&td,MIDIPROP_SET|MIDIPROP_TIMEDIV); + if (err) + { + midiStreamClose(hMo); + MIDI_core::MM_error(err); + return 0; + } + + MIDIPROPTEMPO tempo; + tempo.cbStruct=sizeof(tempo); + tempo.dwTempo=1000;//ns / q + err=midiStreamProperty(hMo,(BYTE*)&tempo,MIDIPROP_SET|MIDIPROP_TEMPO); + if (err) + { + midiStreamClose(hMo); + MIDI_core::MM_error(err); + return 0; + } + } + + events=do_table(MIDI_core::getFile(),8,&n_events,&loop_start,0); + if (!events) + { + midiStreamClose(hMo); + hMo=0; + return 0; + } + total_len=events[n_events-1].tm>>3; + + if (!cfg_nosysex && MIDI_core::getFile()->smap && MIDI_core::getFile()->smap->pos) + { + smap=MIDI_core::getFile()->smap; + } + else smap=0; + + paused=0; + + volctrl_init((HMIDIOUT)hMo,dev); + + midiStreamPause(hMo); + + //sysex_startup((SYSEXFUNC)midiout_sysex,hMo); + + seek_to=-1; + + tm_ofs=GET_TIME; + + cur_buf=0; + in_mm=0; + n_free=N_BUFS; + + ct=cp=0; + + do_bufs(); + +#ifdef USE_LOG + log_write("started OK"); +#endif + midiStreamRestart(hMo); + + return 1; +} + +player_midistream::~player_midistream() +{ +// bool alt_sysex=cfg_alt_sysex; +#ifdef USE_LOG + log_write("shutting down midistream"); +#endif + if (hMo) + { + //ASSERT(trd_id!=GetCurrentThreadId()); + quitting=1; +#ifdef USE_LOG + log_write("midiStreamStop"); +#endif + midiStreamStop(hMo); + +// do_messages(wnd,(bool*)&in_mm); + if (n_free!=N_BUFS) + { + UINT n; + for(n=0;n<N_BUFS;n++) + { + if (hdrs[n].h.dwFlags&MHDR_PREPARED) + { + midiOutUnprepareHeader((HMIDIOUT)hMo,&hdrs[n].h,sizeof(MIDIHDR)); + in_mm--; + n_free++; + } + } + } + +#ifdef HUNT_LEAKS + if (n_free!=N_BUFS) Warning("Not all buffers collected."); +#endif +#ifdef USE_LOG + log_write("midiStreamClose"); +#endif + midiStreamClose(hMo); + //if (midiStreamClose(hMo)) Warning(STRING_MIDISTREAM_WARNING); + } + if (events) free(events); + if (wnd) DestroyWindow(wnd); +#ifdef USE_LOG + log_write("midistream shut down"); +#endif +} + +int player_midistream::gettime() +{ + DWORD ret; + if (paused) ret=p_time; + else if (!tm_ofs) ret=0; + else + { + ret=GET_TIME-tm_ofs; + if (loop_start!=-1 && ret>total_len) + { + UINT _ls=loop_start>>3; + ret=(ret-_ls)%(total_len-_ls)+_ls; + } + } + return ret; +} + +int player_midistream::settime(int tm) +{ + if (!paused) + { + if (trd_id==GetCurrentThreadId()) + { + seek_to=tm<<3; + got_eof=0; + tm_ofs=GET_TIME-tm; + midiStreamStop(hMo); + midiStreamPause(hMo); + quitting=1; + do_messages(wnd,(bool*)&in_mm); + quitting=0; + do_bufs(); + midiStreamRestart(hMo); + } + else PostMessage(wnd,WM_SEEK,0,tm); + } + else + { + p_time=tm; + } + return 1; +} + +void player_midistream::pause() +{ + p_time=GET_TIME-tm_ofs; + paused=1; + midiStreamPause(hMo); +} + +void player_midistream::unpause() +{ + tm_ofs=GET_TIME-p_time; + paused=0; + if (seek_to!=-1) + { + midiStreamStop(hMo); + midiStreamPause(hMo); + if (trd_id==GetCurrentThreadId()) + { + quitting=1; + do_messages(wnd,(bool*)&in_mm); + quitting=0; + do_bufs(); + } + } + midiStreamRestart(hMo); +} + + + + +UINT midiout_volctrl::map_vol(UINT volume,UINT scale) +{ + double _vol=volume>0 ? 20*log10((double)volume/(double)scale) : -60.0;//in negative db + _vol=_vol/60.0+1; + if (_vol<0) _vol=0; + return (UINT)(_vol*(double)scale); +} + + +void midiout_volctrl::_setvol() +{ + DWORD _vol=257*vol; + DWORD vol1=_vol,vol2=_vol; + if (pan!=666) + { + if (pan<0) + { + vol2=(vol2*(128+pan))>>7; + } + else if (pan>0) + { + vol1=(vol1*(128-pan))>>7; + } + } + if (cfg_logvol) + { + vol1=map_vol(vol1,0xFFFF); + vol2=map_vol(vol2,0xFFFF); + } + midiOutSetVolume((HMIDIOUT)hMo,(vol2<<16)|vol1); +} + +void midiout_volctrl::volctrl_init(HMIDIOUT _hMo,MIDI_device_midiout * dev) +{ + hMo=_hMo; + pan=(dev->get_flags()&MIDICAPS_LRVOLUME) ? MIDI_core::player_getPan() : 666; + vol=(dev->get_flags()&MIDICAPS_VOLUME) ? MIDI_core::player_getVol() : 666; + _setvol(); +} + +int midiout_volctrl::volctrl_setvol(int _vol) +{ + if (vol!=666) + { + vol=_vol; + _setvol(); + return 1; + } + else return 0; +} + +int midiout_volctrl::volctrl_setpan(int _pan) +{ + if (pan!=666) + { + pan=_pan; + _setvol(); + return 1; + } + else return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/resource.h b/Src/Plugins/Input/in_midi/resource.h new file mode 100644 index 00000000..ae56e0ee --- /dev/null +++ b/Src/Plugins/Input/in_midi/resource.h @@ -0,0 +1,224 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by in_midi.rc +// +#define IDS_NULLSOFT_MIDI_PLAYER_OLD 0 +#define STRING_FILES_OTHER 2 +#define IDRESET 3 +#define IDS_TO_ENABLE_LYRICS_DISPLAY 3 +#define IDS_INFORMATION 4 +#define STRING_INCOMPLETE 5 +#define STRING_TRACKS_FMT 6 +#define IDS_MIDIS_ARE_NOT_BURNABLE 7 +#define IDS_ABOUT_TITLE 9 +#define IDS_NONE 10 +#define IDS_STREAMED 11 +#define IDS_IMMEDIATE 12 +#define IDS_CONFIGURATION 13 +#define IDS_TYPE 14 +#define IDS_PREFS_DEVICE 15 +#define IDS_PREFS_DISPLAY 16 +#define IDS_PREFS_SAMPLING 17 +#define IDS_PREFS_DIRECTMUSIC 18 +#define IDS_PREFS_MISC 19 +#define IDS_PREFS_FILE_TYPES 20 +#define IDS_PREFS_FILES 21 +#define IDS_PREFS_HARDWARE_SETUP 22 +#define IDS_UNABLE_TO_LOAD_FILE 23 +#define STRING_RETRIEVING_FILE 24 +#define STRING_URL_ERROR 25 +#define STRING_UNKNOWN_MMSYSTEM 26 +#define STRING_MIDI_INFO_FMT2 27 +#define STRING_MIDI_INFO_FMT1 28 +#define STRING_BYTES_FMT 29 +#define STRING_WRITE_ERROR_FMT 30 +#define STRING_INFO_FORMAT_FMT 31 +#define STRING_RMI_INFO_FMT 32 +#define STRING_CONFIG_RESET 33 +#define STRING_STEREO 34 +#define STRING_MONO 35 +#define STRING_VOLUME_AUTO 36 +#define STRING_VOLUME_DRIVER_SPECIFIC 37 +#define STRING_VOLUME_NONE 38 +#define STRING_SAMP_SRC_DEFAULT 39 +#define STRING_LOOP1 40 +#define STRING_LOOP2 41 +#define STRING_LOOP3 42 +#define STRING_UNKNOWN 43 +#define STRING_DIRECT_MIDISTREAM 44 +#define STRING_MOCAPS_WAVETABLE 45 +#define STRING_MOCAPS_SYNTH 46 +#define STRING_MOCAPS_SQUARE 47 +#define STRING_MOCAPS_MAPPER 48 +#define STRING_MOCAPS_HWPORT 49 +#define STRING_MOCAPS_FM 50 +#define STRING_EFFECTS 51 +#define STRING_DEVICE_TYPE 52 +#define STRING_DMCAPS_WDM 53 +#define STRING_DMCAPS_USERMODE 54 +#define STRING_DMCAPS_WINMM 55 +#define STRING_CHORUS 56 +#define STRING_REVERB 57 +#define STRING_DMCAPS_SHARE 58 +#define STRING_DMCAPS_DSOUND 59 +#define STRING_DMCAPS_XG 60 +#define STRING_DMCAPS_GS 61 +#define STRING_DMCAPS_GM 62 +#define STRING_DMCAPS_SOFTSYNTH 63 +#define STRING_DMCAPS_DLS2 64 +#define STRING_DMCAPS_DLS1 65 +#define STRING_FILES_SMF 66 +#define STRING_FILES_CLONE 67 +#define STRING_FILES_COMPRESSED 68 +#define IDS_SYSEX_DATA 69 +#define IDS_MIDI_HARDWARE_PRESETS 70 +#define IDS_DLS_FILES 71 +#define IDS_MIDI_FILES 72 +#define IDS_COMPRESSED_MIDI_FILES 73 +#define IDS_RMI_FILES 74 +#define IDS_COMPRESSED_RMI_FILES 75 +#define IDS_WITH_OUTPUT 76 +#define IDS_STRING105 77 +#define IDS_USES_WINAMPS_OUTPUT_PLUGINS 77 +#define STRING_MS_FMT 78 +#define STRING_BIT_FMT 79 +#define IDS_FAMILY_STRING_MIDI 80 +#define IDS_FAMILY_STRING_KARAOKE_MIDI 81 +#define IDS_FAMILY_STRING_HMI_MIDI 82 +#define IDS_FAMILY_STRING_EXTENDED_MIDI 83 +#define IDS_FAMILY_STRING_MSS_MIDI 84 +#define IDS_FAMILY_STRING_FINALE_MIDI 85 +#define IDS_FAMILY_STRING_CREATIVE_MIDI 86 +#define IDS_FAMILY_STRING_GENERAL_MIDI_DUMP 87 +#define IDS_FAMILY_STRING_COMPRESSED_MIDI 88 +#define IDS_FAMILY_STRING_COMPRESSED_HMI_MIDI 89 +#define IDS_ABOUT 90 +#define IDS_ABOUT_TEXT 90 +#define IDD_CONFIG 101 +#define IDD_INFO 102 +#define IDD_CONFIG1 117 +#define IDD_CONFIG2 118 +#define IDD_CONFIG3 119 +#define IDD_CONFIG4 120 +#define IDD_CONFIG5 121 +#define IDD_CONFIG6 122 +#define IDD_SYSEX 123 +#define IDD_EXT_IMM 124 +#define IDD_CONFIG7 125 +#define IDD_RMI_SHIZ 127 +#define IDD_CONFIG8 128 +#define IDD_LYRICS 129 +#define IDC_PORT 1001 +#define IDC_MS 1011 +#define IDC_TIX 1012 +#define IDC_FORMAT 1013 +#define IDC_NTRAX 1014 +#define IDC_TRAX 1017 +#define IDC_COPYRIGHT 1018 +#define IDC_COMMENT 1019 +#define IDC_SUBJECT 1020 +#define IDC_FREQ 1030 +#define IDC_REVERB 1031 +#define IDC_DLS_CB 1032 +#define IDC_DLS 1033 +#define IDC_DLS_B 1034 +#define IDC_CHORUS 1036 +#define IDC_SAVE 1040 +#define IDC_RMI_CRAP 1041 +#define IDC_SAMPLING_ENABLED 1041 +#define IDC_RTFM 1050 +#define IDC_EDIT1 1052 +#define IDC_ARTIST 1054 +#define IDC_LOOP 1055 +#define IDC_DATE 1055 +#define IDC_ALBUM 1056 +#define IDC_LOOP_S 1057 +#define IDC_SOFTWARE 1057 +#define IDC_LOOP_S2 1058 +#define IDC_TRACK 1058 +#define IDC_FSIZE 1059 +#define IDC_ENGINEER 1059 +#define IDC_COMPOSER 1060 +#define IDC_NAME 1071 +#define IDC_SAMPLING_DSP 1072 +#define IDC_SAMPLING_OUTPUT 1073 +#define IDC_STATIC1 1075 +#define IDC_STATIC2 1077 +#define IDC_STATIC3 1079 +#define IDC_DEV_INFO 1081 +#define IDC_INFINITE 1084 +#define IDC_LOOP_T 1086 +#define IDC_LOOP_SP 1088 +#define IDC_LOOP_S3 1089 +#define IDC_WAVEIN 1090 +#define IDC_WAVEIN_SRC 1094 +#define IDC_HACK_NO_SYSEX 1096 +#define IDC_PLAYBACK_METHOD 1107 +#define IDC_TAB 1108 +#define IDC_STATIC_CLN 1124 +#define IDC_STATIC_MOS 1126 +#define IDC_IMP_F 1127 +#define IDC_EXP_F 1128 +#define IDC_IMP_PR 1129 +#define IDC_EXP_PR 1130 +#define IDC_SYSEX_EDIT 1131 +#define IDC_SYSEX_DELETE 1132 +#define IDC_SYSEX_ADD 1133 +#define IDC_SYSEX_LIST 1134 +#define IDC_DELAY 1135 +#define IDC_SYSEX_UP 1136 +#define IDC_SPIN1 1137 +#define IDC_SYSEX_DOWN 1138 +#define IDC_STATIC_MOS1 1139 +#define IDC_NOVOL 1140 +#define IDC_DM_IMM 1141 +#define IDC_HACK_DM_RESETS 1142 +#define IDC_TEMPO 1145 +#define IDC_SHOW_PANEL 1146 +#define IDC_TDISP 1147 +#define IDC_ALL_ON 1180 +#define IDC_ALL_OFF 1181 +#define IDC_GMRESET 1182 +#define IDC_GSRESET 1183 +#define IDC_DM_KEEP_PORT 1184 +#define IDC_XGRESET 1185 +#define IDC_EXTS_LIST 1207 +#define IDC_EXTS_ED 1208 +#define IDC_SYSEX1 1215 +#define IDC_SYSEX1_SEND 1216 +#define IDC_VOLMODE 1217 +#define IDC_HARDWARE_RESET 1218 +#define IDC_SYSEX2 1221 +#define IDC_SYSEX2_SEND 1222 +#define IDC_NOINS 1223 +#define IDC_HACKTRACK 1225 +#define IDC_HACK_DLS_DRUMS 1226 +#define IDC_HACK_XG_DRUMS 1228 +#define IDC_HACK_DLS_INSTRUMENTS 1229 +#define IDC_WAVEIN_SR 1234 +#define IDC_WAVEIN_CH 1235 +#define IDC_WAVEIN_BPS 1236 +#define IDC_WAVEIN_S2 1237 +#define IDC_DISP 1238 +#define IDC_GENRE 1241 +#define IDC_EOF_DELAY 1242 +#define IDC_EOF_DELAY_SPIN 1243 +#define IDC_LOGVOL 1244 +#define IDC_BLAH 1245 +#define IDC_BMPVIEW 1251 +#define IDC_SAMP_REVERT 1261 +#define IDC_RMI_DEF 2000 +#define IDC_RMI_FMT 2002 +#define IDC_LYRICS_ENABLED 2003 +#define IDS_NULLSOFT_MIDI_PLAYER 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 109 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_midi/sampling.cpp b/Src/Plugins/Input/in_midi/sampling.cpp new file mode 100644 index 00000000..06c34653 --- /dev/null +++ b/Src/Plugins/Input/in_midi/sampling.cpp @@ -0,0 +1,333 @@ +#include "main.h" +#include <ks.h> +#include <ksmedia.h> +#include <malloc.h> + +static void make_wfx(WAVEFORMATEX * wfx,int srate,int nch,int bps) +{ + wfx->wFormatTag=WAVE_FORMAT_PCM; + wfx->nChannels=nch; + wfx->nSamplesPerSec=srate; + wfx->nAvgBytesPerSec=srate*nch*(bps>>3); + wfx->nBlockAlign=nch * (bps>>3); + wfx->wBitsPerSample=bps; + wfx->cbSize=0; +} + +static void make_wfxe(WAVEFORMATEXTENSIBLE * wfx,int srate,int nch,int bps) +{ + make_wfx(&wfx->Format,srate,nch,bps); + wfx->Format.wFormatTag=WAVE_FORMAT_EXTENSIBLE; + wfx->Format.cbSize=22; + wfx->Samples.wReserved=0; + wfx->dwChannelMask=0; + wfx->SubFormat=KSDATAFORMAT_SUBTYPE_PCM; +} + +#ifndef IN_MIDI_NO_WAVEIN_SOURCE + +extern cfg_int cfg_samp_revert; + +#define MMBOOL MIXERCONTROLDETAILS_BOOLEAN + +static MMBOOL *do_mixer_shit(DWORD param,DWORD type,BOOL store,UINT input,MMBOOL *tab) +{ + UINT id=0; + mixerGetID((HMIXEROBJ)param,&id,type); + + MIXERCAPS caps; + mixerGetDevCaps(id,&caps,sizeof(caps)); + MIXERLINE ml; + ZeroMemory(&ml,sizeof(ml)); + ml.cbStruct=sizeof(ml); + ml.dwComponentType=MIXERLINE_COMPONENTTYPE_DST_WAVEIN; + + mixerGetLineInfo((HMIXEROBJ)id,&ml,MIXER_GETLINEINFOF_COMPONENTTYPE|MIXER_OBJECTF_MIXER); + + MIXERLINECONTROLS cs; + MIXERCONTROL c; + ZeroMemory(&cs,sizeof(cs)); + cs.cbStruct=sizeof(cs); + cs.cControls=1; + cs.dwLineID=ml.dwLineID; + cs.dwControlType=MIXERCONTROL_CONTROLTYPE_MUX; + cs.cbmxctrl=sizeof(c); + cs.pamxctrl=&c; + ZeroMemory(&c,sizeof(c)); + c.cbStruct=sizeof(c); + + if (!mixerGetLineControls((HMIXEROBJ)id,&cs,MIXER_OBJECTF_MIXER|MIXER_GETLINECONTROLSF_ONEBYTYPE)) + { + if (store) + { + if (!tab) + { + tab=(MMBOOL*)alloca(sizeof(MMBOOL)*c.cMultipleItems); + memset(tab,0,sizeof(MMBOOL)*c.cMultipleItems); + tab[input].fValue=1; + } + } + else + { + if (!tab) tab=new MMBOOL[c.cMultipleItems]; + } + + if (tab) + { + MIXERCONTROLDETAILS d; + d.cbStruct=sizeof(d); + d.dwControlID=c.dwControlID; + d.cbDetails=sizeof(MMBOOL); + d.cChannels=ml.cChannels; + d.cMultipleItems=c.cMultipleItems; + d.paDetails=tab; + + if (store) mixerSetControlDetails((HMIXEROBJ)id,&d,MIXER_SETCONTROLDETAILSF_VALUE |MIXER_OBJECTF_MIXER); + else mixerGetControlDetails((HMIXEROBJ)id,&d,MIXER_GETCONTROLDETAILSF_VALUE |MIXER_OBJECTF_MIXER); + } + } + return tab; +} +#endif + +class CVis : public CStream +{ +private: +#ifndef IN_MIDI_NO_WAVEIN_SOURCE + MMBOOL * old_settings; + UINT wavein_id; + void src_init() + { + wavein_id=(UINT)cfg_wavein_dev; + if (cfg_wavein_src) + { + if (cfg_samp_revert) old_settings = do_mixer_shit(wavein_id,MIXER_OBJECTF_WAVEIN,0,0,0); + do_mixer_shit(wavein_id,MIXER_OBJECTF_WAVEIN,1,cfg_wavein_src-1,0); + } + } + void src_deinit() + { + if (old_settings) + { + do_mixer_shit(wavein_id,MIXER_OBJECTF_WAVEIN,1,0,old_settings); + delete[] old_settings; + old_settings=0; + } + } +#endif + bool eof; +public: + bool init(int p_srate,int p_nch,int p_bps); + void Eof() {eof=1;} + + + virtual void Pause(int); + virtual UINT ReadData(void*,UINT,bool*); + virtual void Flush(); + virtual ~CVis(); + CVis() + { +#ifndef IN_MIDI_NO_WAVEIN_SOURCE + old_settings=0; +#endif + eof=0;buffer=0;blox=0;hWi=0;} + +private: + BYTE * buffer; + UINT bufsize; + UINT read_pos; + UINT data; + UINT blocksize; + HWAVEIN hWi; + WAVEHDR *blox; + UINT numblocks; + UINT cur_block,cur_done; + int paused; + UINT in_mm; + int srate,nch,bps; +// void on_done(WAVEBUFFER*); +}; + + +void CVis::Flush() +{ + if (paused) return; + waveInReset(hWi); + UINT n; + for(n=0;n<numblocks;n++) + { + blox[n].dwUser=0; + waveInAddBuffer(hWi,&blox[n],sizeof(WAVEHDR)); + } + + cur_block=0; + cur_done=0; + in_mm=numblocks;//added all blocks already + + read_pos=0; + data=0; + waveInStart(hWi); +} + +void CALLBACK waveInProc(HWAVEIN hWi,UINT msg,DWORD dwIns,DWORD p1,DWORD p2) +{ + if (msg==WIM_DATA && p1) + { + ((WAVEHDR*)p1)->dwUser=1; + } +} + +bool CVis::init(int p_srate,int p_nch,int p_bps) +{ + srate=p_srate; + nch=p_nch; + bps=p_bps; + blocksize=576 * (bps/8) * (nch); + if (cfg_sampout) blocksize<<=3; + numblocks=(2 * srate * nch * (bps>>3))/blocksize; + bufsize=numblocks*blocksize; + blox=new WAVEHDR[numblocks]; + memset(blox,0,sizeof(WAVEHDR)*numblocks); + buffer=(BYTE*)malloc(bufsize); + + try + { + WAVEFORMATEX wfx; + make_wfx(&wfx,srate,nch,bps); + if (waveInOpen(&hWi,cfg_wavein_dev,&wfx,(DWORD)waveInProc,0,CALLBACK_FUNCTION)) + { + WAVEFORMATEXTENSIBLE wfxe = {0}; + make_wfxe(&wfxe,srate,nch,bps); + if (waveInOpen(&hWi,cfg_wavein_dev,&wfxe.Format,(DWORD)waveInProc,0,CALLBACK_FUNCTION)) + { + return 0; + } + } + } catch(...)//gay drivers etc + { + return 0; + } +#ifndef IN_MIDI_NO_WAVEIN_SOURCE + src_init(); +#endif + + UINT n; + for(n=0;n<numblocks;n++) + { + blox[n].lpData=(char*)(buffer+blocksize*n); + blox[n].dwBufferLength=blocksize; + waveInPrepareHeader(hWi,&blox[n],sizeof(WAVEHDR)); + } + + paused=0; + Flush(); +#ifdef USE_LOG + log_write("sampling started OK"); +#endif + return 1; +} + +CVis::~CVis() +{ +#ifdef USE_LOG + log_write("shutting down sampling"); +#endif + if (hWi) + { + waveInReset(hWi); + UINT n; + for(n=0;n<numblocks;n++) + { + waveInUnprepareHeader(hWi,&blox[n],sizeof(WAVEHDR)); + } + +#ifndef IN_MIDI_NO_WAVEIN_SOURCE + src_deinit(); +#endif + waveInClose(hWi); + hWi=0; + } +#ifdef USE_LOG + log_write("sampling shut down OK"); +#endif + if (blox) delete[] blox; + if (buffer) free(buffer); +} + +UINT CVis::ReadData(void * _dst,UINT bytes,bool * ks) +{ + if (eof) return 0; + BYTE * dst=(BYTE*)_dst; + if (paused) return 0; + while(!*ks) + { + while(blox[cur_done].dwUser) + { + blox[cur_done].dwUser=0; + cur_done=(cur_done+1)%numblocks; + in_mm--; + data+=blocksize; + } + + { + UINT d=data; + if (d) + { + if (d>bytes) d=bytes; + if (read_pos+d>bufsize) + { + UINT foo=bufsize-read_pos; + memcpy(dst,buffer+read_pos,foo); + memcpy(dst+foo,buffer,read_pos=d-foo); + } + else + { + memcpy(dst,buffer+read_pos,d); + read_pos+=d; + } + dst+=d; + data-=d; + bytes-=d; + } + } + + + { + UINT max=numblocks-(data+blocksize-1)/blocksize; + while(in_mm < max) + { + waveInAddBuffer(hWi,&blox[cur_block],sizeof(WAVEHDR)); + cur_block=(cur_block+1)%numblocks; + in_mm++; + } + } + if (!bytes) break; + MIDI_callback::Idle(); + } + + return dst-(BYTE*)_dst; +} + +void CVis::Pause(int b) +{ + paused=b; + if (b) + { + waveInStop(hWi); + } + else + { + Flush(); + } +} + +CStream * sampling_create(int srate,int nch,int bps) +{ + CVis * ptr = new CVis; + if (!ptr->init(srate,nch,bps)) + { + delete ptr; + ptr=0; + } + return ptr; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/seq.cpp b/Src/Plugins/Input/in_midi/seq.cpp new file mode 100644 index 00000000..f1fb65fb --- /dev/null +++ b/Src/Plugins/Input/in_midi/seq.cpp @@ -0,0 +1,852 @@ +#include "main.h" +#include "seq.h" +#include <commctrl.h> +#include <math.h> +#include "resource.h" + +#ifdef SEQ_HAVE_PANEL + +cfg_int cfg_seq_showpanel("seq_showpanel",0); + +enum +{ + ID_BASE = 0x6543, + MUTE_ID = ID_BASE, + VOL_ID = MUTE_ID+16, + INS_ID_P = VOL_ID+16, + INS_ID_B1 = INS_ID_P+16, + INS_ID_B2 = INS_ID_B1+16, + SPIN_ID = INS_ID_B2 + +}; + +static cfg_int cfg_ctrl_min("ctrl_min",0); + + +static float g_tempo=1; +static BOOL g_novol,g_noins; +static char sysex1[256],sysex2[256]; + +extern BYTE d_GMReset[6]; +extern BYTE d_XGReset[9]; +extern BYTE d_GSReset[11]; +#endif + +#define SEND_MSG(X) seq_shortmsg(preprocess(X)) + +#define _sysex(A,B) seq_sysex(A,B) +#define rsysex(A) seq_sysex(A,sizeof(A)) + +#ifdef SEQ_HAVE_PANEL +void seq_base::set_mute(UINT ch,BOOL st) +{ + if (st) + { + mute_mask|=1<<ch; + seq_shortmsg(0x07B0|ch); + } + else + { + mute_mask&=~(1<<ch); + SEND_MSG(((DWORD)ctrl_tab[ch][7]<<16)|0x07B0|ch); + } +} +#endif + +//debug hack +#if 0 +#define timeGetTime timehack +static DWORD timehack() +{ + static DWORD t; + return t++; +} +#endif + +DWORD seq_base::get_time() +{ +#ifndef SEQ_HAVE_PANEL + return timeGetTime()<<3; +#else + if (!hCtrl) return timeGetTime()<<3;//*8; + EnterCriticalSection(&tm_sec); + DWORD cur_t=timeGetTime(); + if (!last_time_ms) last_time_ms=cur_t; + int d=cur_t-last_time_ms; + if (d<0) d=0; + last_time_ret+=(double)(d*8.0)*tempo; + + last_time_ms=cur_t; + DWORD r=(DWORD)last_time_ret; + LeaveCriticalSection(&tm_sec); + return r; +#endif +} + +#ifdef SEQ_HAVE_PANEL +BOOL CALLBACK seq_base::CtrlProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) +{ + seq_base* s; + if (msg==WM_INITDIALOG) + { + SetWindowLongPtr(wnd,DWLP_USER,lp); + s=(seq_base*)lp; + if (s) s->hCtrl=wnd; + } + else + { +#if defined(_WIN64) + s = (seq_base*)GetWindowLong(wnd, DWLP_USER); +#else + s = (seq_base*)GetWindowLong(wnd, DWL_USER); +#endif + } + if (s) + { + s->do_msg(msg,wp,lp); + } + return 0; +} + +static float ui2tempo(int x) +{ + return (float)pow(4.0,0.02*(float)(x-50)); +} + +static int tempo2ui(float x) +{ + return 50+(int) ((50.0 / log(4.0)) * log(x) ); +} + +static void do_ttext(HWND w,float t) +{ + char tx[32] = {0}; + _itoa((UINT)(t*100.0),tx,10); + char* p=tx; + while(p && *p) p++; + *(p++)='%'; + *p=0; + SetDlgItemTextA(w,IDC_TDISP,tx); +} + +BYTE* read_sysex_edit(HWND w,UINT *siz); + +void CreateControl(DWORD ex,HWND hCtrl,const char * cls,const char * name,DWORD style,UINT x,UINT y,UINT dx,UINT dy,HINSTANCE hDll,UINT id) +{ + RECT r={(LONG)x,(LONG)y,(LONG)(x+dx),(LONG)(y+dy)}; + MapDialogRect(hCtrl,&r); + HWND w = CreateWindowExA( ex, cls, name, WS_CHILD | WS_VISIBLE | style, r.left, r.top, r.right - r.left, r.bottom - r.top, hCtrl, 0, hDll, 0 ); // Must stay in ANSI + if (w) + { + if (id) SetWindowLong(w,GWL_ID,id); + SendMessage(w,WM_SETFONT,SendMessage(hCtrl,WM_GETFONT,0,0),MAKELONG(0,0)); + } +} + +static cfg_int cfg_ctrl_x("ctrl_x",0x80000000),cfg_ctrl_y("ctrl_y",0x80000000); + +void seq_base::do_msg(UINT msg,WPARAM wp,LPARAM lp) +{ + switch(msg) + { + case WM_CLOSE: + ShowWindow(hCtrl,SW_SHOWMINIMIZED); + break; + case WM_INITDIALOG: + { + HINSTANCE hCCdll=GetModuleHandle(TEXT("comctl32.dll")); + UINT n; + HWND w; + for(n=0;n<16;n++) + { + char tmp[16] = {0}; + itoa(n,tmp,10); + CreateControl(0,hCtrl,TRACKBAR_CLASSA,0,TBS_VERT | TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,40+n*28,36,18,80,hCCdll,VOL_ID+n); + CreateControl(0,hCtrl,"STATIC",tmp,0,46+n*28,25,8,8,0,0); + CreateControl(0,hCtrl,"Button",0,BS_AUTOCHECKBOX | WS_TABSTOP,43+28*n,120,9,8,0,MUTE_ID+n); + CreateControl(WS_EX_CLIENTEDGE,hCtrl,"EDIT",0,ES_AUTOHSCROLL | ES_NUMBER,36+28*n,138,26,12,0,INS_ID_P+n); + CreateControl(0,hCtrl,"msctls_updown32",0,UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS,0,0,0,0,0,SPIN_ID+n); + CreateControl(WS_EX_CLIENTEDGE,hCtrl,"EDIT",0,ES_AUTOHSCROLL | ES_NUMBER,36+28*n,150,26,12,0,INS_ID_B1+n); + CreateControl(0,hCtrl,"msctls_updown32",0,UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS,0,0,0,0,0,SPIN_ID+n+16); + CreateControl(WS_EX_CLIENTEDGE,hCtrl,"EDIT",0,ES_AUTOHSCROLL | ES_NUMBER,36+28*n,162,26,12,0,INS_ID_B2+n); + CreateControl(0,hCtrl,"msctls_updown32",0,UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS,0,0,0,0,0,SPIN_ID+n+32); + } + + w=GetDlgItem(hCtrl,IDC_TEMPO); + SendMessage(w,TBM_SETRANGE,0,MAKELONG(0,100)); + SendMessage(w,TBM_SETPOS,1,tempo2ui(tempo)); + do_ttext(hCtrl,tempo); + if (cfg_ctrl_x!=0x80000000 && cfg_ctrl_y!=0x80000000) + { + int max_x=GetSystemMetrics(SM_CXSCREEN)-10,max_y=GetSystemMetrics(SM_CYSCREEN)-10; + if (cfg_ctrl_x>max_x) cfg_ctrl_x=max_x; + if (cfg_ctrl_y>max_y) cfg_ctrl_y=max_y; + SetWindowPos(hCtrl,0,cfg_ctrl_x,cfg_ctrl_y,0,0,SWP_NOZORDER|SWP_NOSIZE); + } + for(n=0;n<16;n++) + { + w=GetDlgItem(hCtrl,VOL_ID+n); + SendMessage(w,TBM_SETRANGE,1,MAKELONG(0,0x7f)); + SendMessage(w,TBM_SETPOS,1,0x7f-90); + } + SendDlgItemMessage(hCtrl,IDC_NOVOL,BM_SETCHECK,novol,0); + SetDlgItemTextA(hCtrl,IDC_SYSEX1,sysex1); + SetDlgItemTextA(hCtrl,IDC_SYSEX2,sysex2); + for(n=0;n<48;n++) + { + SendDlgItemMessage(hCtrl,SPIN_ID+n,UDM_SETRANGE,0,MAKELONG(127,0)); + } + for(n=0;n<16;n++) + { + SendDlgItemMessage(hCtrl,INS_ID_P+n,EM_LIMITTEXT,3,0); + SendDlgItemMessage(hCtrl,INS_ID_B1+n,EM_LIMITTEXT,3,0); + SendDlgItemMessage(hCtrl,INS_ID_B2+n,EM_LIMITTEXT,3,0); + } + initialized=1; + } + break; + case WM_COMMAND: + { + UINT n; + if (HIWORD(wp)==0) + { + if (wp==IDC_SYSEX1_SEND || wp==IDC_SYSEX2_SEND) + { + UINT sl; + BYTE* s=read_sysex_edit(GetDlgItem(hCtrl,(wp==IDC_SYSEX1_SEND)?IDC_SYSEX1:IDC_SYSEX2) , &sl); + if (s) + { + _sysex(s,sl); + free(s); + } + } + else if (wp==IDC_NOVOL) + { + novol=SendMessage((HWND)lp,BM_GETCHECK,0,0); + } + else if (wp==IDC_NOINS) + { + noins=SendMessage((HWND)lp,BM_GETCHECK,0,0); + } + else if (wp==IDC_ALL_ON) + { + UINT n; + for(n=0;n<16;n++) + { + if (mute_mask&(1<<n)) + { + SendDlgItemMessage(hCtrl,MUTE_ID+n,BM_SETCHECK,0,0); + set_mute(n,0); + } + } + } + else if (wp==IDC_ALL_OFF) + { + UINT n; + for(n=0;n<16;n++) + { + if (!(mute_mask&(1<<n))) + { + SendDlgItemMessage(hCtrl,MUTE_ID+n,BM_SETCHECK,1,0); + set_mute(n,1); + } + } + } + else if (wp==IDC_GMRESET) + { + rsysex(d_GMReset); + } + else if (wp==IDC_GSRESET) + { + rsysex(d_GSReset); + } + else if (wp==IDC_XGRESET) + { + rsysex(d_XGReset); + } + else for(n=0;n<16;n++) + { + if (wp==MUTE_ID+n) + { + set_mute(n,SendMessage((HWND)lp,BM_GETCHECK,0,0)); + break; + } + } + } + else if (HIWORD(wp)==EN_CHANGE) + { + if (initialized) + { + wp&=0xFFFF; + UINT n; + for(n=0;n<16;n++) + { + if (wp==INS_ID_P+n) + { + UINT p=GetDlgItemInt(hCtrl,wp,0,0)&0x7F; + if (p!=ins_tab[n]) + { + ins_tab[n]=p; + SEND_MSG(0xC0|n|(p<<8)); + } + break; + } + else if (wp==INS_ID_B1+n) + { + UINT p=GetDlgItemInt(hCtrl,wp,0,0)&0x7F; + if (p!=ctrl_tab[n][0]) + { + ctrl_tab[n][0]=p; + SEND_MSG(0xB0|n|(p<<16)); + SEND_MSG(0xC0|n|(ins_tab[n]<<8)); + } + break; + } + else if (wp==INS_ID_B2+n) + { + UINT p=GetDlgItemInt(hCtrl,wp,0,0)&0x7F; + if (p!=ctrl_tab[n][0x20]) + { + ctrl_tab[n][0x20]=p; + SEND_MSG(0x20B0|n|(p<<16)); + SEND_MSG(0xC0|n|(ins_tab[n]<<8)); + } + break; + } + } + } + } + + } + break; + case WM_VSCROLL: + { + HWND sb=(HWND)lp; + if (sb) + { + UINT id=GetWindowLong(sb,GWL_ID); + UINT n; + for(n=0;n<16;n++) + { + if (id==VOL_ID+n) + { + UINT val=0x7f-SendMessage(sb,TBM_GETPOS,0,0); + ctrl_tab[n][7]=val; + SEND_MSG(0x7B0|n|(val<<16)); + break; + } + + } + } + } + break; + case WM_HSCROLL: + tempo=ui2tempo(SendDlgItemMessage(hCtrl,IDC_TEMPO,TBM_GETPOS,0,0)); + do_ttext(hCtrl,tempo); + break; + } +} +#endif + +seq_base::~seq_base() +{ +#ifdef SEQ_HAVE_PANEL + if (hCtrl) + { + cfg_ctrl_min=!!IsIconic(hCtrl); + RECT r; + GetWindowRect(hCtrl,&r); + cfg_ctrl_x=r.left; + cfg_ctrl_y=r.top; + GetDlgItemTextA(hCtrl,IDC_SYSEX1,sysex1,256); + GetDlgItemTextA(hCtrl,IDC_SYSEX2,sysex2,256); + DestroyWindow(hCtrl); + DeleteCriticalSection(&tm_sec); + } + g_tempo=tempo; + g_novol=novol; + g_noins=noins; +#endif + if (events) free(events); +} + +seq_base::seq_base() +{ + mf=0; + + kill=0;paused=0; + smap=0; + + pan=0;vol=0; + + seek_to=0; + n_events=0; + events=0; + + c_loop=0; + loop_start=0; + memset(¬es,0,sizeof(notes)); + memset(&ctrl_tab,0,sizeof(ctrl_tab)); + memset(&ins_tab,0,sizeof(ins_tab)); + + tm_ofs=0; + p_time=0; + hTrd=0; + + ins_set=0; + +#ifdef SEQ_HAVE_PANEL + hCtrl=0; + + tempo=g_tempo; + novol=g_novol; + noins=g_noins; + + last_time_ms=0; + last_time_ret=0; + + mute_mask=0; + initialized=0; +#endif +} + + +#define GET_TIME get_time()//timeGetTime() + +int IS_SPEC_C(int x) {return (x>=0x60 && x<=0x65) || x==6 || x==26 || x>=120;} + +#define n_sysex smap->pos + + +DWORD seq_base::preprocess(DWORD e) +{ + BYTE t=(BYTE)(e&0xF0); + if (t==0xB0) + { + UINT v=(e>>16)&0xFF; + BYTE c=(BYTE)(e>>8); +#ifdef SEQ_HAVE_PANEL + if (c==7) + { + if (mute_mask&(1<<(e&0xF))) v=0; + } +#endif + e=(e&0xFFFF)|((v&0xFF)<<16); + } + else if (t==0xC0) + { + ins_set|=1<<(e&0xF); + } + return e; +} + +void seq_base::send_sysex(int n) +{ +#ifdef USE_LOG + log_write("send_sysex()"); +#endif + if (!smap || n>=n_sysex) return; + _sysex(smap->data+smap->events[n].ofs,smap->events[n].len); +} + +/* +void seq_base::reset_ins() +{ + UINT n; + for(n=0;n<16;n++) + { + cb->shortmsg(0xC0|n); + } +} +*/ + +BOOL seq_base::do_ctrl(DWORD e) +{ + BYTE tp=(BYTE)(e&0xF0); + BYTE ch=(BYTE)(e&0x0F); + if (tp==0xC0) + { +#ifdef SEQ_HAVE_PANEL + if (noins) return 0; +#endif + //if (!cfg_fctrl && (e>>8)==ins_tab[e&0xF]) return 0; + UINT val=e>>8; + ins_tab[ch]=val; +#ifdef SEQ_HAVE_PANEL + if (hCtrl) SetDlgItemInt(hCtrl,INS_ID_P+ch,val,0); +#endif + } else if (tp==0xB0) + { + UINT cn = (e>>8)&0x7F; + UINT val= (e>>16)&0x7F; +#ifdef SEQ_HAVE_PANEL + if (cn==0) + { + if (noins) return 0; + if (hCtrl) SetDlgItemInt(hCtrl,INS_ID_B1+ch,val,0); + } + else if (cn==0x20) + { + if (noins) return 0; + if (hCtrl) SetDlgItemInt(hCtrl,INS_ID_B2+ch,val,0); + } + else if (cn==7) + { + if (novol) return 0; + if (hCtrl) PostMessage(GetDlgItem(hCtrl,VOL_ID+(e&0xF)),TBM_SETPOS,1,0x7F-val); + } + else if (cn==0x27) + { + if (novol) return 0; + } +#endif + if (!IS_SPEC_C(cn)) ctrl_tab[e&0xF][cn]=val; + } + else if (tp==0x90) + { + if (!(ins_set&(1<<ch))) + { + SEND_MSG(0xC0|ch); + } + } + return 1; +} + +void seq_base::reset() +{ + int not,ch; + for(ch=0;ch<16;ch++) + { + if (ctrl_tab[ch][0x40]) + { + seq_shortmsg(0x40B0|ch); + ctrl_tab[ch][0x40]=0; + } + if (ch==9) continue; + for(not=0;not<128;not++) + { + if (note_state(ch,not)) + { + seq_shortmsg((not<<8)|0x80|ch); + note_off(ch,not); + } + } + } +} + + +int seq_base::note_state(int ch,int note) +{ + UINT pos=(ch<<7)+note; + return notes[pos>>3]&(1<<(pos&0x7)); +} + +void seq_base::note_on(int ch,int note) +{ + UINT pos=(ch<<7)+note; + notes[pos>>3]|=(1<<(pos&0x7)); +} + +void seq_base::note_off(int ch,int note) +{ + UINT pos=(ch<<7)+note; + notes[pos>>3]&=~(1<<(pos&0x7)); +} + +UINT seq_base::do_seek(DWORD n,DWORD p) +{ + UINT m,c; + BYTE _ctrl_tab[16][128] = {0}; + BYTE _ins_tab[16] = {0}; + memcpy(_ctrl_tab,ctrl_tab,sizeof(_ctrl_tab)); + memcpy(_ins_tab,ins_tab,sizeof(_ins_tab)); + + if (n==0) + { + memset(ins_tab,0,sizeof(ins_tab)); + for(m=0;m<16;m++) + { + _ctrl_tab[m][0]=_ctrl_tab[m][0x20]=0; + } + } + + while(n<n_events && p>events[n].tm) + { + DWORD e=events[n].ev; + if (!(e&0x80000000)) + { + if (do_ctrl(e)) + { + if (((e&0xF0)==0xB0) && IS_SPEC_C((e>>8)&0xFF)) + { + seq_shortmsg(e); + } + } + } + n++; + } + for(c=0;c<16;c++) + { + for(m=0;m<128;m++) + { + if (!IS_SPEC_C(m) && _ctrl_tab[c][m]!=ctrl_tab[c][m]) + { + SEND_MSG(((DWORD)ctrl_tab[c][m]<<16)|(m<<8)|0xB0|c); + } + } + if (_ins_tab[c]!=ins_tab[c]) + { + SEND_MSG(((DWORD)ins_tab[c]<<8)|0xC0|c); + } + } + return n; +} + +DWORD WINAPI seq_base::seq_trd(void* p) +{ + ((seq_base*)p)->thread(); + return 0; +} + +void seq_base::sysexfunc(seq_base* cb,BYTE* s,UINT sz) +{ + cb->seq_sysex(s,sz); +} + +void seq_base::thread() +{ + tm_ofs=-1; + if (seq_play_start()) + { + + sysex_startup((SYSEXFUNC)sysexfunc,this); + + tm_ofs=GET_TIME; + DWORD pos=0; + while(!kill) + { + DWORD c_t=GET_TIME-tm_ofs; + if (paused) + { + reset(); + while(paused && !kill) MIDI_callback::Idle(); + if (kill) break; + tm_ofs=GET_TIME-c_t; + } + + if (seek_to!=-1) + { +_seek: + DWORD _p=seek_to > c_t ? pos : 0; + c_t=seek_to; + seek_to=-1; + tm_ofs=GET_TIME-c_t; + reset(); + pos=c_t ? do_seek(_p,c_t) : 0; + } + if (events[pos].tm+1600 < c_t) + { + reset(); + pos=do_seek(pos,c_t); + } + while(pos<n_events && events[pos].tm<=c_t && !kill) + { + DWORD e=events[pos++].ev; + if (e) + { + if (e&0x80000000) + { + send_sysex(e&0x7FFFFFFF); + } + else + { + if ((e&0xF0)==0x90) + { + note_on(e&0xf,(e>>8)&0xFF); + } + else if ((e&0xF0)==0x80) + { + note_off(e&0xf,(e>>8)&0xFF); + } + if (do_ctrl(e)) + SEND_MSG(e); + } + } + } + + if (pos>=n_events || c_t >= events[n_events-1].tm) + { + if (loop_start!=-1 && (--c_loop)) + { + c_t=loop_start; + tm_ofs=GET_TIME-c_t; + pos=do_seek(0,c_t); + continue; + } + if (cfg_eof_delay) + { + DWORD t=timeGetTime(); + do + { + MIDI_callback::Idle(); + } while(!kill && seek_to==-1 && t+cfg_eof_delay>timeGetTime()); + if (seek_to!=-1) { + pos=0; + goto _seek; + } + } + if (!kill) MIDI_core::Eof(); + break; + } + if (kill) break; + + MIDI_callback::Idle(); + } + reset(); + } + seq_play_stop(); +} + +int seq_base::gettime() +{ + if (paused) + return (seek_to==-1) ? seek_to>>3 : p_time; + else + return (GET_TIME-tm_ofs)>>3; +} + +int seq_base::settime(int tm) +{ + seek_to=tm<<3; + return 1; +} + + +void seq_base::pause() +{ + paused=1; + p_time=GET_TIME-tm_ofs; +} + +void seq_base::unpause() +{ + paused=0; +} + +int seq_base::seq_cmd_start(DWORD cflags) +{ + mf=MIDI_core::getFile(); +#ifdef SEQ_HAVE_PANEL + mute_mask=0; +#endif + c_loop=cfg_loop_infinite ? -1 : cfg_loop_count; + memset(notes,0,sizeof(notes)); + memset(ctrl_tab,-1,sizeof(ctrl_tab)); + memset(ins_tab,0,sizeof(ins_tab)); + + UINT n; + for(n=0;n<16;n++) ctrl_tab[n][7]=90; + + events=do_table(mf,8,&n_events,&loop_start,cflags); + if (!events) return 0; + + if (!cfg_nosysex && mf->smap && mf->smap->pos) + { + smap=mf->smap; + } + else smap=0; + + kill=0; + seek_to=-1; + paused=0; + +#ifdef SEQ_HAVE_PANEL + if (cfg_seq_showpanel) + { + InitializeCriticalSection(&tm_sec); + WASABI_API_CREATEDIALOGPARAMW(IDD_EXT_IMM, MIDI_callback::GetMainWindow(), CtrlProc, (LPARAM)this); + ShowWindow(hCtrl,cfg_ctrl_min ? SW_SHOWMINIMIZED : SW_SHOW); + } + else + { + tempo=1; + novol=0; + noins=0; + } +#endif + + DWORD id; + hTrd=CreateThread(0,0,seq_trd,this,CREATE_SUSPENDED,&id); +#ifndef _DEBUG + SetThreadPriority(hTrd,THREAD_PRIORITY_TIME_CRITICAL); +#endif + ResumeThread(hTrd); + return 1; +} + +void seq_base::seq_cmd_stop() +{ +#ifdef USE_LOG + log_write("stopping sequencer"); +#endif + if (hTrd) + { +#ifdef USE_LOG + log_write("killing thread"); +#endif + kill=1; + if (WaitForSingleObject(hTrd,4000)!=WAIT_OBJECT_0) + { +#ifdef USE_LOG + log_write("unable to kill thread"); +#endif + TerminateThread(hTrd,0); + } +#ifdef USE_LOG + else log_write("thread killed normally"); +#endif + CloseHandle(hTrd); + } +} +/* +void seq_base::enum_ins() +{ + DWORD ttab[256]; + memset(ttab,-1,sizeof(ttab)); + UINT tpt=0; + UINT n; + DWORD c_ins[16]; + memset(c_ins,0,sizeof(c_ins)); + c_ins[9]=0x80000000; + for(n=0;n<n_events;n++) + { + DWORD t=events[n].ev; + if (t&0xFF000000) continue; + UINT c=t&0xF0; + UINT ch=events[n].ev&0xF; + if ((t&0xFFF0)==0x20B0) + { + c_ins[ch]=(c_ins[ch]&0xFFFF00FF)|((t>>8)&0xFF00); + } + else if ((t&0xFFF0)==0xB0) + { + c_ins[ch]=(c_ins[ch]&0xFF00FFFF)|(t&0xFF0000); + } + else if ((t&0xF0)==0xC0) + { + c_ins[ch]=(c_ins[ch]&0xFFFFFF00)|((t>>8)&0xFF); + } + else if ((t&0xF0)==0x90) + { + UINT n; + for(n=0;n<256;n++) + { + if (ttab[n]==c_ins[ch]) goto ok; + } + cb->enum_ins(ttab[tpt]=c_ins[ch]); + tpt=(tpt+1)&0xFF; +ok:; + } + } +} +*/ diff --git a/Src/Plugins/Input/in_midi/seq.h b/Src/Plugins/Input/in_midi/seq.h new file mode 100644 index 00000000..eefc5b6f --- /dev/null +++ b/Src/Plugins/Input/in_midi/seq.h @@ -0,0 +1,75 @@ +#define SEQ_HAVE_PANEL + +class seq_base : public player_base +{ +protected: + + + int seq_cmd_start(DWORD cflags); + void seq_cmd_stop(); + + virtual ~seq_base(); + + //OVERRIDE ME + virtual void seq_shortmsg(DWORD msg)=0; + virtual void seq_sysex(BYTE*,UINT)=0; + virtual int seq_play_start() {return 1;} + virtual void seq_play_stop() {} + + + seq_base(); +private: + virtual int gettime(); + virtual int settime(int); + virtual void unpause(); + virtual void pause(); + + DWORD preprocess(DWORD e); + + void send_sysex(int n); +// void reset_ins(); + UINT do_sysex(UINT src,UINT tm); + BOOL do_ctrl(DWORD e); + void reset(); + int note_state(int ch,int note); + void note_on(int ch,int note); + void note_off(int ch,int note); + UINT do_seek(DWORD n,DWORD p); + void thread(); + DWORD get_time(); + void get_ins(UINT c); + static DWORD WINAPI seq_trd(void* p); + static void sysexfunc(seq_base* cb,BYTE* s,UINT sz); + + + MIDI_file* mf; + bool kill,paused; + CSysexMap* smap; + int pan,vol; + + UINT seek_to,n_events; + MIDI_EVENT* events; + + UINT c_loop,loop_start; + BYTE notes[256]; + BYTE ctrl_tab[16][128]; + BYTE ins_tab[16]; + DWORD tm_ofs,p_time; + HANDLE hTrd; + DWORD ins_set; + +#ifdef SEQ_HAVE_PANEL + HWND hCtrl; + float tempo; + BOOL novol,noins; + DWORD last_time_ms; + double last_time_ret; + CRITICAL_SECTION tm_sec; + DWORD mute_mask; + bool initialized; + + static BOOL CALLBACK CtrlProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp); + void do_msg(UINT msg,WPARAM wp,LPARAM lp); + void set_mute(UINT ch,BOOL st); +#endif +}; diff --git a/Src/Plugins/Input/in_midi/utils.cpp b/Src/Plugins/Input/in_midi/utils.cpp new file mode 100644 index 00000000..c76d49f0 --- /dev/null +++ b/Src/Plugins/Input/in_midi/utils.cpp @@ -0,0 +1,1064 @@ +#include "main.h" +#include "../Agave/language/api_language.h" +#include <commdlg.h> +#include "resource.h" + +DWORD _fastcall rev32(DWORD d) {return _rv(d);} + +void CPipe::WriteData(void* b,UINT s) +{ + if (closed) return; + sec.enter(); + if (buf_n+s>buf_s) + { +#ifdef USE_LOG + log_write("buffer overflow"); +#endif + s=buf_s-buf_n; + s-=s%align; + } + if (s) + { + if (buf_wp+s<buf_s) + { + memcpy(buf+buf_wp,b,s); + buf_wp+=s; + } + else + { + UINT d=buf_s-buf_wp; + memcpy(buf+buf_wp,b,d); + memcpy(buf,(BYTE*)b+d,s-d); + buf_wp=s-d; + } + buf_n+=s; + } + sec.leave(); +} + +UINT CPipe::ReadData(void* _b,UINT s,bool* ks) +{ + UINT rv=0; + BYTE * b=(BYTE*)_b; + sec.enter(); + while(1) + { + UINT d=s; + if (d>buf_n) d=buf_n; + if (d) + { + if (buf_rp+d<buf_s) + { + memcpy(b,buf+buf_rp,d); + buf_rp+=d; + } + else + { + UINT d1=buf_s-buf_rp; + memcpy(b,buf+buf_rp,d1); + memcpy(b+d1,buf,d-d1); + buf_rp=d-d1; + } + buf_n-=d; + s-=d; + rv+=d; + b+=d; + } + if (closed || !s || *ks) break; + sec.leave(); + MIDI_callback::Idle(); + sec.enter(); + } + sec.leave(); + return rv; +} + +#ifdef USE_LOG +static HANDLE hLog; +void log_start() +{ + hLog=CreateFile("c:\\in_midi.log",GENERIC_WRITE,FILE_SHARE_READ,0,OPEN_ALWAYS,0,0); + SetFilePointer(hLog,0,0,FILE_END); + log_write("opening log"); +} + +void log_quit() {log_write("closing log");log_write("");log_write("");CloseHandle(hLog);} + +void log_write(char* t) +{ + DWORD bw; + WriteFile(hLog,t,strlen(t),&bw,0); + char _t[2]={13,10}; + WriteFile(hLog,_t,2,&bw,0); + FlushFileBuffers(hLog); +} +#endif + + + + + + + + + + +//tempo map object + +CTempoMap* tmap_create() +{ + CTempoMap* m=new CTempoMap; + if (m) + { + m->pos=0; + m->size=0x100; + m->data=(TMAP_ENTRY*)malloc(m->size*sizeof(TMAP_ENTRY)); + } + return m; +} + +void CTempoMap::AddEntry(int _p,int tm) +{ + if (!data) {pos=size=0;return;} + if (pos && _p<=data[pos-1].pos) {data[pos-1].tm=tm;return;} + if (pos==size) + { + size*=2; + data=(TMAP_ENTRY*)realloc(data,size*sizeof(TMAP_ENTRY)); + if (!data) {pos=0;return;} + } + data[pos].pos=_p; + data[pos].tm=tm; + pos++; +} + +int ReadSysex(const BYTE* src,int ml) +{ + int r=1; + while(r<ml) + { + r++; + if (src[r]==0xF7) return r+1; + } + unsigned int d; + r=1+DecodeDelta(src+1,&d); + r+=d; + return r; +} + +unsigned int DecodeDelta(const BYTE* src,unsigned int* _d, unsigned int limit) +{ + unsigned int l=0; + unsigned int d=0; + BYTE b; + do + { + if (l >= limit) + { + *_d=0; + return l; + } + b=src[l++]; + d=(d<<7)|(b&0x7F); + } while(b&0x80); + *_d=d; + return l; +} + +int EncodeDelta(BYTE* dst,int d) +{ + if (d==0) + { + dst[0]=0; + return 1; + } + else + { + int r=0; + int n=1; + unsigned int temp=d; + while (temp >>= 7) + { + n++; + } + + do { + n--; + BYTE b=(BYTE)((d>>(7*n))&0x7F); + if (n) b|=0x80; + dst[r++]=b; + } while(n); + return r; + } +} + +int CTempoMap::BuildTrack(grow_buf & out) +{ + if (!pos) return 0; + int start=out.get_size(); + //BYTE* trk=(BYTE*)malloc(8+4+pos*10); + //if (!trk) return 0; + out.write_dword(_rv('MTrk')); + out.write_dword(0);//track size + DWORD ct=0; + int n; + BYTE t_event[6]={0xFF,0x51,0x03,0,0,0}; + for(n=0;n<pos;n++) + { + DWORD t=data[n].pos; + gb_write_delta(out,t-ct); + ct=t; + t=data[n].tm; + t_event[3]=(BYTE)(t>>16); + t_event[4]=(BYTE)(t>>8); + t_event[5]=(BYTE)(t); + out.write(t_event,6); + } + out.write_dword(0x002FFF00); + out.write_dword_ptr(rev32(out.get_size()-(start+8)),start+4); + return 1; +} + +//sysex map management + +void CSysexMap::AddEvent(const BYTE* e,DWORD s,DWORD t) +{ + if (!data || !events) return; + DWORD np=pos+1; + if (np>=e_size) + { + do { + e_size<<=1; + } while(np>=e_size); + events=(SYSEX_ENTRY*)realloc(events,e_size*sizeof(SYSEX_ENTRY)); + if (!events) return; + } + DWORD nd=d_pos+s; + if (nd>=d_size) + { + do { + d_size<<=1; + } while(nd>=d_size); + data=(BYTE*)realloc(data,d_size); + if (!data) return; + } + data[d_pos]=0xF0; + unsigned int x; + unsigned int sp=DecodeDelta(e+1,&x); + if (sp >= s) + return; + memcpy(data+d_pos+1,e+1+sp,s-1-sp); + events[pos].pos=t; + events[pos].ofs=d_pos; + events[pos].len=s-sp; + d_pos=nd-sp; + pos++; +} + +CSysexMap* smap_create() +{ + CSysexMap* s=new CSysexMap; + if (s) + { + s->e_size=0x10; + s->d_size=0x40; + s->events=(SYSEX_ENTRY*)malloc(sizeof(SYSEX_ENTRY)*s->e_size); + s->data=(BYTE*)malloc(s->d_size); + s->d_pos=s->pos=0; + } + return s; +} + + +CSysexMap::~CSysexMap() +{ + if (data) free(data); + if (events) free(events); +} + +BYTE d_GMReset[6]={0xF0,0x7E,0x7F,0x09,0x01,0xF7}; +BYTE d_XGReset[9]={0xf0,0x43,0x10,0x4c,0x00,0x00,0x7e,0x00,0xf7}; +BYTE d_GSReset[11]={0xF0,0x41,0x10,0x42,0x12,0x40,0x00,0x7F,0x00,0x41,0xF7}; + +CSysexMap* CSysexMap::Translate(MIDI_file * mf) +{ + CTempoMap* tmap=mf->tmap; + if (!events || !data || !tmap) return 0; + CSysexMap* nm=smap_create(); + if (!nm) return 0; + nm->d_size=d_size; + nm->d_pos=d_pos; + nm->data=(BYTE*)realloc(nm->data,nm->d_size); + if (!nm->data) {delete nm;return 0;} + memcpy(nm->data,data,d_pos); + nm->e_size=e_size; + nm->pos=pos; + nm->events=(SYSEX_ENTRY*)realloc(nm->events,sizeof(SYSEX_ENTRY)*nm->e_size); + if (!nm->events) {delete nm;return 0;} + + int pos_ms=0; + int n=0; + int cur_temp=0; + int ntm=tmap->pos,t_pos=0; + int p_t=0; + int dtx = rev16(*(WORD*)(mf->data+12))*1000; + int pos_tx=0; + + while(n<pos) + { + pos_tx=events[n].pos; + int d=pos_tx-p_t; + p_t=pos_tx; + while(t_pos<ntm && pos_tx+d>=tmap->data[t_pos].pos) + { + DWORD d1=tmap->data[t_pos].pos-pos_tx; + pos_ms+=MulDiv(cur_temp,d1<<8,dtx); + cur_temp=tmap->data[t_pos].tm; + t_pos++; + pos_tx+=d1; + d-=d1; + } + pos_ms+=MulDiv(cur_temp,d<<8,dtx); + pos_tx+=d; + + nm->events[n].pos=pos_ms>>8; + nm->events[n].ofs=events[n].ofs; + nm->events[n].len=events[n].len; + n++; + } + return nm; +} + +int CSysexMap::BuildTrack(grow_buf & out) +{ + if (!pos) return 0; + + int start=out.get_size(); + out.write_dword(_rv('MTrk')); + out.write_dword(0); + + int ct=0; + int n; + for(n=0;n<pos;n++) + { + DWORD t=events[n].pos; + gb_write_delta(out,t-ct); + ct=t; + out.write_byte(0xF0); + gb_write_delta(out,events[n].len-1); + out.write(data+events[n].ofs+1,events[n].len-1); + } + out.write_dword(0x002FFF00); + out.write_dword_ptr(rev32(out.get_size()-(start+8)),start+4); + return 1; +} + +const char* CSysexMap::GetType() +{ + int ret=0; + int n; + for(n=0;n<pos;n++) + { + ret=data[events[n].ofs+1]; + if (ret!=0x7E) break; + } + + switch(ret) + { + case 0x7E: + return "GM"; + case 0x43: + return "XG"; + case 0x42: + return "X5"; + case 0x41: + return "GS"; + } + return 0; +} + +void CSysexMap::CleanUp() +{ + if (!pos) return; + int n,m; + for(n=0;n<pos-1;n++) + { + for(m=n+1;m<pos;m++) + { + if (events[n].pos>events[m].pos) + { + SYSEX_ENTRY t=events[n]; + events[n]=events[m]; + events[m]=t; + } + } + } +} + +char* BuildFilterString(UINT res_id, char* ext, int* len) +{ + static char filterStr[256]; + char *f = filterStr; + ZeroMemory(filterStr,256); + *len = 0; + WASABI_API_LNGSTRING_BUF(res_id,filterStr,256); + f += (*len = lstrlenA(filterStr) + 1); + lstrcatA(f,"*."); + f += 2; + lstrcatA(f,ext); + *(f + lstrlenA(ext)+1) = 0; + *len += lstrlenA(ext)+3; + return filterStr; +} + +BOOL DoOpenFile(HWND w,char* fn,UINT res_id, char* ext,BOOL save) +{ + int len = 0; + OPENFILENAMEA ofn = {sizeof(ofn),0}; + ofn.hwndOwner=w; + ofn.lpstrFilter=BuildFilterString(res_id,ext,&len); + ofn.lpstrFile=fn; + ofn.nMaxFile=MAX_PATH; + ofn.lpstrDefExt=ext; + if (save) + { + ofn.Flags=OFN_OVERWRITEPROMPT|OFN_PATHMUSTEXIST|OFN_HIDEREADONLY; + return GetSaveFileNameA(&ofn); + } + else + { + ofn.Flags=OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_HIDEREADONLY; + return GetOpenFileNameA(&ofn); + } +} + +BOOL DoSaveFile(HWND w, char* fn, char* filt, char* ext) +{ + OPENFILENAMEA ofn; + ZeroMemory(&ofn,sizeof(ofn)); + ofn.lStructSize=sizeof(ofn); + ofn.hwndOwner=w; + ofn.lpstrFilter=filt; + ofn.lpstrFile=fn; + ofn.nMaxFile=MAX_PATH; + ofn.lpstrDefExt=ext; + ofn.Flags=OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_HIDEREADONLY; + return GetOpenFileNameA(&ofn); +} + +typedef void (*SYSEXFUNC)(void*,BYTE*,UINT); + +#define rsysex(X) f(i,X,sizeof(X)) +#define _sysex(X,Y) f(i,X,Y) + +bool need_sysex_start() +{ + return cfg_hardware_reset>0 + || cfg_sysex_table.num_entries()>0 + ; +} + +void sysex_startup(SYSEXFUNC f,void* i) +{ + if (cfg_hardware_reset>0) + { + switch(cfg_hardware_reset) + { + case 1:rsysex(d_GMReset);break; + case 2:rsysex(d_GSReset);break; + case 3:rsysex(d_XGReset);break; + } + MIDI_callback::Idle(200); + } + if (cfg_sysex_table.num_entries()>0) + { + int idx=0; + BYTE * data; + int size,time; + while(cfg_sysex_table.get_entry(idx++,&data,&size,&time)) + { + _sysex(data,size); + MIDI_callback::Idle(time); + } + } +} + + +MIDI_EVENT* do_table(MIDI_file * mf,UINT prec,UINT * size,UINT* _lstart,DWORD cflags) +{ + BYTE * data_ptr = 0; + int data_size = 0; + if (!DoCleanUp(mf,CLEAN_1TRACK|CLEAN_NOSYSEX|CLEAN_NOTEMPO|cflags,(void**)&data_ptr,&data_size)) return 0; + if (data_size<=0x0e) {free(data_ptr);return 0;} + + UINT ts; + BYTE* track; + UINT ntm; + track=data_ptr+8+6+8; + ts=rev32(*(DWORD*)(track-4)); + CTempoMap* tmap=mf->tmap; + UINT n=0; + UINT pt=0; + ntm=tmap->pos; + CSysexMap* smap; + + if (!cfg_nosysex && mf->smap && mf->smap->pos) + { + smap=mf->smap; + } + else smap=0; + + n=0; + DWORD pos=0; + DWORD pos_ms=0; + DWORD t_pos=0; + DWORD cur_temp=0; + UINT dtx=(UINT)rev16(*(WORD*)(data_ptr+8+4))*1000/prec; + grow_buf boo; + + int ns=0; + UINT track_pos=0,smap_pos=0; + UINT loop_start=-1; + + { + unsigned int _d; + n+=DecodeDelta(track+n,&_d); + track_pos+=_d; + } + + if (smap) + { + smap_pos=smap->events[0].pos; + } + else smap_pos=-1; + + while(1) + { + DWORD ev=0; + DWORD d=0; + { + if (n >= (data_size-26)) + break; + if (track_pos<smap_pos) + { + d=track_pos-pos; + ev=(*(DWORD*)(track+n))&0xFFFFFF; + if ((ev&0xF0)==0xF0) + { + track_pos=-1; + continue; + } + if ((ev&0xF0)==0xC0 || (ev&0xF0)==0xD0) + { + ev&=0xFFFF;n+=2; + } + else + { + n+=3; + } + if ((ev&0xFF00F0)==0x90) + { + ev=(ev&0xFF0F)|0x7F0080; + } + unsigned int _d; + n+=DecodeDelta(track+n,&_d); + track_pos+=_d; + if (n >= (data_size-26)) + break; + } + else if (smap_pos!=-1) + { + d=smap_pos-pos; + ev=0x80000000|ns; + ns++; + if (ns==smap->pos) + smap_pos=-1; + else + smap_pos=smap->events[ns].pos; + } + } + if (!ev) break; + while(t_pos<ntm && pos+d>=(UINT)tmap->data[t_pos].pos) + { + DWORD d1=tmap->data[t_pos].pos-pos; + if (loop_start==-1 && (UINT)mf->loopstart_t<=pos+d1) loop_start=pos_ms+MulDiv(cur_temp,pos+d1-mf->loopstart_t,dtx); + pos_ms+=MulDiv(cur_temp,d1,dtx); + cur_temp=tmap->data[t_pos].tm; + t_pos++; + pos+=d1; + d-=d1; + } + if (loop_start==-1 && (UINT)mf->loopstart_t<=pos+d) loop_start=pos_ms+MulDiv(cur_temp,d,dtx); + pos_ms+=MulDiv(cur_temp,d,dtx); + pos+=d; + { + MIDI_EVENT me={pos_ms,ev}; + boo.write(&me,sizeof(me)); + } + } + + free(data_ptr); + + UINT sz=boo.get_size(); + MIDI_EVENT* ret=(MIDI_EVENT*)boo.finish(); + if (ret) + { + *size=sz>>3;//sz/sizeof(MIDI_EVENT); + if (cfg_loop_type==2 && loop_start==-1) loop_start=0; + else if (cfg_loop_type==0) loop_start=-1; + if (_lstart) *_lstart=loop_start; + } + return ret; +} + + +void gb_write_delta(grow_buf & gb,DWORD d) +{ + BYTE tmp[8] = {0}; + gb.write(tmp,EncodeDelta(tmp,d)); +} + +void do_messages(HWND w,bool* b) +{ + MSG msg; + while(b && *b) + { + BOOL b=GetMessage(&msg,w,0,0); + if (b==-1 || !b) break; + DispatchMessage(&msg); + } +} + +static wchar_t cb_class[]=TEXT("CallbackWndClass0"); + +ATOM do_callback_class(WNDPROC p) +{ + cb_class[sizeof(cb_class)-2]++; + WNDCLASS wc= + { + 0,p,0,4,MIDI_callback::GetInstance(),0,0,0,0,cb_class + }; + return RegisterClassW(&wc); +} + +HWND create_callback_wnd(ATOM cl,void* p) +{ + HWND w=CreateWindowA((char*)cl,0,0,0,0,0,0,MIDI_callback::GetMainWindow(),0,MIDI_callback::GetInstance(),0); + if (w) SetWindowLong(w,0,(long)p); + return w; +} + +CTempoMap* tmap_merge(CTempoMap* m1,CTempoMap* m2) +{ + int p1=0,p2=0; + CTempoMap * ret=0; + if (m1 && m2 && m1->data && m2->data) + { + ret=tmap_create(); + if (ret) + { + while(p1<m1->pos && p2<m2->pos) + { + if (m1->data[p1].pos<=m2->data[p2].pos) + { + ret->AddEntry(m1->data[p1].pos,m1->data[p1].tm); + p1++; + } + else + { + ret->AddEntry(m2->data[p2].pos,m2->data[p2].tm); + p2++; + } + } + while(p1<m1->pos) + { + ret->AddEntry(m1->data[p1].pos,m1->data[p1].tm); + p1++; + } + while(p2<m2->pos) + { + ret->AddEntry(m2->data[p2].pos,m2->data[p2].tm); + p2++; + } + } + } + if (m1) delete m1; + if (m2) delete m2; + return ret; + +} + +KAR_ENTRY * kmap_create(MIDI_file* mf,UINT prec,UINT * num,char** text) +{ + if (!mf->kar_track) return 0; + grow_buf b_data,b_map; + KAR_ENTRY te; + BYTE *track=(BYTE*)mf->data+mf->kar_track+8; + BYTE *track_end = track+rev32(*(DWORD*)(mf->data+mf->kar_track+4)); + int time=0; + int ptr=0; + BYTE lc=0; + while(track<track_end) + { + unsigned int d; + track+=DecodeDelta(track,&d); + time+=d; + if (*track==0xFF) //meta + { + BYTE type=track[1]; + track+=2; + track+=DecodeDelta(track,&d); + char * ptr=(char*)track; + track+=d; + if ((type==0x5 || type==0x1) && d && *ptr!='@') //lyrics + { + te.time=time; + te.foo=1; + unsigned int n; + te.start=b_data.get_size(); + for(n=0;n<d;n++) + { + switch(ptr[n]) + { +// case '@': + case '\\': + case '/': + case 0x0D: + b_data.write("\x0d\x0a",2); + break; + case 0x0A: + break; + default: + te.foo=0; + b_data.write_byte(ptr[n]); + break; + } + } + te.end=b_data.get_size(); + if (te.start<te.end) b_map.write(&te,sizeof(te)); + } + } + else if (*track==0xF0) + { + track++; + track+=DecodeDelta(track,&d); + track+=d; + } + else if ((*track&0xF0)==0xF0) + { + track++;//hack + } + else + { + if (*track&0x80) lc=*(track++)&0xF0; + if (lc==0 || lc==0xC0 || lc==0xD0) track++; + else track+=2; + } + } + int map_siz = b_map.get_size(); + KAR_ENTRY * map=(KAR_ENTRY*)b_map.finish(); + map_siz/=sizeof(KAR_ENTRY); + + if (num) *num=map_siz; + + if (text) + { + b_data.write_byte(0); + *text=(char*)b_data.finish(); + } + else b_data.reset(); + + if (map) + { + int n; + + time=0; + + CTempoMap* tmap=mf->tmap; + + int pos_ms=0; + int t_pos=0; + int cur_temp=0; + int dtx=(UINT)rev16(*(WORD*)(mf->data+8+4))*1000/prec; + + for(n=0;n<map_siz;n++) + { + int d=0; + d=map[n].time-time; + + while(t_pos<tmap->pos && time+d>=tmap->data[t_pos].pos) + { + DWORD d1=tmap->data[t_pos].pos-time; + pos_ms+=MulDiv(cur_temp,d1,dtx); + cur_temp=tmap->data[t_pos].tm; + t_pos++; + time+=d1; + d-=d1; + } + pos_ms+=MulDiv(cur_temp,d,dtx); + time+=d; + map[n].time=pos_ms; + } + } + + return map; +} + +int sysex_table::num_entries() const +{ + int num=0; + entry * ptr=entries; + while(ptr) {ptr=ptr->next;num++;} + return num; +} + +int sysex_table::get_entry(int idx,BYTE ** p_data,int * p_size,int * p_time) const +{ + entry * ptr=entries; + while(ptr && idx>0) {ptr=ptr->next;idx--;} + if (!ptr) return 0; + if (p_data) *p_data = ptr->data; + if (p_size) *p_size = ptr->size; + if (p_time) *p_time = ptr->time; + return 1; +} + +void sysex_table::insert_entry(int idx,BYTE * data,int size,int time) +{ + entry ** ptr = &entries; + while(idx>0 && *ptr) + { + ptr = &(*ptr)->next; + idx--; + } + entry * insert = new entry; + insert->data = (BYTE*)malloc(size); + memcpy(insert->data,data,size); + insert->size = size; + insert->time = time; + insert->next = *ptr; + *ptr = insert; +} + +int sysex_table::remove_entry(int idx) +{ + entry ** ptr = &entries; + while(idx>0 && *ptr) + { + ptr = &(*ptr)->next; + idx--; + } + if (!*ptr) return 0; + entry * remove = *ptr; + *ptr=remove->next; + free(remove->data); + delete remove; + return 1; +} + + +int sysex_table::file_write(const char* file) const +{ + HANDLE f=CreateFileA(file,GENERIC_WRITE,0,0,CREATE_ALWAYS,0,0); + if (f==INVALID_HANDLE_VALUE) return 0; + + int size; + void * ptr = memblock_write(&size); + DWORD bw = 0; + WriteFile(f,ptr,size,&bw,0); + free(ptr); + CloseHandle(f); + return 1; +} + +void * sysex_table::memblock_write(int * size) const +{ + grow_buf wb; + + entry * ptr; + //MAGIC:DWORD , NUM: DWORD,DATA_SIZE:DWORD, offsets, sleep,data + DWORD temp; + temp=MHP_MAGIC; + wb.write(&temp,4); + temp=num_entries(); + wb.write(&temp,4); + + temp=0; + for(ptr=entries;ptr;ptr=ptr->next) temp+=ptr->size; + wb.write(&temp,4); + temp=0; + for(ptr=entries;ptr;ptr=ptr->next) + { + wb.write(&temp,4); + temp+=ptr->size; + } + for(ptr=entries;ptr;ptr=ptr->next) + { + temp = ptr->time; + wb.write(&temp,4); + } + + for(ptr=entries;ptr;ptr=ptr->next) + { + wb.write(ptr->data,ptr->size); + } + + if (size) *size = wb.get_size(); + + return wb.finish(); +} + +int sysex_table::memblock_read(const void * block,int size) +{ + entry * ptr; + const BYTE * src = (const BYTE*)block; + DWORD temp,total_size,total_num; + + + if (*(DWORD*)src!=MHP_MAGIC) return 0; + src+=4; + + temp=total_num=*(DWORD*)src; + src+=4; + if (total_num>0xFFFF) return 0; + + reset(); + while(temp>0) + { + ptr=new entry; + ptr->next=entries; + entries = ptr; + temp--; + } + + total_size=*(DWORD*)src; + + UINT n; + + for(n=0,ptr=entries;ptr;ptr=ptr->next,n++) + { +//offset : 12 + 4 * n; +//time : 12 + 4 * total_num + 4 * n; +//data : 12 + 8 * total_num + offset + DWORD offset,time,offset2; + src = (const BYTE*)block + 12 + 4*n; + offset=*(DWORD*)src; + + if (n!=total_num-1) offset2=*(DWORD*)(src+4); + else offset2=total_size; + ptr->size = offset2-offset; + src = (const BYTE*)block + 12 + 4*total_num + 4*n; + time = *(DWORD*)src; + + ptr->data = (BYTE*)malloc(offset2); + src = (const BYTE*)block + 12 + 8*total_num + offset; + memcpy(ptr->data,src,ptr->size); + + ptr->time = time; + } + + return 1; +} + +int sysex_table::file_read(const char* file) +{ + + HANDLE f=CreateFileA(file,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,0,0); + if (f==INVALID_HANDLE_VALUE) return 0; + int size = GetFileSize(f,0); + void * temp = malloc(size); + DWORD br = 0; + ReadFile(f,temp,size,&br,0); + CloseHandle(f); + int rv = memblock_read(temp,size); + free(temp); + return rv; +} + +int sysex_table::print_preview(int idx,char * out) const +{ + BYTE* data; + int size,time; + if (!get_entry(idx,&data,&size,&time)) return 0; + int size2=size; + if (size2>10) size2=10; + wsprintfA(out,WASABI_API_LNGSTRING(STRING_MS_FMT),time); + while(out && *out) out++; + int n; + for(n=0;n<size2;n++) + { + wsprintfA(out," %02X",data[n]); + out+=3; + } + + if (size!=size2) + { + strcpy(out,"..."); + } + return 1; +} + +void sysex_table::print_edit(int idx,HWND wnd) const +{ + BYTE* data; + int size,time; + if (!get_entry(idx,&data,&size,&time)) {SetWindowTextA(wnd,"");return;} + if (size<=2) {SetWindowTextA(wnd,"");return;} + char *temp = (char*)malloc(3*size); + char *ptr = temp; + int n; + for(n=1;n<size-1;n++) + { + wsprintfA(ptr,"%02X ",data[n]); + ptr+=3; + } + ptr[-1]=0; + SetWindowTextA(wnd,temp); + free(temp); +} + +void sysex_table::copy(const sysex_table & src) +{ + reset(); + int idx=0; + BYTE * data; + int size,time; + while(src.get_entry(idx++,&data,&size,&time))//ASS SLOW + insert_entry(idx,data,size,time); +} + +//special sysex table cfg_var hack +class cfg_var_sysex : private cfg_var +{ +private: + sysex_table * tab; + + virtual void read(HKEY hk) + { + int size=reg_get_struct_size(hk); + if (size>0) + { + void * temp = malloc(size); + if (temp) + { + reg_read_struct(hk,temp,size); + tab->memblock_read(temp,size); + free(temp); + } + } + } + virtual void write(HKEY hk) + { + void * data; + int size; + data = tab->memblock_write(&size); + if (data) reg_write_struct(hk,data,size); + + } + virtual void reset() {tab->reset();} + +public: + cfg_var_sysex(const char * name,sysex_table * p_tab) : cfg_var(name) {tab=p_tab;} +}; + +static cfg_var_sysex thevar("sysex_table",&cfg_sysex_table); diff --git a/Src/Plugins/Input/in_midi/utils.h b/Src/Plugins/Input/in_midi/utils.h new file mode 100644 index 00000000..1628e846 --- /dev/null +++ b/Src/Plugins/Input/in_midi/utils.h @@ -0,0 +1,193 @@ +#if !defined(_UTILS_H_INCLUDED_) +#define _UTILS_H_INCLUDED_ + + +#include "../pfc/pfc.h" + + +class NOVTABLE CStream +{ +public: + virtual UINT ReadData(void*,UINT,bool*)=0; + virtual void Flush()=0; + virtual ~CStream() {}; + + //for sampling + virtual void Pause(int) {}; + virtual void Eof() {} +}; + +class CPipe : public CStream +{ + BYTE* buf; + volatile UINT buf_s,buf_n,buf_rp,buf_wp; + critical_section sec; + UINT align; + volatile bool closed; +public: + void WriteData(void*,UINT); + UINT CanWrite() {return buf_s-buf_n;} + UINT ReadData(void*,UINT,bool*); + void Flush() + { + sec.enter(); + buf_n=0; + sec.leave(); + + } + CPipe(UINT _align=4,UINT freq=44100) + { + buf_s=MulDiv(1024*256,freq,22050); + buf=(BYTE*)malloc(buf_s); + buf_wp=buf_rp=0; + buf_n=0; + align=_align; + closed=0; + } + ~CPipe() + { + if (buf) free(buf); + } + void Eof() {closed=1;} +}; + + +class MIDI_file; + +#pragma pack(push) +#pragma pack(1) +typedef struct +{ + WORD fmt,trax,dtx; +} MIDIHEADER; +#pragma pack(pop) + +WORD _inline rev16(WORD x) {return (x>>8)|(x<<8);} +//#define rev16(X) (((X)&0xFF)<<8)|(((X)>>8)&0xFF) +DWORD _fastcall rev32(DWORD); + +#define _rv(X) ((((DWORD)(X)&0xFF)<<24)|(((DWORD)(X)&0xFF00)<<8)|(((DWORD)(X)&0xFF0000)>>8)|(((DWORD)(X)&0xFF000000)>>24)) + +#define FixHeader(H) {(H).fmt=rev16((H).fmt);(H).trax=rev16((H).trax);(H).dtx=rev16((H).dtx);} + +struct write_buf; + +typedef struct +{ + int pos,tm; +} TMAP_ENTRY; + +struct CTempoMap +{ +public: + TMAP_ENTRY *data; + int pos,size; + void AddEntry(int _p,int tm); + ~CTempoMap() {if (data) free(data);} + int BuildTrack(grow_buf & out); +}; + +CTempoMap* tmap_merge(CTempoMap*,CTempoMap*); + +typedef struct +{ + int pos,ofs,len; +} SYSEX_ENTRY; + +struct CSysexMap +{ +public: + DWORD d_size,e_size; + SYSEX_ENTRY *events; + BYTE* data; + int pos,d_pos; + void CleanUp(); + void AddEvent(const BYTE* e,DWORD s,DWORD t); + ~CSysexMap(); + CSysexMap* Translate(MIDI_file* _mf);//MIDI_file* mf + int BuildTrack(grow_buf & out); + const char* GetType(); +}; + + +typedef struct tagKAR +{ + UINT time; + UINT start,end; + BOOL foo; +} KAR_ENTRY; + + +KAR_ENTRY * kmap_create(MIDI_file* mf,UINT prec,UINT * num,char** text); + + +CTempoMap* tmap_create(); +CSysexMap* smap_create(); + + +int EncodeDelta(BYTE* dst,int d); +unsigned int DecodeDelta(const BYTE* src,unsigned int* _d, unsigned int limit=-1); +int ReadSysex(const BYTE* src,int ml); + +char* BuildFilterString(UINT res_id, char* ext, int* len); +BOOL DoOpenFile(HWND w,char* fn,UINT res_id,char* ext,BOOL save); + +typedef void (*SYSEXFUNC)(void*,BYTE*,UINT); +void sysex_startup(SYSEXFUNC,void*); +void sysex_startup_midiout(UINT m_id); +bool need_sysex_start(); + +typedef struct +{ + DWORD tm; + DWORD ev; +} MIDI_EVENT; + +MIDI_EVENT* do_table(MIDI_file* mf,UINT prec,UINT * size,UINT* _lstart,DWORD cflags); + +void gb_write_delta(grow_buf & gb,DWORD d); + +void do_messages(HWND w,bool* br); +ATOM do_callback_class(WNDPROC p); +HWND create_callback_wnd(ATOM cl,void* p); + +class sysex_table +{ +private: + struct entry + { + entry * next; + int size,time; + BYTE * data; + }; + entry * entries; + enum {MHP_MAGIC='0PHM'}; +public: + sysex_table() {entries=0;} + ~sysex_table() {reset();} + int num_entries() const; + int get_entry(int idx,BYTE ** p_data,int * p_size,int * p_time) const; + void insert_entry(int idx,BYTE * data,int size,int time); + int remove_entry(int idx); + + inline void add_entry(BYTE * data,int size,int time) {insert_entry(num_entries(),data,size,time);} + inline void modify_entry(int idx,BYTE * data,int size,int time) {remove_entry(idx);insert_entry(idx,data,size,time);} + inline void reset() {while(entries) remove_entry(0);} + inline int get_time(int idx) const {int time;return get_entry(idx,0,0,&time) ? time : 0;} + + int file_read(const char * path); + int file_write(const char * path) const; + void * memblock_write(int * size) const; + int memblock_read(const void * ptr,int size); + + int print_preview(int idx,char * out) const; + void print_edit(int idx,HWND wnd) const; + + void copy(const sysex_table & src); + sysex_table(const sysex_table & src) {entries=0;copy(src);} + sysex_table& operator=(const sysex_table & src) {copy(src);return *this;} + int is_empty() {return !entries;} +}; + + +#endif diff --git a/Src/Plugins/Input/in_midi/version.rc2 b/Src/Plugins/Input/in_midi/version.rc2 new file mode 100644 index 00000000..990863e5 --- /dev/null +++ b/Src/Plugins/Input/in_midi/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 3,57,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", "3,57,0,0" + VALUE "InternalName", "Nullsoft MIDI Player" + VALUE "LegalCopyright", "Copyright © 2000-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_midi.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_midi/wa2.cpp b/Src/Plugins/Input/in_midi/wa2.cpp new file mode 100644 index 00000000..71e39ff2 --- /dev/null +++ b/Src/Plugins/Input/in_midi/wa2.cpp @@ -0,0 +1,820 @@ +#include "main.h" +#include "resource.h" +#include <shlwapi.h> +#include <api/service/waServiceFactory.h> +#include "../winamp/wa_ipc.h" +#include "../Agave/language/api_language.h" +#include "CompressionUtility.h" +#include "minizip/unzip.h" + +static bool paused; +static int volume=255; +static int pan=0; +static string cur_file; +static HANDLE thread; +static HINSTANCE hRFdll; +static reader_source * pRF; + +// wasabi based services for localisation support +api_language *WASABI_API_LNG = 0; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; + +#define IPC_GETHTTPGETTER 240 + +typedef int (*t_getf)(HWND hwnd, char *url, char *file, char *dlgtitle); + +static WReader * get_reader(const char * fn);//wa2 hack + + +static int reader_process_file(WReader * r,const char * fn,void * &out_data, size_t &out_size) +{ + void * data=0; + int size=0; + char ks=0; + if (r->Open((char*)fn,&ks)) + return 0; + + size = r->GetLength(); + + if (size==-1 || size<0x20) + return 0; + + data=malloc(size);//scan funcs assume that theres at least 256 data + + if (!data) + return 0; + + if (r->Read((char*)data,size,&ks)!=size) + { + free(data); + return 0; + } + + void* pvOut; + size_t nSizeOut = 0; + // GZIP + if (((*(DWORD*)data) & 0xFFFFFF) == 0x088b1f) + { + if (CompressionUtility::DecompressGZip(data, size, &pvOut, nSizeOut) >= 0) + { + out_data = pvOut; + out_size = nSizeOut; + return 1; + } + else + { + return 0; + } + } + // PKZIP + else if (*(DWORD*)data == 0x04034B50) + { + if (CompressionUtility::DecompressPKZip(fn, &pvOut, nSizeOut) >= 0) + { + out_data = pvOut; + out_size = nSizeOut; + return 1; + } + else + { + return 0; + } + } + + out_size = size; + out_data = data; + return 1; +} + +MIDI_file * wa2_open_file(const char * url) +{ + WReader * r=get_reader(url); + if (!r) return 0; + void * data=0; + size_t size=0; + MIDI_file* mf=0; + if (reader_process_file(r,url,data,size)) + { + mf=MIDI_file::Create(url,data,size); + free(data); + } + delete r; + return mf; +} + +static void build_fmtstring(); + +static cfg_int cfg_mod_output("dev_output",1); + +static void wa2_onCfgUpdate() +{ + MIDI_device * dev = MIDI_driver::find_device(cfg_driver,cfg_device); + if (!dev) + { + dev = MIDI_driver::find_device_default(); + } + + //hack for wa2input.wac in wa3 + mod.UsesOutputPlug=(dev->has_output() || (cfg_smp && cfg_sampout)) ? 1 : 0x8001; + cfg_mod_output=mod.UsesOutputPlug; + build_fmtstring(); +} + +static char fmts_string[1024]; + +#define NSEEK(a) {while(!(cfg_ext_mask&(1<<a)) && a<n_exts) a++;} +static void build_fmtstring() +{ + UINT n_exts = MIDI_core::FileTypes_GetNum(); + if (!cfg_ext_mask) + { + fmts_string[1]=fmts_string[0]=0; + return; + } + UINT n=0; + NSEEK(n); + const char* d=MIDI_core::FileTypes_GetDescription(n); + char* o=fmts_string; + while(1) + { + UINT f=n; + while(n<n_exts && d==MIDI_core::FileTypes_GetDescription(n)) + { + const char * e=MIDI_core::FileTypes_GetExtension(n); + while(e && *e) *(o++)=*(e++); + n++; + NSEEK(n); + *(o++)=';'; + } + o[-1]=0; + while(d && *d) *(o++)=*(d++); + *(o++)=' '; + *(o++)='('; + while(f<n) + { + const char * e=MIDI_core::FileTypes_GetExtension(f); + while(e && *e) *(o++)=*(e++); + f++; + NSEEK(f); + *(o++)=','; + } + o[-1]=')'; + *(o++)=0; + if (n>=n_exts) break; + d=MIDI_core::FileTypes_GetDescription(n); + } + if (cfg_extra_exts.get_string().length()>0) + { + d=cfg_extra_exts; + while(d && *d) *(o++)=*(d++); + *(o++)=0; + d=WASABI_API_LNGSTRING(STRING_FILES_OTHER); + while(d && *d) *(o++)=*(d++); + d=cfg_extra_exts; + while(d && *d) + { + if (*d==';') *o=','; + else *o=*d; + o++; + d++; + } + *(o++)=')'; + *(o++)=0; + } + *(o++)=0; +} +#undef NSEEK + + +static void Config(HWND p) +{ + if (MIDI_core::Config(p)) + { + MIDI_core::WriteConfig(); + wa2_onCfgUpdate(); + } +} + +void About(HWND); + +class CMemReader : public WReader +{ +public: + BYTE* mem; + UINT sz; + UINT pos; + int Open(char *url, char *killswitch); + int Read(char *buffer, int length, char* killswitch) {if (!mem) return 0;if (length+pos>sz) length=sz-pos;memcpy(buffer,mem+pos,length);pos+=length;return length;} + int GetLength(void) {return sz;} + int CanSeek(void) {return 1;}; + int Seek(int position, char*killswitch) {pos=position;return 0;}; + + CMemReader() {mem=0;sz=0;pos=0;} + ~CMemReader() {if (mem) free(mem);} + +}; + +static int Download(char* url,UINT* f_size,BYTE** m_buf) +{ + typedef int (*t_getf)(HWND hwnd, char *url, char *file, char *dlgtitle); + + t_getf getf; + + int t=SendMessage(mod.hMainWindow,WM_USER,0,IPC_GETHTTPGETTER); + if (!t || t==1) + { +#ifndef WINAMPX + MessageBoxA(mod.hMainWindow,WASABI_API_LNGSTRING(STRING_URL_ERROR),ERROR,MB_ICONERROR); +#endif + return 0; + } + else + { + int rv=0; + char tmp[MAX_PATH] = {0}; + get_temp_file(tmp); + HANDLE f; + DWORD br = 0,s = 0; + void* b; + getf=(t_getf)t; + if (getf(mod.hMainWindow,url,tmp,WASABI_API_LNGSTRING(STRING_RETRIEVING_FILE))) goto fail; + f=CreateFileA(tmp,GENERIC_READ,0,0,OPEN_EXISTING,0,0); + if (f==INVALID_HANDLE_VALUE) goto fail; + br=0; + s=GetFileSize(f,0); + if (!s) goto fail; + b=malloc(s); + if (!b) goto fail; + ReadFile(f,b,s,&br,0); + rv=1; + *f_size=br; + *m_buf=(BYTE*)b; +fail: + CloseHandle(f); + DeleteFileA(tmp); + return rv; + } +} + + +int CMemReader::Open(char* url,char*) +{ + sz=pos=0; + if (mem) {free(mem);mem=0;} + HANDLE f=CreateFileA(url,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,0,OPEN_EXISTING,0,0); + if (f!=INVALID_HANDLE_VALUE) + { + sz=GetFileSize(f,0); + mem=(BYTE*)malloc(sz); + if (!mem) {CloseHandle(f);return 1;} + ReadFile(f,mem,sz,(DWORD*)&sz,0); + CloseHandle(f); + return 0; + } + return !Download(url,&sz,&mem); +} + +class CFileReader : public WReader +{ + public: + HANDLE f; + CFileReader() {f=0;}; + ~CFileReader() {if (f) CloseHandle(f);} + int Open(char *url, char*killswitch) {f=CreateFileA(url,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,0,OPEN_EXISTING,0,0);return f==INVALID_HANDLE_VALUE;} + int Read(char *buffer, int length, char*killswitch) {DWORD br=0;ReadFile(f,buffer,length,&br,0);return br;} + int GetLength(void) {return GetFileSize(f,0);} + int CanSeek(void) {return 1;}; + int Seek(int position, char*killswitch) {SetFilePointer(f,position,0,FILE_BEGIN);return 0;} + +}; + +static WReader *get_reader(const char* url) +{ + if (!_strnicmp(url,"file://",7)) url+=7; + WReader* ret=0; + if (pRF && pRF->ismine((char*)url)) ret=pRF->create(); + if (ret) + { + ret->m_player=0; + return ret; + } + + if (_strnicmp(url,"http://",7)==0 || _strnicmp(url,"ftp://",6)==0 || _strnicmp(url,"https://",8)==0) return new CMemReader; + return new CFileReader(); +} + +int Init() +{ + if (!IsWindow(mod.hMainWindow)) + return IN_INIT_FAILURE; + + // loader so that we can get the localisation service api for use + waServiceFactory *sf = mod.service->service_getServiceByGuid(languageApiGUID); + if (sf) WASABI_API_LNG = reinterpret_cast<api_language*>(sf->getInterface()); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(mod.hDllInstance,InMidiLangGUID); + + static wchar_t szDescription[256]; + swprintf(szDescription,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_MIDI_PLAYER),VER); + mod.description = (char*)szDescription; + + MIDI_core::GlobalInit(); + mod.UsesOutputPlug=cfg_mod_output; + build_fmtstring(); + return IN_INIT_SUCCESS; +} + +void Quit() +{ + MIDI_core::GlobalQuit(); +} + +void GetFileInfo(const char *url, char *title, int *len) +{ + if (!url || !*url) + { + url=cur_file; + } + if (len) *len=0; + if (title) *title=0; + + char ks=0; + + bool file_local=0; + MIDI_file * file=0; + if (MIDI_core::getFile() && !_stricmp(url,MIDI_core::getFile()->path)) + { + file = MIDI_core::getFile()->AddRef(); + } + + if (!file) + { + file = wa2_open_file(url); + if (!file) { + return; + } + file_local=1; + } + + if (len) + *len=file->GetLength(); + if (title) + file->GetTitle(title,256); + + file->Free(); +} + +BOOL CALLBACK InfoProc(HWND,UINT,WPARAM,LPARAM); + +int show_rmi_info(HWND w,MIDI_file* mf); + +int infoDlg(const char *fn, HWND hwnd) +{ + int rv=1; + MIDI_file *mf=wa2_open_file(fn); + + if (!mf) return INFOBOX_UNCHANGED; + + if (cfg_rmi_def) rv=show_rmi_info(hwnd,mf); + else + { + rv = WASABI_API_DIALOGBOXPARAM(IDD_INFO,hwnd,InfoProc,(LPARAM)mf); + } + if (!rv && !_stricmp(mf->path,cur_file)) + { + PostMessage(mod.hMainWindow,WM_USER,0,243); + } + mf->Free(); + return rv; +} + +int InfoBox(const char *file, HWND parent) +{ + if (!file) file=cur_file; + return infoDlg(file,parent); +} + +static char kill; +static int pos_ms; +static int seek_to; +static bool out_open; + +DWORD WINAPI PlayThread(void*) +{ +#ifdef USE_LOG + log_write("PlayThread"); +#endif + short * visbuf; + + char *sample_buf; + int sr,bps,nch; + pos_ms=0; + int pos_base=0; + int samp_wr=0; + int max_l=0; + MIDI_core::GetPCM(&sr,&nch,&bps); + int s_size=576 * (bps/8) * nch; + if (bps>16) + { + visbuf=(short*)malloc(576*2*nch); + } + else visbuf=0; + + sample_buf = (char*)malloc(576 * 2 * (bps/8) * nch); + + bool done=0; + while(!(kill&1)) + { +#ifdef USE_LOG + log_write("main loop"); +#endif + if (paused) { +#ifdef USE_LOG + log_write("paused"); +#endif + Sleep(10); + continue; + } + if (seek_to!=-1) + { +#ifdef USE_LOG + log_write("seeking"); +#endif + if (MIDI_core::SetPosition(seek_to)) + { + pos_ms=seek_to; + if (out_open) + { + mod.outMod->Flush(pos_ms); + } + pos_base=pos_ms; + samp_wr=0; + done=0; + } + kill&=~2; + seek_to=-1; + } + if (done) + { +#ifdef USE_LOG + log_write("done"); +#endif + if (!mod.outMod->IsPlaying()) + { + PostMessage(mod.hMainWindow,WM_WA_MPEG_EOF,0,0); + break; + } + Sleep(10);continue; + } +#ifdef USE_LOG + log_write("calling GetSamples"); +#endif + int l=MIDI_core::GetSamples(sample_buf,s_size,&kill); + if (kill&1) { +#ifdef USE_LOG + log_write("kill&1"); +#endif + break; + } + if (kill&2) { +#ifdef USE_LOG + log_write("kill&2"); +#endif + continue; + } + if (l<=0 && !paused) + { +#ifdef USE_LOG + log_write("done(?)"); +#endif + done=1; + if (out_open) + { + mod.outMod->Write(sample_buf,0); + continue; + } + else + { + PostMessage(mod.hMainWindow,WM_WA_MPEG_EOF,0,0); + break; + } + } + if (mod.dsp_isactive()) + { +#ifdef USE_LOG + log_write("DSP"); +#endif + l=(8*l)/(bps*nch); + l=mod.dsp_dosamples((short*)sample_buf,l,bps,nch,sr); + l*=(nch*bps)/8; + } + if (out_open) + { +#ifdef USE_LOG + log_write("sending to output"); +#endif + if (kill&1) break; + while(mod.outMod->CanWrite()<l && !kill) Sleep(2); + if (kill&1) break; + + if (!kill) mod.outMod->Write((char*)sample_buf,l); + } + { + char * vis=sample_buf; + UINT vis_bps=bps; + if (bps>16) + { + int n; + UINT d=bps>>3; + char * foo=sample_buf+d-2; + for(n=0;n<576*nch;n++) + { + visbuf[n]=*(short*)foo; + foo+=d; + } + vis=(char*)visbuf; + vis_bps=16; + } +#ifdef USE_LOG + log_write("doing vis"); +#endif + mod.SAAddPCMData(vis,nch,vis_bps,pos_ms); + mod.VSAAddPCMData(vis,nch,vis_bps,pos_ms); + + } + samp_wr+=(8*l)/(bps*nch); + pos_ms=pos_base+MulDiv(1000,samp_wr,sr); + } + + free(sample_buf); + if (visbuf) free(visbuf); + return 0; +} + +int initDefaultDeviceShit() +{ + //CT> find default device if no device set + MIDI_device * dev = MIDI_driver::find_device(cfg_driver,cfg_device); + if(dev) return 1; + + //reinit to default + MIDI_driver *driver=MIDI_driver::driver_enumerate(0); + if(!driver) return 0; + MIDI_device *device=driver->device_enumerate(0); + if(!device) return 0; + cfg_driver=driver->get_guid(); + cfg_device=device->get_guid(); + return 1; +} + +int Play(const char *fn) +{ + if(!initDefaultDeviceShit()) return 0; + + paused=0; + seek_to=-1; + kill=0; + + if (!MIDI_core::Init()) return 0; + + if (!MIDI_core::UsesOutput()) + { + MIDI_core::SetVolume(volume); + MIDI_core::SetPan(pan); + } + else + { + MIDI_core::SetVolume(255); + MIDI_core::SetPan(0); + } + + MIDI_file * file = wa2_open_file(fn); + if (!file) return -1; + + int rv=MIDI_core::OpenFile(file); + + file->Free(); + + if (rv==0) + { + MIDI_core::Close(); + return 1; + } + cur_file=fn; + int sr,nch,bps; + MIDI_core::GetPCM(&sr,&nch,&bps); + + { + MIDI_file * mf=MIDI_core::getFile(); + UINT nc=0; + if (mf) nc=mf->info.channels; + mod.SetInfo(nc*10000,sr/1000,2,1); + } + + if (MIDI_core::HavePCM()) + { + int max_l=0; + MIDI_core::GetPCM(&sr,&nch,&bps); + if (MIDI_core::UsesOutput()) + { +#ifdef USE_LOG + log_write("output init"); +#endif + max_l=mod.outMod->Open(sr,nch,bps,-1,-1); + if (max_l<0) + { + MIDI_core::Close(); + return 1; + } + out_open=1; + mod.outMod->SetVolume(volume); + mod.outMod->SetPan(pan); + } + mod.SAVSAInit(max_l,sr); + mod.VSASetInfo(sr,nch); +#ifdef USE_LOG + log_write("Creating thread"); +#endif + DWORD id; + thread=CreateThread(0,0,PlayThread,0,CREATE_SUSPENDED,&id); +#ifndef _DEBUG + SetThreadPriority(thread,THREAD_PRIORITY_TIME_CRITICAL); +#endif + ResumeThread(thread); + + } + else + { +#ifdef USE_LOG + log_write("threadless mode"); +#endif + thread=0; + } + + return 0; +} + +void Pause() +{ + if (MIDI_core::HavePlayer() && !paused) + { + MIDI_core::Pause(paused=1); + if (MIDI_core::UsesOutput()) mod.outMod->Pause(1); + } +} + +void UnPause() +{ + if (MIDI_core::HavePlayer() && paused) + { + MIDI_core::Pause(paused=0); + if (MIDI_core::UsesOutput()) + { + mod.outMod->Flush(0); + mod.outMod->Pause(0); + } + } +} + +int IsPaused() +{ + return paused; +} + +void Stop() +{ + if (thread) + { + kill|=1; + WaitForSingleObject(thread,INFINITE); + CloseHandle(thread); + thread=0; + mod.SAVSADeInit(); + + if (out_open) + { + out_open=0; + mod.outMod->Close(); + } + } + + MIDI_core::Close(); +} + +void EQSet(int on, char data[10], int preamp) +{ +} + +int GetLength() +{ + return MIDI_core::GetLength(); +} + +int GetOutputTime() +{ + if (seek_to!=-1) return seek_to; + + if (thread && MIDI_core::UsesOutput()) return pos_ms+mod.outMod->GetOutputTime()-mod.outMod->GetWrittenTime(); + else return MIDI_core::GetPosition(); +} + +void SetOutputTime(int t) +{ + if (thread) + { + seek_to=t; + kill|=2; + } + else MIDI_core::SetPosition(t); +} + +void SetVolume(int v) +{ + volume=v; + if (MIDI_core::UsesOutput()) mod.outMod->SetVolume(v); + else MIDI_core::SetVolume(v); +} + +void SetPan(int p) +{ + pan=p; + if (MIDI_core::UsesOutput()) mod.outMod->SetPan(p); + else MIDI_core::SetPan(p); +} + +In_Module mod= +{ + IN_VER_RET, + "nullsoft(in_midi.dll)", + 0,0, + + fmts_string, + + 1, + 1, + + Config, + About, + + Init, + Quit, + + GetFileInfo, + InfoBox, + + MIDI_core::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 &mod; + } +} + +void MIDI_callback::NotifyEOF() {PostMessage(mod.hMainWindow,WM_WA_MPEG_EOF,0,0);} +HWND MIDI_callback::GetMainWindow() {return mod.hMainWindow;} +HINSTANCE MIDI_callback::GetInstance() {return mod.hDllInstance;} + +void MIDI_callback::Error(const char * tx) +{ +#ifndef WINAMPX + MessageBoxA(mod.hMainWindow,tx,0,MB_ICONERROR); +#endif +} + +BOOL APIENTRY DllMain(HANDLE hMod,DWORD r,void*) +{ + if (r==DLL_PROCESS_ATTACH) + { + DisableThreadLibraryCalls((HMODULE)hMod); + } + return 1; + +} + +void MIDI_callback::Idle(int ms) +{ + int start = timeGetTime(); + do Sleep(1); while( (int)timeGetTime() - start < ms); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_midi/wa3.cpp b/Src/Plugins/Input/in_midi/wa3.cpp new file mode 100644 index 00000000..661a5001 --- /dev/null +++ b/Src/Plugins/Input/in_midi/wa3.cpp @@ -0,0 +1,395 @@ +#include "../studio/services/svc_mediaconverter.h" +#include "../studio/wac.h" +#include "../common/rootcomp.h" +#include "../studio/services/svc_action.h" +#include "../unpack/unpack_helper.h" +#include "main.h" + +#define WACNAME WACcnv_midi + +class WACNAME : public WAComponentClient{ +public: + WACNAME(); + virtual ~WACNAME(); + + virtual const char *getName() { return NAME; }; + virtual GUID getGUID(); + + virtual void onCreate(); + virtual void onDestroy(); + + virtual int getDisplayComponent() { return FALSE; }; + + virtual CfgItem *getCfgInterface(int n) { return this; } + +private: +}; + + +static WACNAME wac; +WAComponentClient *the = &wac; + + +// {28FDCD38-26A2-482c-A691-55901A355D9E} +static const GUID guid = +{ 0x28fdcd38, 0x26a2, 0x482c, { 0xa6, 0x91, 0x55, 0x90, 0x1a, 0x35, 0x5d, 0x9e } }; + +GUID WACNAME::getGUID() { + return guid; +} + +static void update_extensions() +{ + static int old_mask; + int new_mask = cfg_ext_mask; + int n; + for(n=0;n<MIDI_core::FileTypes_GetNum();n++) + { + int bit = 1<<n; + if ( (new_mask & bit) && !(old_mask & bit) ) + api->core_registerExtension(StringPrintf("*.%s",MIDI_core::FileTypes_GetExtension(n)),MIDI_core::FileTypes_GetDescription(n),"Audio"); + else if ( !(new_mask & bit) && (old_mask & bit) ) + { + api->core_unregisterExtension(StringPrintf("*.%s",MIDI_core::FileTypes_GetExtension(n))); + } + } + old_mask = new_mask; +} + +void WACNAME::onCreate() +{ + // {EDAA0599-3E43-4eb5-A65D-C0A0484240E7} + static const GUID cfg_audio_guid = + { 0xedaa0599, 0x3e43, 0x4eb5, { 0xa6, 0x5d, 0xc0, 0xa0, 0x48, 0x42, 0x40, 0xe7 } }; + + registerSkinFile("xml/midi-prefs.xml"); + + api->preferences_registerGroup("winamp.preferences.midi", "MIDI playback", guid, cfg_audio_guid); + + MIDI_core::GlobalInit(); + + + update_extensions(); +} + +void WACNAME::onDestroy() { + MIDI_core::GlobalQuit(); +} + +static void check_messages() +{ + MSG msg; + while(PeekMessage(&msg,0,0,0,PM_REMOVE)) + DispatchMessage(&msg); +} + +//note: multiinstance support is NOT working, and will never be; it makes no sense anyway. also, multiinstance safety was totally fuct in directmusic drivers last time i bothered trying. + +class cnv_MIDI : public svc_mediaConverterI { +private: + static critical_section core_owner_sync; + static cnv_MIDI * core_owner; + + DWORD thread_id; + + MemBlock<char> sample_buffer; + + MIDI_file * file; + + int is_open; + int eof_flag; + + void core_reset() + { + core_owner_sync.enter(); + if (core_owner==this) {core_owner=0;MIDI_core::Close();} + core_owner_sync.leave(); + if (file) {file->Free();file=0;} + is_open=0; + eof_flag=0; + } + + int core_takeover() + { + core_owner_sync.enter(); + if (core_owner!=this) + { + if (core_owner!=0) {core_owner_sync.leave();return 0;} + core_owner=this; + thread_id = GetCurrentThreadId(); + MIDI_core::Init(); + } + core_owner_sync.leave(); + return 1; + } + + int check_file(MediaInfo * infos) + { + if (file && !STRICMP(file->path,infos->getFilename())) return 1; + core_reset(); + MemBlock<char> data; + int size; + + try { + svc_fileReader * reader = infos->getReader(); + if (!reader) return 0; + size = reader->getLength(); + if (size<=0) return 0; + reader->seek(0); + int firstread = size > 256 ? 256 : size; + data.setSize(firstread); + if (reader->read(data.getMemory(),firstread)!=firstread) return 0; + if (MIDI_file::HeaderTest(data.getMemory(),size)) + { + if (firstread != size) + { + if (data.setSize(size)==0) return 0; + if (reader->read(data.getMemory()+firstread,size-firstread)!=size-firstread) return 0; + } + } + else + { + void * unpack = unpack_helper::unpack_getHandle(reader); + if (!unpack) return 0; + size = api->fileGetFileSize(unpack); + firstread = size > 256 ? 256 : size; + data.setSize(firstread); + if (api->fileRead(data.getMemory(),firstread,unpack)!=firstread) {api->fileClose(unpack);return 0;} + if (!MIDI_file::HeaderTest(data.getMemory(),size)) {api->fileClose(unpack);return 0;} + + if (firstread != size) + { + if (data.setSize(size)==0) {api->fileClose(unpack);return 0;} + if (api->fileRead(data.getMemory()+firstread,size-firstread,unpack)!=size-firstread) {api->fileClose(unpack);return 0;} + } + + api->fileClose(unpack); + } + file = MIDI_file::Create(infos->getFilename(),data.getMemory(),size); + + return !!file; + } + catch(...) + { + file = 0; + return 0; + } + } + +public: + + static const char *getServiceName() { return NAME; } + + cnv_MIDI() + { + file=0; + is_open=0; + eof_flag=0; + thread_id=0; + } + + ~cnv_MIDI() + { + core_reset(); + } + + virtual int canConvertFrom(svc_fileReader *reader, const char *name, const char *chunktype) + { + return reader && !chunktype && name && MIDI_core::IsOurFile(name); + } + + virtual const char *getConverterTo() + { + if (!core_takeover()) return "FINAL"; + return MIDI_core::UsesOutput() ? "PCM" : "FINAL"; + } + + virtual int getInfos(MediaInfo *infos) + { + if (!check_file(infos)) return 0; + infos->setTitle(Std::filename(file->path)); + infos->setLength(file->len); + + infos->setInfo( + StringPrintf("%sMIDI %i channels", + file->info.e_type ? StringPrintf("%s ",file->info.e_type) : "" + ,file->info.channels) + ); + + return 1; + } + + virtual int processData(MediaInfo *infos, ChunkList *chunk_list, bool *killswitch) + { + if (!check_file(infos)) return 0; + if (!core_takeover()) return 0; + + if (!is_open) + { + MIDI_core::SetVolume(api->core_getVolume(m_coretoken)); + MIDI_core::SetPan(api->core_getPan(m_coretoken)); + + if (!MIDI_core::OpenFile(file)) + return 0; + is_open=1; + eof_flag=0; + } + + check_messages(); + + if (!MIDI_core::HavePCM()) {Sleep(1);check_messages();return eof_flag ? 0 : 1;} + else + { + int srate,nch,bps; + int size; + MIDI_core::GetPCM(&srate,&nch,&bps); + size = 576 * nch * (bps/8); + if (sample_buffer.getSize()<size) sample_buffer.setSize(size); + size = MIDI_core::GetSamples(sample_buffer.getMemory(),size,(char*)killswitch); + if (size<=0) + return 0; + + ChunkInfosI *ci=new ChunkInfosI(); + ci->addInfo("srate",srate); + ci->addInfo("bps",bps); + ci->addInfo("nch",nch); + + chunk_list->setChunk("PCM",sample_buffer.getMemory(),size,ci); + + + return 1; + } + } + + virtual int getLatency(void) { return 0; } + + // callbacks + virtual int corecb_onSeeked(int newpos) + { + if (core_owner==this) MIDI_core::SetPosition(newpos); + return 0; + } + + int getPosition(void) + { + if (core_owner==this && !MIDI_core::UsesOutput()) return MIDI_core::GetPosition(); + return -1; + } + + virtual int corecb_onVolumeChange(int v) + { + if (core_owner==this) MIDI_core::SetVolume(v); + return 0; + } + virtual int corecb_onPanChange(int v) + { + if (core_owner==this) MIDI_core::SetPan(v); + return 0; + } + virtual int corecb_onAbortCurrentSong() {return 0;}; + virtual int corecb_onPaused() + { + if (core_owner==this) MIDI_core::Pause(1); + return 0; + } + virtual int corecb_onUnpaused() + { + if (core_owner==this) MIDI_core::Pause(0); + return 0; + } + + static void notify_eof() + { + core_owner_sync.enter(); + if (core_owner) core_owner->eof_flag=1; + core_owner_sync.leave(); + } + + static DWORD get_core_thread() + { + DWORD ret = 0; + core_owner_sync.enter(); + if (core_owner) ret = core_owner->thread_id; + core_owner_sync.leave(); + return ret; + } + +}; + +cnv_MIDI * cnv_MIDI::core_owner=0; +critical_section cnv_MIDI::core_owner_sync; + +static waServiceFactoryT<svc_mediaConverter, cnv_MIDI> midi_svc; + +#define ACTIONID_CONFIG "MIDI:CONFIG" + +class MIDI_actions : public svc_actionI { + public: + MIDI_actions() { + registerAction(ACTIONID_CONFIG, 0); + } + virtual ~MIDI_actions() { } + + virtual int onActionId(int id, const char *action, const char *param,int,int,void*,int,RootWnd*) { + switch (id) { + case 0: + if (!_stricmp(action,ACTIONID_CONFIG)) + { + if (MIDI_core::Config(MIDI_callback::GetMainWindow())) + { + update_extensions(); + } + } + return 1; + } + return 0; + } + static const char *getServiceName() { return "MIDI Player Actions Service"; } +}; + + +static waServiceFactoryTSingle<svc_actionI, MIDI_actions> actions; + +WACNAME::WACNAME() { +#ifdef FORTIFY + FortifySetName("cnv_midi.wac"); + FortifyEnterScope(); +#endif + registerService(&midi_svc); + registerService(&actions); +} + +WACNAME::~WACNAME() { +#ifdef FORTIFY + FortifyLeaveScope(); +#endif +} + + +void MIDI_callback::NotifyEOF() {cnv_MIDI::notify_eof();} +HWND MIDI_callback::GetMainWindow() {return api->main_getRootWnd()->gethWnd();} +HINSTANCE MIDI_callback::GetInstance() {return wac.gethInstance();} +void MIDI_callback::Error(const char * tx) {} + +void MIDI_callback::Idle(int ms) +{ + int core_thread = (GetCurrentThreadId() == cnv_MIDI::get_core_thread()); + int start = timeGetTime(); + do { + if (core_thread) check_messages(); + Sleep(1); + } while((int)timeGetTime() - start < ms); + if (core_thread) check_messages(); +} + +extern "C" { +BOOL APIENTRY DllMain(HANDLE hMod,DWORD r,void*) +{ + if (r==DLL_PROCESS_ATTACH) + { + DisableThreadLibraryCalls((HMODULE)hMod); + } + return 1; + +} +} diff --git a/Src/Plugins/Input/in_midi/xmi.cpp b/Src/Plugins/Input/in_midi/xmi.cpp new file mode 100644 index 00000000..6f18ec87 --- /dev/null +++ b/Src/Plugins/Input/in_midi/xmi.cpp @@ -0,0 +1,310 @@ +#include "main.h" +#include "cvt.h" + +#pragma pack(push) +#pragma pack(1) +typedef struct +{ + DWORD mthd,hdsize; + MIDIHEADER mhd; +} FILEHEADER; + +typedef struct +{ + DWORD mtrk,size; +} TRACKHEADER; + +#pragma pack(pop) + +struct XMI_cvt +{ +public: + grow_buf out; + bool _end; + DWORD tr_sz; + DWORD cur_time,wr_time; +// DWORD loopstart; + bool hasevents; + void q_add(BYTE ch,BYTE note,DWORD tm); + void WriteDelta(DWORD _d); + void DoQueue(); + DWORD ProcessNote(const BYTE* e); + DWORD ProcessDelta(const BYTE* d); + DWORD WriteEvent(const BYTE* e); + bool run(MIDI_file* mf,const BYTE*,DWORD); +#pragma pack(push) +#pragma pack(1) +#define Q_MAX 512 + +struct +{ + DWORD time; + BYTE note; + BYTE channel; +} ch_q[Q_MAX]; + +#pragma pack(pop) + +}; + + +#define WriteBuf(A,B) out.write(A,B) +#define WriteBufB(A) out.write_byte(A) +#define WriteBufD(A) out.write_dword(A) + +//WORD _fastcall rev16(WORD); +DWORD _fastcall rev32(DWORD); + + +#define FixHeader(H) {(H).fmt=rev16((H).fmt);(H).trax=rev16((H).trax);(H).dtx=rev16((H).dtx);} +#define MThd 'dhTM' +#define MTrk 'krTM' +#define EVNT 'TNVE' + +void XMI_cvt::q_add(BYTE ch,BYTE note,DWORD tm) +{ + UINT n,_n=-1; + for(n=0;n<Q_MAX;n++) + { + if (ch_q[n].note==note && ch_q[n].channel==ch && ch_q[n].time!=-1) + { + /*if (ch_q[n].time>tm) */ch_q[n].time=tm; +// q_notes++; + return; + } + else if (ch_q[n].time==-1) _n=n; + } + if (_n!=-1) + { + ch_q[_n].channel=ch; + ch_q[_n].time=tm; + ch_q[_n].note=note; +// q_notes++; + } +} + +void XMI_cvt::WriteDelta(DWORD _d) +{ + DWORD d=_d-wr_time; + wr_time=_d; + int st=out.get_size(); + gb_write_delta(out,d); + tr_sz+=out.get_size()-st; +} + +DWORD _inline ReadDelta1(const BYTE* d,DWORD* _l) +{ + DWORD r=d[0],l=0; + while(!(d[l+1]&0x80)) + { + r+=d[++l]; + } + *_l=l+1; + return r; +} + +void XMI_cvt::DoQueue() +{ + while(1) + { + DWORD _i=-1; + DWORD _mt=-1; + UINT i; + for(i=0;i<Q_MAX;i++) + { + if (ch_q[i].time<_mt) {_i=i;_mt=ch_q[i].time;} + } + if (_mt<=cur_time) + { + WriteDelta(_mt); + BYTE t[3]={(BYTE)(0x80|ch_q[_i].channel),ch_q[_i].note,0}; + WriteBuf(t,3); + ch_q[_i].time=-1; + tr_sz+=3; +// q_notes--; + } + else break; + } +} + +DWORD XMI_cvt::ProcessNote(const BYTE* e) +{ + DoQueue(); + WriteDelta(cur_time); + + WriteBuf(e,3); + tr_sz+=3; + DWORD l=3; + unsigned int _d; + l+=DecodeDelta(e+l,&_d); + + + if (e[2]) q_add(e[0]&0xF,e[1],cur_time+_d); + + return l; +} + +DWORD XMI_cvt::ProcessDelta(const BYTE* d) +{ + DWORD l; + cur_time+=ReadDelta1(d,&l); + return l; +} + +DWORD XMI_cvt::WriteEvent(const BYTE* e) +{ + if ((e[0]&0xF0)==0xF0 && e[0]!=0xFF && e[0]!=0xF0)//hack + { + UINT l=1; + while(!(e[l]&0x80)) l++; + return l; + } + else if (e[0]==0xFF) + { + if (e[1]==0x2F) + { + DWORD _ct=cur_time; + cur_time=-2; + DoQueue(); + +// loopstart=_ct-wr_time; + cur_time=_ct; + DWORD _ev=0x002FFF00; + WriteBuf(&_ev,4); + tr_sz+=4; + _end=1; + return 3; + } + else + { + UINT l=e[2]; + if (l&0x80) + { + l=((l<<7)|e[3])+1; + } + return 3+l; + } + } + DoQueue(); + WriteDelta(cur_time); + if (e[0]==0xF0) + { + unsigned int d; + UINT l = 1 + DecodeDelta(e+1,&d); + l+=d; + WriteBuf(e,l); + tr_sz+=l; + return l; + } + DWORD l; + //hasevents=1; + if ((e[0]&0xF0)==0xC0 || (e[0]&0xF0)==0xD0) l=2; + else l=3; + WriteBuf(e,l); + tr_sz+=l; + return l; +} + +BYTE xmi_track0[19]={'M','T','r','k',0,0,0,11,0,0xFF,0x51,0x03,0x20,0x8d,0xb7,0,0xFF,0x2F,0}; + +void ReleaseObject(IUnknown* o); + +bool XMI_cvt::run(MIDI_file * mf,const BYTE* buf,DWORD sz) +{ +// q_notes=0; + FILEHEADER fhd= + { + MThd, + 0x06000000, + 0x0100,0x0000,0x0001 + }; + WriteBuf(&fhd,sizeof(fhd)); + UINT ptr=0x22; + DWORD _sz; + DWORD sp; + UINT ntrax=1; + WriteBuf(xmi_track0,sizeof(xmi_track0)); + while(ptr<sz-4 && *(DWORD*)(buf+ptr)!=EVNT) ptr++; + if (*(DWORD*)(buf+ptr)!=EVNT) goto fail; +// loopstart=0; +_track: + cur_time=wr_time=0; + WriteBufD(_rv('MTrk')); + sp=out.get_size(); + WriteBufD(0); + ntrax++; + tr_sz=0; + ptr+=4; + _sz=ptr+4+rev32(*(DWORD*)(buf+ptr)); + if (_sz>sz+1) goto fail; + ptr+=4; + _end=0; + { + UINT n; + for(n=0;n<Q_MAX;n++) ch_q[n].time=-1; + } + hasevents=0; + while(ptr<sz-1 && !_end) + { + if ((buf[ptr]&0x80)==0) + { + ptr+=ProcessDelta(buf+ptr); + } + if ((buf[ptr]&0xF0)==0x90) + { + hasevents=1; + ptr+=ProcessNote(buf+ptr); + } + else ptr+=WriteEvent(buf+ptr); + } + if (!hasevents) {out.truncate(sp-4);ntrax--;} + else out.write_dword_ptr(rev32(tr_sz),sp); + if (ptr&1) ptr++; + if (ptr>=sz) goto _e; + if (*(DWORD*)(buf+ptr)==_rv('FORM') && *(DWORD*)(buf+ptr+8)==_rv('XMID')) + { + ptr+=12; +_te: if (ptr&1) ptr++; + if (*(DWORD*)(buf+ptr)==_rv('EVNT')) goto _track; + else if (*(DWORD*)(buf+ptr)==_rv('TIMB')) + { + ptr+=8+rev32(*(DWORD*)(buf+ptr+4)); + if (ptr<sz) goto _te; + } + } +_e: + { + WORD w=rev16(ntrax); + out.write_ptr(&w,2,10); + if (ntrax>1) + w=0x200; + out.write_ptr(&w,2,8); + } + + mf->size = out.get_size(); + mf->data = (BYTE*)out.finish(); + if (!mf->data) return 0; + +// if (loopstart>0x10) mf->loopstart=loopstart; +#ifdef MF_USE_DMCRAP + if (sz>ptr+0x20 && *(DWORD*)(buf+ptr)==_rv('FORM') && *(DWORD*)(buf+ptr+8)==_rv('XDLS')) + { + DWORD rs=rev32(*(DWORD*)(buf+_sz+4)); + if (rs+12+_sz>sz) goto _ret; + if (*(DWORD*)(buf+ptr+0x14)!=_rv('DLS ')) goto _ret; + mf->DLSsize=rs; + mf->pDLSdata=(BYTE*)malloc(rs); + memcpy(mf->pDLSdata,buf+_sz+12,rs); + } +#endif +_ret: + return 1; +fail: + return 0; +} + +bool load_xmi(MIDI_file * mf,const BYTE* buf,size_t sz) +{ + XMI_cvt c; + return c.run(mf,buf,sz); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mkv/ExtendedFileInfo.cpp b/Src/Plugins/Input/in_mkv/ExtendedFileInfo.cpp new file mode 100644 index 00000000..b4823355 --- /dev/null +++ b/Src/Plugins/Input/in_mkv/ExtendedFileInfo.cpp @@ -0,0 +1,80 @@ +#include <bfc/platform/types.h> +#include <windows.h> +#include "api__in_mkv.h" +#include "MKVInfo.h" +#include <strsafe.h> +#include "resource.h" + +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>3 && L'.' == fn[len-4])) + { + p = &fn[len - 3]; + if (!_wcsicmp(p, L"MKV") && S_OK == StringCchCopyW(dest, destlen, WASABI_API_LNGSTRINGW(IDS_FAMILY_STRING))) return 1; + } + else if (len>4 && L'.' == fn[len-5]) + { + p = &fn[len - 4]; + if (!_wcsicmp(p, L"webm") && S_OK == StringCchCopyW(dest, destlen, WASABI_API_LNGSTRINGW(IDS_FAMILY_STRING_WEBM))) return 1; + } + return 0; + } + else + { + MKVInfo info; + dest[0]=0; + if (!_stricmp(data, "length")) + { + if (info.Open(fn)) + StringCchPrintf(dest, destlen, L"%d", info.GetLengthMilliseconds()); + } + else if (!_stricmp(data, "bitrate")) + { + if (info.Open(fn)) + StringCchPrintf(dest, destlen, L"%d", info.GetBitrate()); + } + else if (!_stricmp(data, "title")) + { + return 1; + } + else if (!_stricmp(data, "width")) + { + + if (info.Open(fn)) + { + int width; + if (info.GetWidth(width)) + StringCchPrintf(dest, destlen, L"%d", width); + + } + } + else if (!_stricmp(data, "height")) + { + if (info.Open(fn)) + { + int height; + if (info.GetHeight(height)) + StringCchPrintf(dest, destlen, L"%d", height); + + } + } + else + return 0; + return 1; + } + + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mkv/InfoDialog.cpp b/Src/Plugins/Input/in_mkv/InfoDialog.cpp new file mode 100644 index 00000000..0bca76e7 --- /dev/null +++ b/Src/Plugins/Input/in_mkv/InfoDialog.cpp @@ -0,0 +1,247 @@ +#include "MKVInfo.h" +#include "api__in_mkv.h" +#include "../nu/ListView.h" +#include "../nu/AutoWide.h" +#include "resource.h" +#include "main.h" +#include <strsafe.h> + + +enum +{ + COLUMN_TRACK_TYPE = 0, + COLUMN_CODEC_NAME = 1, + COLUMN_CODEC_ID = 2, + COLUMN_DESCRIPTION = 3, + COLUMN_STREAM_NAME = 4, + COLUMN_LANGUAGE = 5, +}; + + +#if 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(L"Field", 100); + list_view.AddCol(L"FOURCC", 75); + list_view.AddCol(L"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_UTF8)); + } + } + } + return 1; + } + return 0; +} +#endif + +static INT_PTR CALLBACK InfoDialog_Tracks(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + MKVInfo *metadata = (MKVInfo *)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); + list_view.AddCol(WASABI_API_LNGSTRINGW(IDS_COLUMN_LANGUAGE), 50); + + const nsmkv::Tracks *tracks = metadata->GetTracks(); + if (tracks) + { + const nsmkv::TrackEntry *track_entry; + size_t i=0; + while (track_entry = tracks->EnumTrack(i++)) + { + int n; + switch(track_entry->track_type) + { + case mkv_track_type_audio: + { + n = list_view.AppendItem(WASABI_API_LNGSTRINGW(IDS_TYPE_AUDIO), 0); + } + break; + case mkv_track_type_video: + { + n = list_view.AppendItem(WASABI_API_LNGSTRINGW(IDS_TYPE_VIDEO), 0); + + } + break; + case mkv_track_type_subtitle: + { + n = list_view.AppendItem(WASABI_API_LNGSTRINGW(IDS_TYPE_SUBTITLE), 0); + } + break; + default: + { + wchar_t track_type[64] = {0}; + StringCchPrintf(track_type, 64, L"%X", track_entry->track_type); + n = list_view.AppendItem(track_type, 0); + } + break; + } + if (track_entry->codec_id) + list_view.SetItemText(n, COLUMN_CODEC_ID, AutoWide(track_entry->codec_id, CP_UTF8)); + + if (track_entry->codec_name) + { + list_view.SetItemText(n, COLUMN_CODEC_NAME, AutoWide(track_entry->codec_name, CP_UTF8)); + } + else + { + // TODO: enumerate through a list of known codecs + if (track_entry->codec_id) + list_view.SetItemText(n, COLUMN_CODEC_NAME, AutoWide(track_entry->codec_id, CP_UTF8)); + } + + if (track_entry->name) + { + list_view.SetItemText(n, COLUMN_STREAM_NAME, AutoWide(track_entry->name, CP_UTF8)); + } + + if (track_entry->language && stricmp(track_entry->language, "und")) + { + list_view.SetItemText(n, COLUMN_LANGUAGE, AutoWide(track_entry->language, 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 +{ + MKVInfo *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_Tracks, (LPARAM)context->metadata); + + break; + case 1: +// context->active_tab = WASABI_API_CREATEDIALOGPARAMW(IDD_TRACKS, hwndDlg, InfoDialog_Metadata, (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 = (MKVInfo *)lParam; + context->active_tab = 0; + SetWindowLongPtr(hwndDlg,GWLP_USERDATA, (LPARAM)context); + TCITEMW tie; + tie.mask = TCIF_TEXT; + tie.pszText = WASABI_API_LNGSTRINGW(IDS_TAB_TRACKS); + SendMessageW(hwndTab, TCM_INSERTITEMW, 0, (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_mkv/MKVDuration.cpp b/Src/Plugins/Input/in_mkv/MKVDuration.cpp new file mode 100644 index 00000000..0f84cb62 --- /dev/null +++ b/Src/Plugins/Input/in_mkv/MKVDuration.cpp @@ -0,0 +1,115 @@ +#include "MKVDuration.h" +#include "../nsmkv/global_elements.h" +#include "../nsmkv/read.h" +#include "../nsmkv/segment.h" +#include "../nsmkv/file_mkv_reader.h" + +MKVDuration::MKVDuration() +{ + segment_info_found = false; + content_length = 0; +} + +bool MKVDuration::Open(const wchar_t *filename) +{ + FILE *f = _wfopen(filename, L"rb"); + if (!f) + return false; + + MKVReaderFILE reader(f); + + content_length = reader.GetContentLength(); + + ebml_node node; + while (!segment_info_found) + { + if (read_ebml_node(&reader, &node) == 0) + break; + + switch(node.id) + { + case mkv_header: + if (nsmkv::ReadHeader(&reader, node.size, header) == 0) + { + return false; + } + break; + case mkv_segment: + if (ReadSegment(&reader, node.size) == 0) + { + return false; + } + break; + default: + nsmkv::SkipNode(&reader, node.id, node.size); + + } + } + + return segment_info_found; +} + +uint64_t MKVDuration::ReadSegment(nsmkv::MKVReader *reader, uint64_t size) +{ + uint64_t total_bytes_read=0; + while (size && !segment_info_found) + { + ebml_node node; + uint64_t bytes_read = read_ebml_node(reader, &node); + + if (bytes_read == 0) + return 0; + + // benski> checking bytes_read and node.size separately prevents possible integer overflow attack + if (bytes_read > size) + return 0; + total_bytes_read+=bytes_read; + size-=bytes_read; + + if (node.size > size) + return 0; + total_bytes_read+=node.size; + size-=node.size; + + switch(node.id) + { + case mkv_segment_segmentinfo: + { + printf("SegmentInfo\n"); + if (ReadSegmentInfo(reader, node.size, segment_info) == 0) + return 0; + segment_info_found=true; + } + break; + + default: + if (nsmkv::SkipNode(reader, node.id, node.size) == 0) + return 0; + } + } + return total_bytes_read; +} + +int MKVDuration::GetLengthMilliseconds() +{ + if (!segment_info_found) + return -1000; + else + return segment_info.GetDurationMilliseconds(); +} + +const char *MKVDuration::GetTitle() +{ + return segment_info.title; +} + +int MKVDuration::GetBitrate() +{ + if (segment_info_found) + { + int time_ms = segment_info.GetDurationMilliseconds(); + if (time_ms) + return (int) (8ULL * content_length / (uint64_t)time_ms); + } + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mkv/MKVDuration.h b/Src/Plugins/Input/in_mkv/MKVDuration.h new file mode 100644 index 00000000..eb2b21a4 --- /dev/null +++ b/Src/Plugins/Input/in_mkv/MKVDuration.h @@ -0,0 +1,20 @@ +#pragma once +#include "../nsmkv/header.h" +#include "../nsmkv/segmentinfo.h" +// parses only enough information to determine the file duration + +class MKVDuration +{ +public: + MKVDuration(); + bool Open(const wchar_t *filename); + int GetLengthMilliseconds(); + const char *GetTitle(); + int GetBitrate(); +private: + uint64_t ReadSegment(nsmkv::MKVReader *reader, uint64_t size); + bool segment_info_found; + nsmkv::Header header; + nsmkv::SegmentInfo segment_info; + uint64_t content_length; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mkv/MKVInfo.cpp b/Src/Plugins/Input/in_mkv/MKVInfo.cpp new file mode 100644 index 00000000..dba25981 --- /dev/null +++ b/Src/Plugins/Input/in_mkv/MKVInfo.cpp @@ -0,0 +1,168 @@ +#include "MKVInfo.h" +#include "../nsmkv/global_elements.h" +#include "../nsmkv/read.h" +#include "../nsmkv/segment.h" +#include "../nsmkv/tracks.h" +#include "../nsmkv/file_mkv_reader.h" + +MKVInfo::MKVInfo() +{ + segment_info_found = false; + content_length = 0; + tracks_found = false; +} + +bool MKVInfo::Open(const wchar_t *filename) +{ + FILE *f = _wfopen(filename, L"rb"); + if (!f) + return false; + + MKVReaderFILE reader(f); + + content_length = reader.GetContentLength(); + + ebml_node node; + while (!segment_info_found) + { + if (read_ebml_node(&reader, &node) == 0) + break; + + switch(node.id) + { + case mkv_header: + if (nsmkv::ReadHeader(&reader, node.size, header) == 0) + { + return false; + } + break; + case mkv_segment: + if (ReadSegment(&reader, node.size) == 0) + { + return false; + } + break; + default: + nsmkv::SkipNode(&reader, node.id, node.size); + + } + } + + return segment_info_found; +} + +uint64_t MKVInfo::ReadSegment(nsmkv::MKVReader *reader, uint64_t size) +{ + uint64_t total_bytes_read=0; + while (size && (!segment_info_found || !tracks_found)) + { + ebml_node node; + uint64_t bytes_read = read_ebml_node(reader, &node); + + if (bytes_read == 0) + return 0; + + // benski> checking bytes_read and node.size separately prevents possible integer overflow attack + if (bytes_read > size) + return 0; + total_bytes_read+=bytes_read; + size-=bytes_read; + + if (node.size > size) + return 0; + total_bytes_read+=node.size; + size-=node.size; + + switch(node.id) + { + case mkv_segment_segmentinfo: + { + if (ReadSegmentInfo(reader, node.size, segment_info) == 0) + return 0; + segment_info_found=true; + } + break; + case mkv_segment_tracks: + { + if (ReadTracks(reader, node.size, tracks) == 0) + return 0; + tracks_found=true; + } + break; + + default: + if (nsmkv::SkipNode(reader, node.id, node.size) == 0) + return 0; + } + } + return total_bytes_read; +} + +int MKVInfo::GetLengthMilliseconds() +{ + if (!segment_info_found) + return -1000; + else + return segment_info.GetDurationMilliseconds(); +} + +const char *MKVInfo::GetTitle() +{ + return segment_info.title; +} + +int MKVInfo::GetBitrate() +{ + if (segment_info_found) + { + int time_ms = segment_info.GetDurationMilliseconds(); + if (time_ms) + return (int) (8ULL * content_length / (uint64_t)time_ms); + } + return 0; +} + +bool MKVInfo::GetHeight(int &height) +{ + if (tracks_found) + { + size_t i=0; + const nsmkv::TrackEntry *track=0; + while (track = tracks.EnumTrack(i++)) + { + if (track->track_type == mkv_track_type_video) + { + height = (int)track->video.pixel_height; + return true; + } + } + } + return false; +} + + +bool MKVInfo::GetWidth(int &width) +{ + if (tracks_found) + { + size_t i=0; + const nsmkv::TrackEntry *track=0; + while (track = tracks.EnumTrack(i++)) + { + if (track->track_type == mkv_track_type_video) + { + width = (int)track->video.pixel_width; + return true; + } + } + } + return false; +} + +const nsmkv::Tracks *MKVInfo::GetTracks() +{ + if (tracks_found) + return &tracks; + else + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mkv/MKVInfo.h b/Src/Plugins/Input/in_mkv/MKVInfo.h new file mode 100644 index 00000000..bed2522f --- /dev/null +++ b/Src/Plugins/Input/in_mkv/MKVInfo.h @@ -0,0 +1,26 @@ +#pragma once +#include "../nsmkv/header.h" +#include "../nsmkv/segmentinfo.h" +#include "../nsmkv/tracks.h" +// parses only enough information to use in GetExtendedFileInfo + +class MKVInfo +{ +public: + MKVInfo(); + bool Open(const wchar_t *filename); + int GetLengthMilliseconds(); + const char *GetTitle(); + int GetBitrate(); + bool GetHeight(int &height); + bool GetWidth(int &width); + const nsmkv::Tracks *GetTracks(); +private: + uint64_t ReadSegment(nsmkv::MKVReader *reader, uint64_t size); + bool segment_info_found; + bool tracks_found; + nsmkv::Header header; + nsmkv::SegmentInfo segment_info; + nsmkv::Tracks tracks; + uint64_t content_length; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mkv/MKVPlayer.h b/Src/Plugins/Input/in_mkv/MKVPlayer.h new file mode 100644 index 00000000..91de5122 --- /dev/null +++ b/Src/Plugins/Input/in_mkv/MKVPlayer.h @@ -0,0 +1,138 @@ +#pragma once +#include <windows.h> +#include <bfc/platform/types.h> + +// nsmkv stuff +#include "../nsmkv/Cluster.h" +#include "../nsmkv/header.h" +#include "../nsmkv/SeekTable.h" +#include "../nsmkv/SegmentInfo.h" +#include "../nsmkv/Tracks.h" +#include "../nsmkv/Cues.h" +#include "../nsmkv/Attachments.h" +#include "../nsmkv/mkv_reader.h" + +#include "ifc_mkvvideodecoder.h" +#include "ifc_mkvaudiodecoder.h" + +#include "../nu/AutoLock.h" +#include "../nu/AudioOutput.h" + + +class MKVPlayer +{ +public: + MKVPlayer(const wchar_t *_filename); + ~MKVPlayer(); + DWORD CALLBACK ThreadFunction(); + DWORD CALLBACK VideoThreadFunction(); + + void Kill(); + void Seek(int seek_pos); + int GetOutputTime() const; + +private: + // subfunctions to make the code cleaner + // they all have "side effects", which is a coding style I don't like + // but it makes it easier to read & modify + // TODO: move these to step, and have a state variable to know where we are + bool ParseHeader(); // on completion, header will be filled, file pointer will be at next level 0 node + bool FindSegment(); // on completion, segment_position will be calculated, file pointer will be at first level 1 node under segment + + // file position is restored at end of function + bool FindCues(); + + int ParseCluster(nsmkv::MKVReader *stream, uint64_t size, uint64_t *track_numbers, size_t track_numbers_len); + + enum + { + // values that you can return from OnXXXX() + MKV_CONTINUE = 0, // continue processing + MKV_ABORT = 1, // abort parsing gracefully (maybe 'stop' was pressed) + MKV_STOP = 2, // stop parsing completely - usually returned when mkv version is too new or codecs not supported + + // values returned from errors within the Step() function itself + MKV_EOF = 3, // end of file + MKV_ERROR = 4, // parsing error + }; + int OnHeader(const nsmkv::Header &header); + void OnSegmentInfo(const nsmkv::SegmentInfo &segment_info); + int OnTracks(const nsmkv::Tracks &tracks); + int OnBlock(const nsmkv::Cluster &cluster, const nsmkv::Block &block); + int OnFirstCluster(uint64_t position); + int OnAudio(const nsmkv::Cluster &cluster, const nsmkv::BlockBinary &binary); + int OnVideo(const nsmkv::Cluster &cluster, const nsmkv::BlockBinary &binary); + + int OutputPictures(uint64_t default_timestamp); + /* start calling with cluster_number = 0 and block_number = 0 (or whatever appropriate based on CuePoints when seeking + will return 0 on success, 1 on EOF and -1 on failure + */ + int GetBlock(nsmkv::MKVReader *stream, uint64_t track_number, nsmkv::BlockBinary &binary, const nsmkv::Cluster **cluster, size_t &cluster_number, size_t &block_number); + static void CALLBACK SeekAPC(ULONG_PTR data); + + int Step(nsmkv::MKVReader *stream, uint64_t *track_numbers, size_t track_numbers_len); // only gives you block data for the passed track number + +private: + /* nsmkv internal implementation */ + nsmkv::Header header; + uint64_t segment_position; // position of the start of the first level 1 element in the segment(for SeekHead relative positions) + uint64_t segment_size; // size of that segment + nsmkv::SeekTable seek_table; + nsmkv::SegmentInfo segment_info; + nsmkv::Tracks tracks; + nsmkv::Clusters clusters; + nsmkv::Cues cues; + nsmkv::Attachments attachments; + bool cues_searched; + bool first_cluster_found; + + /* player implementation */ + nsmkv::MKVReader *main_reader; // also gets used as audio_stream + Nullsoft::Utility::LockGuard cluster_guard; + HANDLE killswitch, seek_event; + wchar_t *filename; + volatile int m_needseek; + + /* Audio */ + ifc_mkvaudiodecoder *audio_decoder; + bool audio_opened; + uint64_t audio_track_num; + uint8_t audio_buffer[65536]; // TODO: dynamically allocate from OutputFrameSize + size_t audio_output_len; + size_t audio_buffered; + int audio_first_timestamp; + enum FlushState + { + FLUSH_NONE=0, + FLUSH_START=1, + FLUSH_SEEK=2, + }; + FlushState audio_flushing; + unsigned int audio_bitrate; + + /* Video */ + ifc_mkvvideodecoder *video_decoder; + const nsmkv::TrackEntry *video_track_entry; + bool video_opened; + HANDLE video_thread; + double video_timecode_scale; + uint64_t video_track_num; + uint64_t video_cluster_position; + nsmkv::MKVReader *video_stream; + HANDLE video_break, video_flush, video_flush_done, video_resume, video_ready; + unsigned int video_bitrate; + int consecutive_early_frames; + + /* AudioOutput implementation */ + class MKVWait + { + public: + void Wait_SetEvents(HANDLE killswitch, HANDLE seek_event); + protected: + int WaitOrAbort(int time_in_ms); + private: + HANDLE handles[2]; + }; + nu::AudioOutput<MKVWait> audio_output; + +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mkv/PlayThread.cpp b/Src/Plugins/Input/in_mkv/PlayThread.cpp new file mode 100644 index 00000000..81941c7c --- /dev/null +++ b/Src/Plugins/Input/in_mkv/PlayThread.cpp @@ -0,0 +1,902 @@ +#include "MKVPlayer.h" +#include "api__in_mkv.h" +#include "main.h" + + +#include "../Winamp/wa_ipc.h" +#include "../nsmkv/nsmkv.h" +#include "../nu/AutoLock.h" +#include "../nu/ns_wc.h" +#include "../nu/AutoWide.h" +#include "../nu/AudioOutput.h" +#include "ifc_mkvaudiodecoder.h" +#include "svc_mkvdecoder.h" +#include "ifc_mkvvideodecoder.h" +#include "../nsmkv/file_mkv_reader.h" +#include <api/service/waservicefactory.h> +#include <api/service/services.h> +#include <windows.h> +#include <strsafe.h> + +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = +{ + 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } +}; + + +IVideoOutput *videoOutput = 0; +/* benski> +TODO: keep track of "fully parsed position" we don't have to always start over at segment_position +TODO: if we have multiple audio or video tracks, do that weird winamp interface for it +*/ + + +void MKVPlayer::MKVWait::Wait_SetEvents(HANDLE killswitch, HANDLE seek_event) +{ + handles[0]=killswitch; + handles[1]=seek_event; +} + +int MKVPlayer::MKVWait::WaitOrAbort(int time_in_ms) +{ + switch(WaitForMultipleObjects(2, handles, FALSE, 55)) + { + case WAIT_TIMEOUT: // all good, wait successful + return 0; + case WAIT_OBJECT_0: // killswitch + return MKVPlayer::MKV_STOP; + case WAIT_OBJECT_0+1: // seek event + return MKVPlayer::MKV_ABORT; + default: // some OS error? + return MKVPlayer::MKV_ERROR; + } +} + +MKVPlayer::MKVPlayer(const wchar_t *_filename) : audio_output(&plugin) +{ + first_cluster_found = false; + cues_searched = false; + segment_position=0; + segment_size = 0; + + filename = _wcsdup(_filename); + m_needseek = -1; + + audio_decoder=0; + audio_track_num=0; + audio_output_len = 65536; + audio_opened=false; + audio_flushing=FLUSH_START; + audio_first_timestamp=0; + audio_buffered=0; + audio_bitrate=0; + + video_decoder=0; + video_track_num=0; + video_opened = false; + video_stream = 0; + video_thread = 0; + video_timecode_scale = 0; + video_cluster_position = 0; + video_track_entry = 0; + video_bitrate = 0; + consecutive_early_frames = 0; + + if (!videoOutput) + videoOutput = (IVideoOutput *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GET_IVIDEOOUTPUT); + + killswitch = CreateEvent(NULL, TRUE, FALSE, NULL); + seek_event = CreateEvent(NULL, TRUE, FALSE, NULL); + + /* video events */ + video_break = CreateEvent(NULL, TRUE, FALSE, NULL); + video_flush_done = CreateEvent(NULL, FALSE, FALSE, NULL); + video_flush = CreateEvent(NULL, TRUE, FALSE, NULL); + video_resume = CreateEvent(NULL, TRUE, FALSE, NULL); + video_ready = CreateEvent(NULL, TRUE, FALSE, NULL); + + audio_output.Wait_SetEvents(killswitch, seek_event); +} + +MKVPlayer::~MKVPlayer() { + free(filename); + CloseHandle(killswitch); + CloseHandle(seek_event); + + CloseHandle(video_break); + CloseHandle(video_flush_done); + CloseHandle(video_flush); + CloseHandle(video_resume); + CloseHandle(video_ready); + + if (audio_decoder) { + audio_decoder->Close(); + } + delete main_reader; +} + +void MKVPlayer::Kill() +{ + SetEvent(killswitch); + if (video_thread) + WaitForSingleObject(video_thread, INFINITE); + video_thread = 0; + if (video_decoder) + video_decoder->Close(); + video_decoder=0; +} + +void MKVPlayer::Seek(int seek_pos) +{ + m_needseek = seek_pos; + SetEvent(seek_event); +} + +int MKVPlayer::GetOutputTime() const +{ + if (m_needseek != -1) + return m_needseek; + else + return plugin.outMod->GetOutputTime() + audio_first_timestamp; +} + +DWORD CALLBACK MKVThread(LPVOID param) +{ + MKVPlayer *player = (MKVPlayer *)param; + DWORD ret = player->ThreadFunction(); + return ret; +} + +bool MKVPlayer::FindCues() +{ + if (cues_searched) + return true; + + uint64_t original_position = main_reader->Tell(); + + // first, let's try the seek table + uint64_t cues_position = 0; + if (seek_table.GetEntry(mkv_segment_cues, &cues_position)) + { + main_reader->Seek(cues_position+segment_position); + ebml_node node; + if (read_ebml_node(main_reader, &node) == 0) + return false; + + if (node.id == mkv_segment_cues) // great success! + { + if (nsmkv::ReadCues(main_reader, node.size, cues) == 0) + return false; + cues_searched=true; + main_reader->Seek(original_position); + return true; + } + } + + main_reader->Seek(segment_position); // TODO: keep track of how far Step() has gotten so we don't have to start from scratch + /* --- TODO: make this block into a function in nsmkv --- */ + while (1) // TODO: key off segment size to make sure we don't overread + { + uint64_t this_position = main_reader->Tell(); + ebml_node node; + if (read_ebml_node(main_reader, &node) == 0) + return false; + + if (node.id != mkv_void) + { + nsmkv::SeekEntry seek_entry(node.id, this_position-segment_position); + seek_table.AddEntry(seek_entry, nsmkv::SeekTable::ADDENTRY_FOUND); + } + + if (node.id == mkv_segment_cues) + { + if (nsmkv::ReadCues(main_reader, node.size, cues) == 0) + return false; + break; + } + else + { + main_reader->Skip(node.size); + } + } + /* ------ */ + cues_searched=true; + main_reader->Seek(original_position); + return true; +} + +bool MKVPlayer::ParseHeader() +{ + ebml_node node; + if (read_ebml_node(main_reader, &node) == 0) + return false; + + if (node.id != mkv_header) + return false; + + if (nsmkv::ReadHeader(main_reader, node.size, header) == 0) + return false; + + if (OnHeader(header) != MKV_CONTINUE) + return false; + + return true; +} + +bool MKVPlayer::FindSegment() +{ + ebml_node node; + while (segment_position == 0) + { + if (read_ebml_node(main_reader, &node) == 0) + return false; + + if (node.id == mkv_segment) + { + segment_position = main_reader->Tell(); + segment_size = node.size; + } + else + { + if (nsmkv::SkipNode(main_reader, node.id, node.size) == 0) + return false; + } + } + return true; +} + +static ifc_mkvvideodecoder *FindVideoDecoder(const nsmkv::TrackEntry *track_entry) +{ + size_t n = 0; + waServiceFactory *sf = 0; + while (sf = plugin.service->service_enumService(WaSvc::MKVDECODER, n++)) + { + svc_mkvdecoder *dec = static_cast<svc_mkvdecoder *>(sf->getInterface()); + if (dec) + { + ifc_mkvvideodecoder *decoder=0; + if (dec->CreateVideoDecoder(track_entry->codec_id, track_entry, &track_entry->video, &decoder) == svc_mkvdecoder::CREATEDECODER_SUCCESS) + { + sf->releaseInterface(dec); + return decoder; + } + sf->releaseInterface(dec); + } + } + return 0; +} + +static ifc_mkvaudiodecoder *FindAudioDecoder(const nsmkv::TrackEntry *track_entry) +{ + 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::MKVDECODER, n++)) + { + svc_mkvdecoder *dec = static_cast<svc_mkvdecoder *>(sf->getInterface()); + if (dec) + { + ifc_mkvaudiodecoder *decoder=0; + // TODO: read from api_config!! + if (dec->CreateAudioDecoder(track_entry->codec_id, track_entry, &track_entry->audio, bits_per_sample, max_channels, false, &decoder) == svc_mkvdecoder::CREATEDECODER_SUCCESS) + { + sf->releaseInterface(dec); + return decoder; + } + sf->releaseInterface(dec); + } + } + return 0; +} + +int MKVPlayer::OnAudio(const nsmkv::Cluster &cluster, const nsmkv::BlockBinary &binary) +{ + if (video_decoder) + { + HANDLE handles[3] = {killswitch, seek_event, video_ready}; + if (WaitForMultipleObjects(3, handles, FALSE, INFINITE) != WAIT_OBJECT_0+2) + return MKV_ABORT; + } + + if (audio_flushing != FLUSH_NONE) + { + uint64_t timestamp=cluster.time_code + binary.time_code; + uint64_t timestamp_ms = segment_info.time_code_scale * timestamp / 1000000ULL; + if (audio_flushing == FLUSH_START || !audio_opened) + { + audio_first_timestamp = (int)timestamp_ms; + } + else + { + audio_first_timestamp=0; + audio_output.Flush((int)timestamp_ms); + m_needseek = -1; + } + audio_flushing=FLUSH_NONE; + } + + nsmkv::LacingState lacing_state; + uint32_t i = 0; + if (nsmkv::Lacing::GetState(binary.flags, (const uint8_t *)binary.data, binary.data_size, &lacing_state)) + { + const uint8_t *frame = 0; + size_t frame_len = 0; + while (nsmkv::Lacing::GetFrame(i++, (const uint8_t *)binary.data, binary.data_size, &frame, &frame_len, &lacing_state)) + { + size_t decoded_size = audio_output_len-audio_buffered; + if (audio_decoder->DecodeBlock((void *)frame, frame_len, audio_buffer+audio_buffered, &decoded_size) == ifc_mkvaudiodecoder::MKV_SUCCESS) + { + decoded_size+=audio_buffered; + audio_buffered=0; + if (!audio_opened) + { + unsigned int sample_rate, channels, bps; + bool is_float; + if (audio_decoder->GetOutputProperties(&sample_rate, &channels, &bps, &is_float) == ifc_mkvaudiodecoder::MKV_SUCCESS) + { + // TODO: pass -666 for 5th param if video + if (!audio_output.Open(audio_first_timestamp, channels, sample_rate, bps, -1, -1)) + { + return MKV_STOP; + } + audio_opened=true; + } + } + + if (audio_opened && decoded_size) + { + int ret = audio_output.Write((char *)audio_buffer, decoded_size); + if (ret != 0) + return ret; + } + else + { + audio_buffered=decoded_size; + } + } + } + } + return MKV_CONTINUE; +} + +int MKVPlayer::OutputPictures(uint64_t default_timestamp) +{ + void *data=0, *decoder_data=0; + uint64_t timestamp=default_timestamp; + while (video_decoder->GetPicture(&data, &decoder_data, ×tamp) == ifc_mkvvideodecoder::MKV_SUCCESS) + { + if (!video_opened) + { + int color_format = 0; + int width = 0, height = 0; + double aspect_ratio = 1.0; + if (video_decoder->GetOutputProperties(&width, &height, &color_format, &aspect_ratio) == ifc_mkvvideodecoder::MKV_SUCCESS) + { + if (video_track_entry && video_track_entry->video.display_height && video_track_entry->video.display_width && video_track_entry->video.pixel_height && video_track_entry->video.pixel_width) + { + aspect_ratio = (double)video_track_entry->video.pixel_width / (double)video_track_entry->video.pixel_height / ((double)video_track_entry->video.display_width / (double)video_track_entry->video.display_height); + } + else + { + // winamp wants an "aspect correction value" not the true aspect ratio itself + aspect_ratio = 1.0/aspect_ratio; + } + videoOutput->extended(VIDUSER_SET_THREAD_SAFE, 1, 0); + videoOutput->open(width, height, 0, aspect_ratio, color_format); + video_opened=true; + SetEvent(video_ready); + } + } + + if (video_opened) + { + uint64_t timestamp_ms; + if (video_timecode_scale == 0) + timestamp_ms = segment_info.time_code_scale * timestamp / 1000000ULL; + else + timestamp_ms = (uint64_t) (video_timecode_scale * (double)segment_info.time_code_scale * (double)timestamp / 1000000.0); +again: + int realTime = plugin.outMod->GetOutputTime() + audio_first_timestamp; + int time_diff = (int)timestamp_ms - realTime; + if (time_diff > 12 && consecutive_early_frames) // plenty of time, go ahead and turn off frame dropping + { + if (--consecutive_early_frames == 0) + video_decoder->HurryUp(0); + } + else if (time_diff < -50) // shit we're way late, start dropping frames + { + video_decoder->HurryUp(1); + consecutive_early_frames += 3; + } + if (time_diff > 3) + { + HANDLE handles[] = {killswitch, video_break}; + int ret= WaitForMultipleObjects(2, handles, FALSE, (DWORD)(timestamp_ms-realTime)); + if (ret != WAIT_TIMEOUT) + { + video_decoder->FreePicture(data, decoder_data); + if (ret == WAIT_OBJECT_0+1) /* second event doesn't stop stream*/ + return MKV_ABORT; + return MKV_STOP; + } + goto again; // TODO: handle paused state a little better than this + } + + videoOutput->draw(data); + } + + video_decoder->FreePicture(data, decoder_data); + } + return MKV_CONTINUE; +} + +int MKVPlayer::OnVideo(const nsmkv::Cluster &cluster, const nsmkv::BlockBinary &binary) +{ + if (video_decoder) + { + nsmkv::LacingState lacing_state; + uint32_t i = 0; + if (nsmkv::Lacing::GetState(binary.flags, (const uint8_t *)binary.data, binary.data_size, &lacing_state)) + { + const uint8_t *frame = 0; + size_t frame_len = 0; + while (nsmkv::Lacing::GetFrame(i++, (const uint8_t *)binary.data, binary.data_size, &frame, &frame_len, &lacing_state)) + { + // matroska epic fail: laced frames don't have separate timestamps! + if (video_decoder) video_decoder->DecodeBlock(frame, frame_len, cluster.time_code+binary.time_code); + + uint64_t timestamp=cluster.time_code + binary.time_code; + int ret = OutputPictures(timestamp); + if (ret != MKV_CONTINUE) + return ret; + } + } + } + return MKV_CONTINUE; +} + +DWORD CALLBACK MKVPlayer::VideoThreadFunction() +{ + //video_stream = _wfopen(filename, L"rb"); + if (!video_stream) + return 1; + + video_stream->Seek(video_cluster_position); + HANDLE handles[] = { killswitch, video_break, video_flush, video_resume }; + DWORD waitTime = 0; + while (1) + { + int ret = WaitForMultipleObjects(4, handles, FALSE, waitTime); + if (ret == WAIT_TIMEOUT) + { + int ret = Step(video_stream, &video_track_num, 1); + if (ret == MKV_EOF) + { + video_decoder->EndOfStream(); + OutputPictures(0); + // TODO: tell decoder about end-of-stream to flush buffers + waitTime = INFINITE; + } + else if (ret == MKV_ERROR || ret == MKV_STOP) + { + waitTime = INFINITE; // wait for killswitch + } + } + else if (ret == WAIT_OBJECT_0) + { + break; + } + else if (ret == WAIT_OBJECT_0 + 1) // video break + { + waitTime = INFINITE; // this will stop us from decoding samples for a while + 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); + waitTime = 0; + SetEvent(video_flush_done); + } + else if (ret == WAIT_OBJECT_0 + 3) // resume video + { + ResetEvent(video_resume); + waitTime = 0; + SetEvent(video_flush_done); + } + } + if (videoOutput) + videoOutput->close(); + + delete video_stream; + video_stream=0; + return 0; +} + +int MKVPlayer::OnHeader(const nsmkv::Header &header) +{ + // TODO: figure out if the file is really matroska, and if we can support the ebml version + return MKV_CONTINUE; +} + +void MKVPlayer::OnSegmentInfo(const nsmkv::SegmentInfo &segment_info) +{ + g_duration = segment_info.GetDurationMilliseconds(); + uint64_t content_length = main_reader->GetContentLength(); + if (content_length && g_duration) + { + int bitrate = (int)(8ULL * content_length / (uint64_t)g_duration); + plugin.SetInfo(bitrate, -1, -1, -1); + } +} + +int MKVPlayer::OnTracks(const nsmkv::Tracks &tracks) +{ + wchar_t audio_info[256] = {0}; + wchar_t video_info[256] = {0}; + // ===== enumerate tracks and find decoders ===== + size_t i=0; + const nsmkv::TrackEntry *track_entry; + while (track_entry = tracks.EnumTrack(i++)) + { + if (track_entry->track_type == mkv_track_type_audio && !audio_decoder) + { + audio_decoder = FindAudioDecoder(track_entry); + if (audio_decoder) + { + MultiByteToWideCharSZ(CP_UTF8, 0, track_entry->codec_id, -1, audio_info, 256); + audio_track_num = track_entry->track_number; + } + } + else if (track_entry->track_type == mkv_track_type_video && !video_decoder) + { + video_decoder = FindVideoDecoder(track_entry); + if (video_decoder) + { + StringCbPrintfW(video_info, sizeof(video_info), L"%s %I64ux%I64u", AutoWide(track_entry->codec_id, CP_UTF8), track_entry->video.pixel_width, track_entry->video.pixel_height); + video_track_num = track_entry->track_number; + video_stream = new MKVReaderFILE(filename); + video_timecode_scale = track_entry->track_timecode_scale; + video_track_entry = track_entry; + } + } + } + + // TODO this prevents trying to play video only files + // which the plug-in is not at all happy playing + /*if (!audio_decoder)// && !video_decoder) + return MKV_STOP;*/ + + wchar_t video_status[512] = {0}; + if (audio_decoder && video_decoder) + { + StringCbPrintf(video_status, sizeof(video_status), L"MKV: %s, %s", audio_info, video_info); + videoOutput->extended(VIDUSER_SET_INFOSTRINGW,(INT_PTR)video_status,0); + } + else if (audio_decoder) + { + StringCbPrintf(video_status, sizeof(video_status), L"MKV: %s", audio_info); + videoOutput->extended(VIDUSER_SET_INFOSTRINGW,(INT_PTR)video_status,0); + } + else if (video_decoder) + { + StringCbPrintf(video_status, sizeof(video_status), L"MKV: %s, %s", audio_info, video_info); + videoOutput->extended(VIDUSER_SET_INFOSTRINGW,(INT_PTR)video_status,0); + } + + return MKV_CONTINUE; +} + +DWORD CALLBACK VideoThread(LPVOID param) +{ + MKVPlayer *player = (MKVPlayer *)param; + return player->VideoThreadFunction(); +} + +int MKVPlayer::ParseCluster(nsmkv::MKVReader *stream, uint64_t size, uint64_t *track_numbers, size_t track_numbers_len) +{ + nsmkv::Cluster cluster; + + uint64_t total_bytes_read=0; + while (size) + { + ebml_node node; + uint64_t bytes_read = read_ebml_node(stream, &node); + + if (bytes_read == 0) + return MKV_ERROR; + + // benski> checking bytes_read and node.size separately prevents possible integer overflow attack + if (bytes_read > size) + return MKV_ERROR; + total_bytes_read+=bytes_read; + size-=bytes_read; + + if (node.size > size) + return MKV_ERROR; + total_bytes_read+=node.size; + size-=node.size; + + switch(node.id) + { + case mkv_cluster_timecode: + { + uint64_t val; + if (read_unsigned(stream, node.size, &val) == 0) + return MKV_ERROR; + + printf("Time Code: %I64u\n", val); + cluster.time_code = val; + } + break; + case mkv_cluster_blockgroup: + { + printf("Block Group\n"); + nsmkv::Block block; + if (nsmkv::ReadBlockGroup(stream, node.size, block, track_numbers, track_numbers_len) == 0) + return MKV_ERROR; + + if (block.binary.data) + { + int ret = OnBlock(cluster, block); + if (ret != MKV_CONTINUE) + return ret; + } + } + break; + case mkv_cluster_simpleblock: + { + printf("simple block, size: %I64u\n", node.size); + nsmkv::Block block; + if (ReadBlockBinary(stream, node.size, block.binary, track_numbers, track_numbers_len) == 0) + return 0; + + if (block.binary.data) + { + int ret = OnBlock(cluster, block); + if (ret != MKV_CONTINUE) + return ret; + } + } + break; + default: + nsmkv::ReadGlobal(stream, node.id, node.size); + } + } + return MKV_CONTINUE; +} + +int MKVPlayer::OnBlock(const nsmkv::Cluster &cluster, const nsmkv::Block &block) +{ + if (WaitForSingleObject(killswitch, 0) == WAIT_TIMEOUT) + { + if (block.binary.track_number == audio_track_num) + { + return OnAudio(cluster, block.binary); + } + else if (block.binary.track_number == video_track_num) + { + return OnVideo(cluster, block.binary); + } + return MKV_CONTINUE; + } + else + return MKV_ABORT; +} + +int MKVPlayer::OnFirstCluster(uint64_t position) +{ + if (video_decoder) + { + video_cluster_position = position; + video_thread = CreateThread(0, 0, VideoThread, this, 0, 0); + SetThreadPriority(video_thread, (int)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + } + return MKV_CONTINUE; +} + +int MKVPlayer::Step(nsmkv::MKVReader *stream, uint64_t *track_numbers, size_t track_numbers_len) +{ + uint64_t this_position = stream->Tell(); + + ebml_node node; + if (read_ebml_node(stream, &node) == 0) + return MKV_EOF; + + if (node.id != mkv_void) + { + nsmkv::SeekEntry seek_entry(node.id, this_position-segment_position); + seek_table.AddEntry(seek_entry, nsmkv::SeekTable::ADDENTRY_FOUND); + } + + switch(node.id) + { + case mkv_segment_segmentinfo: + if (nsmkv::ReadSegmentInfo(stream, node.size, segment_info) == 0) + return MKV_EOF; + OnSegmentInfo(segment_info); + break; + + case mkv_metaseek_seekhead: + if (nsmkv::ReadSeekHead(stream, node.size, seek_table) == 0) + return MKV_EOF; + break; + + case mkv_segment_tracks: + if (nsmkv::ReadTracks(stream, node.size, tracks) == 0) + return MKV_EOF; + return OnTracks(tracks); + break; + + case mkv_segment_cues: + if (!cues_searched) + { + if (nsmkv::ReadCues(stream, node.size, cues) == 0) + return MKV_EOF; + cues_searched=true; + } + else + { + stream->Skip(node.size); + } + break; + + case mkv_segment_cluster: + if (!first_cluster_found) + { + first_cluster_found=true; + OnFirstCluster(this_position); + } + return ParseCluster(stream, node.size, track_numbers, track_numbers_len); + break; + + case mkv_segment_attachments: + if (nsmkv::ReadAttachment(stream, node.size, attachments) == 0) + return MKV_EOF; + break; + + default: + if (nsmkv::ReadGlobal(stream, node.id, node.size) == 0) + return MKV_EOF; + break; + } + return MKV_CONTINUE; +} + +DWORD CALLBACK MKVPlayer::ThreadFunction() +{ + // ===== tell audio output helper object about the output plugin ===== + audio_output.Init(plugin.outMod); + + FILE *f = _wfopen(filename, L"rb"); + if (!f) + goto btfo; + + main_reader = new MKVReaderFILE(f); + + // ===== read the header ===== + if (!ParseHeader()) + goto btfo; + + + // ===== find segment start ===== + if (!FindSegment()) + goto btfo; + + // TODO: try to find more segments? + + HANDLE handles[] = {killswitch, seek_event}; + while(1) + { + int ret = WaitForMultipleObjects(2, handles, FALSE, 0); + if (ret == WAIT_TIMEOUT) + { + int ret = Step(main_reader, &audio_track_num, 1); + if (ret == MKV_EOF) + { + break; + } + else if (ret == MKV_ERROR || ret == MKV_STOP) + { + break; + } + } + else if (ret == WAIT_OBJECT_0) // kill + { + break; + } + else if (ret == WAIT_OBJECT_0+1) // seek event + { + ResetEvent(seek_event); + // pause video thread + if (video_decoder) + { + SetEvent(video_break); + WaitForSingleObject(video_flush_done, INFINITE); + } + FindCues(); + uint64_t seek_time = segment_info.ConvertMillisecondsToTime(m_needseek); + uint64_t curr_time = segment_info.ConvertMillisecondsToTime(plugin.outMod->GetOutputTime() + audio_first_timestamp); + int direction = (curr_time < seek_time)?nsmkv::Cues::SEEK_FORWARD:nsmkv::Cues::SEEK_BACKWARD; + nsmkv::CuePoint *cue_point = cues.GetCuePoint(seek_time, curr_time, direction); + if (cue_point) + { + nsmkv::CueTrackPosition *position = cue_point->GetPosition(audio_track_num); + if (!position) // some files don't have the audio track. we're going to assume the data is interleaved and just use the video track + position = cue_point->GetPosition(video_track_num); + if (position) + { + audio_flushing=FLUSH_SEEK; + if (audio_decoder) audio_decoder->Flush(); + main_reader->Seek(position->cluster_position + segment_position); + } + if (video_stream) + { + position = cue_point->GetPosition(video_track_num); + if (position) + { + video_stream->Seek(position->cluster_position + segment_position); + } + } + } + else + { + // TODO enumerate clusters & blocks to find closest time (ugh) + } + + if (video_decoder) + { + SetEvent(video_flush); + WaitForSingleObject(video_flush_done, INFINITE); + } + } + } + + delete main_reader; + main_reader=0; + if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0) + { + // TODO: tell audio decoder about end-of-stream and get remaining audio + audio_output.Write(0,0); + audio_output.WaitWhilePlaying(); + + if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + } + if (audio_decoder) + { + audio_decoder->Close(); + audio_decoder=0; + audio_output.Close(); + } + + return 0; + +btfo: // bail the fuck out + if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + delete main_reader; + main_reader=0; + if (audio_decoder) + { + audio_decoder->Close(); + audio_decoder=0; + audio_output.Close(); + } + return 1; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mkv/api__in_mkv.cpp b/Src/Plugins/Input/in_mkv/api__in_mkv.cpp new file mode 100644 index 00000000..876e3369 --- /dev/null +++ b/Src/Plugins/Input/in_mkv/api__in_mkv.cpp @@ -0,0 +1,45 @@ +#include "api__in_mkv.h" +#include "main.h" +#include <api/service/waservicefactory.h> + +api_config *AGAVE_API_CONFIG = 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_LNG, languageApiGUID); + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(plugin.hDllInstance,InMkvLangGUID); +} + +void WasabiQuit() +{ + ServiceRelease(AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceRelease(WASABI_API_LNG, languageApiGUID); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mkv/api__in_mkv.h b/Src/Plugins/Input/in_mkv/api__in_mkv.h new file mode 100644 index 00000000..22fe2ea5 --- /dev/null +++ b/Src/Plugins/Input/in_mkv/api__in_mkv.h @@ -0,0 +1,9 @@ +#pragma once + +#include "../Agave/Config/api_config.h" +extern api_config *AGAVE_API_CONFIG; + +#include "../Agave/Language/api_language.h" + +void WasabiInit(); +void WasabiQuit(); diff --git a/Src/Plugins/Input/in_mkv/ifc_mkvaudiodecoder.h b/Src/Plugins/Input/in_mkv/ifc_mkvaudiodecoder.h new file mode 100644 index 00000000..2bdb413d --- /dev/null +++ b/Src/Plugins/Input/in_mkv/ifc_mkvaudiodecoder.h @@ -0,0 +1,61 @@ +#pragma once +#include <bfc/dispatch.h> + +class NOVTABLE ifc_mkvaudiodecoder : public Dispatchable +{ +protected: + ifc_mkvaudiodecoder() {} + ~ifc_mkvaudiodecoder() {} +public: + enum + { + MKV_SUCCESS = 0, + MKV_NEED_MORE_INPUT = -1, + MKV_FAILURE=1, + }; + int OutputFrameSize(size_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" + int DecodeBlock(const void *inputBuffer, size_t inputBufferBytes, void *outputBuffer, size_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_BLOCK = 2, + FLUSH = 3, + CLOSE = 4, + END_OF_STREAM = 5, + }; +}; + +inline int ifc_mkvaudiodecoder::OutputFrameSize(size_t *frame_size) +{ + return _call(OUTPUT_FRAME_SIZE, (int)MKV_FAILURE, frame_size); +} + +inline int ifc_mkvaudiodecoder::GetOutputProperties(unsigned int *sampleRate, unsigned int *channels, unsigned int *bitsPerSample, bool *isFloat) +{ + return _call(GET_OUTPUT_PROPERTIES, (int)MKV_FAILURE, sampleRate, channels, bitsPerSample, isFloat); +} + +inline int ifc_mkvaudiodecoder::DecodeBlock(const void *inputBuffer, size_t inputBufferBytes, void *outputBuffer, size_t *outputBufferBytes) +{ + return _call(DECODE_BLOCK, (int)MKV_FAILURE, inputBuffer, inputBufferBytes, outputBuffer, outputBufferBytes); +} + +inline void ifc_mkvaudiodecoder::Flush() +{ + _voidcall(FLUSH); +} + +inline void ifc_mkvaudiodecoder::Close() +{ + _voidcall(CLOSE); +} + +inline void ifc_mkvaudiodecoder::EndOfStream() +{ + _voidcall(END_OF_STREAM); +} diff --git a/Src/Plugins/Input/in_mkv/ifc_mkvvideodecoder.h b/Src/Plugins/Input/in_mkv/ifc_mkvvideodecoder.h new file mode 100644 index 00000000..390a542b --- /dev/null +++ b/Src/Plugins/Input/in_mkv/ifc_mkvvideodecoder.h @@ -0,0 +1,69 @@ +#pragma once +#include <bfc/dispatch.h> + +class NOVTABLE ifc_mkvvideodecoder : public Dispatchable +{ +protected: + ifc_mkvvideodecoder() {} + ~ifc_mkvvideodecoder() {} +public: + enum + { + MKV_SUCCESS = 0, + MKV_NEED_MORE_INPUT = -1, + MKV_FAILURE=1, + }; + int GetOutputProperties(int *x, int *y, int *color_format, double *aspect_ratio); + int DecodeBlock(const void *inputBuffer, size_t inputBufferBytes, uint64_t timestamp); + void Flush(); + void Close(); + int GetPicture(void **data, void **decoder_data, uint64_t *timestamp); + 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 + DISPATCH_CODES + { + GET_OUTPUT_PROPERTIES = 0, + DECODE_BLOCK = 1, + FLUSH = 2, + CLOSE = 3, + GET_PICTURE = 4, + FREE_PICTURE = 5, + END_OF_STREAM = 6, + HURRY_UP = 7, + }; +}; + +inline int ifc_mkvvideodecoder::GetOutputProperties(int *x, int *y, int *color_format, double *aspect_ratio) +{ + return _call(GET_OUTPUT_PROPERTIES, (int)MKV_FAILURE, x, y, color_format, aspect_ratio); +} +inline int ifc_mkvvideodecoder::DecodeBlock(const void *inputBuffer, size_t inputBufferBytes, uint64_t timestamp) +{ + return _call(DECODE_BLOCK, (int)MKV_FAILURE, inputBuffer, inputBufferBytes, timestamp); +} +inline void ifc_mkvvideodecoder::Flush() +{ + _voidcall(FLUSH); +} +inline void ifc_mkvvideodecoder::Close() +{ + _voidcall(CLOSE); +} +inline int ifc_mkvvideodecoder::GetPicture(void **data, void **decoder_data, uint64_t *timestamp) +{ + return _call(GET_PICTURE, (int)MKV_FAILURE, data, decoder_data, timestamp); +} +inline void ifc_mkvvideodecoder::FreePicture(void *data, void *decoder_data) +{ + _voidcall(FREE_PICTURE, data, decoder_data); +} +inline void ifc_mkvvideodecoder::EndOfStream() +{ + _voidcall(END_OF_STREAM); +} + +inline void ifc_mkvvideodecoder::HurryUp(int state) +{ + _voidcall(HURRY_UP, state); +} diff --git a/Src/Plugins/Input/in_mkv/in_mkv.rc b/Src/Plugins/Input/in_mkv/in_mkv.rc new file mode 100644 index 00000000..8b07e77f --- /dev/null +++ b/Src/Plugins/Input/in_mkv/in_mkv.rc @@ -0,0 +1,153 @@ +// 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, 316, 183 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "MKV File Properties" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Close",IDOK,259,162,50,14 + CONTROL "",IDC_TAB1,"SysTabControl32",0x0,7,7,302,151 +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, 309 + TOPMARGIN, 7 + BOTTOMMARGIN, 176 + END + + IDD_TRACKS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 309 + TOPMARGIN, 7 + BOTTOMMARGIN, 176 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_MKV "Nullsoft Matroska Demuxer v%s" + 65535 "{5BDA8055-292D-4fcd-8404-884C2A34A8F9}" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_MKV_OLD "Nullsoft Matroska Demuxer" + IDS_BUFFERING "Buffering" + IDS_FAMILY_STRING "Matroska Video" + IDS_TAB_TRACKS "Tracks" + 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_LANGUAGE "Language" + IDS_TYPE_VIDEO "Video" + IDS_TYPE_AUDIO "Audio" + IDS_TYPE_SUBTITLE "Subtitle" + IDS_MKV_DESC "Matroska Video (MKV)" +END + +STRINGTABLE +BEGIN + IDS_WEBM_DESC "WebM Video (webm)" + IDS_FAMILY_STRING_WEBM "WebM Video" +END + +STRINGTABLE +BEGIN + 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_mkv/in_mkv.sln b/Src/Plugins/Input/in_mkv/in_mkv.sln new file mode 100644 index 00000000..2d4282de --- /dev/null +++ b/Src/Plugins/Input/in_mkv/in_mkv.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29613.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_mkv", "in_mkv.vcxproj", "{FD5581A9-33B6-4D0A-8F1B-C4DC448C2FAE}" +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 + {FD5581A9-33B6-4D0A-8F1B-C4DC448C2FAE}.Debug|Win32.ActiveCfg = Debug|Win32 + {FD5581A9-33B6-4D0A-8F1B-C4DC448C2FAE}.Debug|Win32.Build.0 = Debug|Win32 + {FD5581A9-33B6-4D0A-8F1B-C4DC448C2FAE}.Debug|x64.ActiveCfg = Debug|x64 + {FD5581A9-33B6-4D0A-8F1B-C4DC448C2FAE}.Debug|x64.Build.0 = Debug|x64 + {FD5581A9-33B6-4D0A-8F1B-C4DC448C2FAE}.Release|Win32.ActiveCfg = Release|Win32 + {FD5581A9-33B6-4D0A-8F1B-C4DC448C2FAE}.Release|Win32.Build.0 = Release|Win32 + {FD5581A9-33B6-4D0A-8F1B-C4DC448C2FAE}.Release|x64.ActiveCfg = Release|x64 + {FD5581A9-33B6-4D0A-8F1B-C4DC448C2FAE}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {05F9DA40-C414-4686-8FF9-A07CFB14A3C8} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_mkv/in_mkv.vcxproj b/Src/Plugins/Input/in_mkv/in_mkv.vcxproj new file mode 100644 index 00000000..5aefb111 --- /dev/null +++ b/Src/Plugins/Input/in_mkv/in_mkv.vcxproj @@ -0,0 +1,285 @@ +<?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>{FD5581A9-33B6-4D0A-8F1B-C4DC448C2FAE}</ProjectGuid> + <RootNamespace>in_mkv</RootNamespace> + <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" 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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|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> + <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_MKV_EXPORTS;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4146;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>ws2_32.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_MKV_EXPORTS;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4146;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>ws2_32.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_MKV_EXPORTS;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4146;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>ws2_32.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>WIN64;NDEBUG;_WINDOWS;_USRDLL;IN_MKV_EXPORTS;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4146;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>ws2_32.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="..\..\..\nsmkv\Attachments.cpp" /> + <ClCompile Include="..\..\..\nsmkv\Cluster.cpp" /> + <ClCompile Include="..\..\..\nsmkv\Cues.cpp" /> + <ClCompile Include="..\..\..\nsmkv\ebml_float.cpp" /> + <ClCompile Include="..\..\..\nsmkv\ebml_signed.cpp" /> + <ClCompile Include="..\..\..\nsmkv\ebml_unsigned.cpp" /> + <ClCompile Include="..\..\..\nsmkv\file_mkv_reader.cpp" /> + <ClCompile Include="..\..\..\nsmkv\global_elements.cpp" /> + <ClCompile Include="..\..\..\nsmkv\header.cpp" /> + <ClCompile Include="..\..\..\nsmkv\Lacing.cpp" /> + <ClCompile Include="..\..\..\nsmkv\mkv_date.cpp" /> + <ClCompile Include="..\..\..\nsmkv\read.cpp" /> + <ClCompile Include="..\..\..\nsmkv\SeekTable.cpp" /> + <ClCompile Include="..\..\..\nsmkv\SegmentInfo.cpp" /> + <ClCompile Include="..\..\..\nsmkv\Tracks.cpp" /> + <ClCompile Include="..\..\..\nsmkv\vint.cpp" /> + <ClCompile Include="..\..\..\nu\listview.cpp" /> + <ClCompile Include="..\..\..\nu\SpillBuffer.cpp" /> + <ClCompile Include="api__in_mkv.cpp" /> + <ClCompile Include="ExtendedFileInfo.cpp" /> + <ClCompile Include="InfoDialog.cpp" /> + <ClCompile Include="main.cpp" /> + <ClCompile Include="MKVDuration.cpp" /> + <ClCompile Include="MKVInfo.cpp" /> + <ClCompile Include="PlayThread.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\nsmkv\Attachments.h" /> + <ClInclude Include="..\..\..\nsmkv\Cluster.h" /> + <ClInclude Include="..\..\..\nsmkv\Cues.h" /> + <ClInclude Include="..\..\..\nsmkv\file_mkv_reader.h" /> + <ClInclude Include="..\..\..\nsmkv\Lacing.h" /> + <ClInclude Include="..\..\..\nsmkv\mkv_reader.h" /> + <ClInclude Include="..\..\..\nsmkv\segment.h" /> + <ClInclude Include="..\..\..\nsmkv\SegmentInfo.h" /> + <ClInclude Include="..\..\..\nsmkv\vint.h" /> + <ClInclude Include="..\..\..\nu\AudioOutput.h" /> + <ClInclude Include="api__in_mkv.h" /> + <ClInclude Include="ifc_mkvaudiodecoder.h" /> + <ClInclude Include="ifc_mkvvideodecoder.h" /> + <ClInclude Include="main.h" /> + <ClInclude Include="MKVDuration.h" /> + <ClInclude Include="MKVInfo.h" /> + <ClInclude Include="MKVPlayer.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="svc_mkvdecoder.h" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_mkv.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_mkv/in_mkv.vcxproj.filters b/Src/Plugins/Input/in_mkv/in_mkv.vcxproj.filters new file mode 100644 index 00000000..e268bf30 --- /dev/null +++ b/Src/Plugins/Input/in_mkv/in_mkv.vcxproj.filters @@ -0,0 +1,155 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="api__in_mkv.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedFileInfo.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="MKVDuration.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="MKVInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="PlayThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\Attachments.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\Cluster.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\Cues.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\ebml_float.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\ebml_signed.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\ebml_unsigned.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\file_mkv_reader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\global_elements.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\header.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\Lacing.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\listview.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\mkv_date.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\read.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\SeekTable.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\SegmentInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\SpillBuffer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\Tracks.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\vint.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="api__in_mkv.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ifc_mkvaudiodecoder.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ifc_mkvvideodecoder.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="main.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="MKVDuration.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="MKVInfo.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="MKVPlayer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="svc_mkvdecoder.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsmkv\Attachments.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\AudioOutput.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsmkv\Cluster.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsmkv\Cues.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsmkv\file_mkv_reader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsmkv\Lacing.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsmkv\mkv_reader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsmkv\segment.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsmkv\SegmentInfo.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsmkv\vint.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{ed30a23b-d56f-472b-acf7-658076aa03a3}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{06cdbd20-2120-4a72-9eeb-d8ad2546fd1b}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{3fcda994-3740-4059-81b9-3769d4d24f7b}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_mkv.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mkv/main.cpp b/Src/Plugins/Input/in_mkv/main.cpp new file mode 100644 index 00000000..6db55f9f --- /dev/null +++ b/Src/Plugins/Input/in_mkv/main.cpp @@ -0,0 +1,242 @@ +#include "../Winamp/in2.h" +#include "api__in_mkv.h" +#include "MKVInfo.h" +#include "../Winamp/wa_ipc.h" +#include "main.h" +#include "MKVPlayer.h" +#include "MKVDuration.h" +#include "../nu/ns_wc.h" +#include "resource.h" +#include <strsafe.h> + +#define MKV_PLUGIN_VERSION L"0.86" + +static wchar_t pluginName[256] = {0}; +int g_duration=0; +int paused = 0; +static HANDLE play_thread = 0; +static MKVPlayer *player = 0; + +// {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}; // "MKV\0Matroska Video (MKV)\0" + char* end = 0; + size_t remaining; + StringCchCopyExA(fileExtensionsString, 255, "MKV", &end, &remaining, 0); + StringCchCopyExA(end+1, remaining-1, WASABI_API_LNGSTRING(IDS_MKV_DESC), &end, &remaining, 0); + StringCchCopyExA(end+1, remaining-1, "webm", &end, &remaining, 0); + StringCchCopyExA(end+1, remaining-1, WASABI_API_LNGSTRING(IDS_WEBM_DESC), &end, &remaining, 0); + plugin.FileExtensions = fileExtensionsString; +} + +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_MKV_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,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_MKV),MKV_PLUGIN_VERSION); + plugin.description = (char*)pluginName; + SetFileExtensions(); + + return IN_INIT_SUCCESS; +} + +void Quit() +{ + WasabiQuit(); +} + +void GetFileInfo(const wchar_t *file, wchar_t *title, int *length_in_ms) +{ + if (title) + *title=0; + if (length_in_ms) + { + if (file && *file) + { + MKVDuration duration; + if (duration.Open(file)) + { + if (title) + { + const char *mkv_title = duration.GetTitle(); + if (mkv_title) + MultiByteToWideCharSZ(CP_UTF8, 0, mkv_title, -1, title, GETFILEINFO_TITLE_LENGTH); + } + *length_in_ms=duration.GetLengthMilliseconds(); + } + else + *length_in_ms=-1000; + } + else + *length_in_ms = g_duration; + } +} + +int InfoBox(const wchar_t *file, HWND hwndParent) +{ + MKVInfo info; + if (info.Open(file)) + { + WASABI_API_DIALOGBOXPARAMW(IDD_INFODIALOG, hwndParent, InfoDialog, (LPARAM)&info); + } + return INFOBOX_UNCHANGED; +} + +int IsOurFile(const wchar_t *fn) +{ + return 0; +} + +DWORD CALLBACK MKVThread(LPVOID param); + +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=-1000; + delete player; + player = new MKVPlayer(fn); + play_thread = CreateThread(0, 0, MKVThread, player, 0, 0); + SetThreadPriority(play_thread, (int)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + return 0; // success +} + + +void Pause() +{ + paused = 1; + plugin.outMod->Pause(1); +} + +void UnPause() +{ + paused = 0; + plugin.outMod->Pause(0); +} + +int IsPaused() +{ + return paused; +} + +void Stop() +{ + if (player) + { + player->Kill(); + if (play_thread) { + WaitForSingleObject(play_thread, INFINITE); + } + play_thread = 0; + delete player; + player=0; + } +} + +// time stuff +int GetLength() +{ + return g_duration; +} + +int GetOutputTime() +{ + if (plugin.outMod && player) + return player->GetOutputTime(); + else + return 0; +} + +void SetOutputTime(int time_in_ms) +{ + if (player) + player->Seek(time_in_ms); +} + +void SetVolume(int volume) +{ + plugin.outMod->SetVolume(volume); +} + +void SetPan(int pan) +{ + plugin.outMod->SetPan(pan); +} + +void EQSet(int on, char data[10], int preamp) +{ +} + +In_Module plugin = +{ + IN_VER_RET, + "nullsoft(in_mkv.dll)", + NULL, // hMainWindow + NULL, // hDllInstance + 0 /*"mkv\0Matroska Video\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_mkv/main.h b/Src/Plugins/Input/in_mkv/main.h new file mode 100644 index 00000000..fc5a435a --- /dev/null +++ b/Src/Plugins/Input/in_mkv/main.h @@ -0,0 +1,5 @@ +#pragma once +#include "../Winamp/in2.h" +extern int g_duration; +extern In_Module plugin; +INT_PTR CALLBACK InfoDialog(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mkv/resource.h b/Src/Plugins/Input/in_mkv/resource.h new file mode 100644 index 00000000..a59479ef --- /dev/null +++ b/Src/Plugins/Input/in_mkv/resource.h @@ -0,0 +1,40 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by in_mkv.rc +// +#define IDS_NULLSOFT_MKV_OLD 0 +#define IDS_BUFFERING 2 +#define IDS_MKV_VIDEO 3 +#define IDS_FAMILY_STRING 3 +#define IDS_STRING4 4 +#define IDS_TAB_TRACKS 5 +#define IDS_COLUMN_TRACK_TYPE 6 +#define IDS_COLUMN_CODEC_NAME 7 +#define IDS_COLUMN_CODEC_ID 8 +#define IDS_COLUMN_DESCRIPTION 9 +#define IDS_COLUMN_STREAM_NAME 10 +#define IDS_COLUMN_LANGUAGE 11 +#define IDS_TYPE_VIDEO 12 +#define IDS_TYPE_AUDIO 13 +#define IDS_TYPE_SUBTITLE 14 +#define IDS_MKV_DESC 15 +#define IDS_ABOUT_TEXT 16 +#define IDD_DIALOG1 103 +#define IDD_INFODIALOG 103 +#define IDD_TRACKS 104 +#define IDS_WEBM_DESC 107 +#define IDS_FAMILY_STRING_WEBM 108 +#define IDC_TAB1 1001 +#define IDC_TRACKLIST 1002 +#define IDS_NULLSOFT_MKV 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 17 +#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_mkv/svc_mkvdecoder.h b/Src/Plugins/Input/in_mkv/svc_mkvdecoder.h new file mode 100644 index 00000000..11608c6e --- /dev/null +++ b/Src/Plugins/Input/in_mkv/svc_mkvdecoder.h @@ -0,0 +1,37 @@ +#pragma once +#include <bfc/dispatch.h> +#include "../nsmkv/Tracks.h" +#include <api/service/services.h> +class ifc_mkvvideodecoder; +class ifc_mkvaudiodecoder; +class NOVTABLE svc_mkvdecoder : public Dispatchable +{ +protected: + svc_mkvdecoder() {} + ~svc_mkvdecoder() {} +public: + static FOURCC getServiceType() { return WaSvc::MKVDECODER; } + 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 char *codec_id, const nsmkv::TrackEntryData *track_entry_data, const nsmkv::AudioData *audio_data, unsigned int preferred_bits, unsigned int max_channels, bool floating_point, ifc_mkvaudiodecoder **decoder); + int CreateVideoDecoder(const char *codec_id, const nsmkv::TrackEntryData *track_entry_data, const nsmkv::VideoData *video_data, ifc_mkvvideodecoder **decoder); + DISPATCH_CODES + { + CREATE_AUDIO_DECODER = 0, + CREATE_VIDEO_DECODER = 1, + }; +}; + +inline int svc_mkvdecoder::CreateAudioDecoder(const char *codec_id, const nsmkv::TrackEntryData *track_entry_data, const nsmkv::AudioData *audio_data, unsigned int preferred_bits, unsigned int max_channels, bool floating_point, ifc_mkvaudiodecoder **decoder) +{ + return _call(CREATE_AUDIO_DECODER, (int)CREATEDECODER_NOT_MINE, codec_id, track_entry_data, audio_data, preferred_bits, max_channels, floating_point, decoder); +} + +inline int svc_mkvdecoder::CreateVideoDecoder(const char *codec_id, const nsmkv::TrackEntryData *track_entry_data, const nsmkv::VideoData *video_data, ifc_mkvvideodecoder **decoder) +{ + return _call(CREATE_VIDEO_DECODER, (int)CREATEDECODER_NOT_MINE, codec_id, track_entry_data, video_data, decoder); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mkv/version.rc2 b/Src/Plugins/Input/in_mkv/version.rc2 new file mode 100644 index 00000000..344a1deb --- /dev/null +++ b/Src/Plugins/Input/in_mkv/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,86,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,86,0,0" + VALUE "InternalName", "Nullsoft Matroksa Demuxer" + VALUE "LegalCopyright", "Copyright © 2009-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_mkv.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_mod-openmpt/ExtendedInfo.cpp b/Src/Plugins/Input/in_mod-openmpt/ExtendedInfo.cpp new file mode 100644 index 00000000..93f2fa4c --- /dev/null +++ b/Src/Plugins/Input/in_mod-openmpt/ExtendedInfo.cpp @@ -0,0 +1,90 @@ +#include <bfc/platform/types.h> +#include <windows.h> +#include <strsafe.h> +#include "api__in_mod.h" +#include "resource.h" +#include <libopenmpt/libopenmpt.h> +#include "../nu/AutoChar.h" +#include "../nu/ns_wc.h" +#include <libopenmpt/libopenmpt_stream_callbacks_file.h> + +static wchar_t *open_filename = 0; +static openmpt_module *info_mod = 0; +openmpt_module *OpenMod(const wchar_t *filename); + +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]='0'; + dest[1]=0; + return 1; + } + else if (!_stricmp(data, "family")) + { + size_t len; + const wchar_t *p; + if (!fn || !fn[0]) { + return 0; + } + len = wcslen(fn); + if (len < 4 || L'.' != fn[len - 4]) { + return 0; + } + p = &fn[len - 3]; + const char *tracker = openmpt_get_tracker_name(AutoChar(p)); + if (tracker && *tracker) { + *dest = 0; + MultiByteToWideCharSZ(CP_UTF8, 0, tracker, -1, dest, (int)destlen); + openmpt_free_string(tracker); + return 1; + } + openmpt_free_string(tracker); + return 0; + } else { + if (!open_filename || _wcsicmp(open_filename,fn)) { + free(open_filename); + open_filename = _wcsdup(fn); + openmpt_module_destroy(info_mod); + info_mod = 0; + info_mod = OpenMod(fn); + } + int retval = 0; + + if (!_stricmp(data, "length")) { + double seconds = openmpt_module_get_duration_seconds(info_mod); + StringCchPrintf(dest, destlen, L"%.0f", seconds*1000.0); + retval = 1; + } else if (!_stricmp(data, "artist")) { + const char *value = openmpt_module_get_metadata(info_mod, "artist"); + MultiByteToWideCharSZ(CP_UTF8, 0, value, -1, dest, (int)destlen); + openmpt_free_string(value); + retval = 1; + } else if (!_stricmp(data, "tool")) { + const char *value = openmpt_module_get_metadata(info_mod, "tracker"); + MultiByteToWideCharSZ(CP_UTF8, 0, value, -1, dest, (int)destlen); + openmpt_free_string(value); + retval = 1; + } else if (!_stricmp(data, "title")) { + const char *value = openmpt_module_get_metadata(info_mod, "title"); + MultiByteToWideCharSZ(CP_UTF8, 0, value, -1, dest, (int)destlen); + openmpt_free_string(value); + retval = 1; + } else if (!_stricmp(data, "year")) { + const char *value = openmpt_module_get_metadata(info_mod, "date"); + MultiByteToWideCharSZ(CP_UTF8, 0, value, -1, dest, (int)destlen); + openmpt_free_string(value); + retval = 1; + } else if (!_stricmp(data, "comment")) { + const char *value = openmpt_module_get_metadata(info_mod, "message"); + MultiByteToWideCharSZ(CP_UTF8, 0, value, -1, dest, (int)destlen); + openmpt_free_string(value); + retval = 1; + } + + return retval; + } + + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mod-openmpt/ExtendedRead.cpp b/Src/Plugins/Input/in_mod-openmpt/ExtendedRead.cpp new file mode 100644 index 00000000..646b3807 --- /dev/null +++ b/Src/Plugins/Input/in_mod-openmpt/ExtendedRead.cpp @@ -0,0 +1,139 @@ +#include "api__in_mod.h" +#include <libopenmpt/libopenmpt.h> +#include "../nsutil/pcm.h" + +static const size_t kModBufferSize = 512; +static const unsigned int kModSampleRate = 44100; // TODO(benski) configurable! + +openmpt_module *OpenMod(const wchar_t *filename); + +class PlayParams +{ +public: + PlayParams(); + ~PlayParams(); + openmpt_module *mod; + float *buffer; + int bps; + int channels; + int sample_rate; + bool use_float; + size_t (*openmpt_read)(openmpt_module * mod, int32_t samplerate, size_t count, float *interleaved_stereo); +}; + +PlayParams::PlayParams() +{ + mod = 0; + buffer = 0; +} + +PlayParams::~PlayParams() +{ + openmpt_module_destroy(mod); + free(buffer); + +} + +static PlayParams *ExtendedOpen(const wchar_t *fn, int *size, int *bps, int *nch, int *srate, bool use_float) +{ + float *float_buffer = 0; + size_t (*openmpt_read)(openmpt_module * mod, int32_t samplerate, size_t count, float *interleaved_stereo)=openmpt_module_read_interleaved_float_stereo; + + openmpt_module *mod = OpenMod(fn); + if (!mod) { + return 0; + } + + int requested_channels = *nch; + int requested_bits = *bps; + int requested_srate = *srate; + + if (!requested_channels) { + requested_channels=2; + } + + if (!requested_bits) { + if (use_float) { + requested_bits=32; + } else { + requested_bits=16; + } + } + + if (!requested_srate) { + requested_srate = kModSampleRate; + } + + if (requested_channels == 1) { + openmpt_read = openmpt_module_read_float_mono; + } else if (requested_channels < 4) { + requested_channels = 2; + openmpt_read = openmpt_module_read_interleaved_float_stereo; + } else if (requested_channels) { + requested_channels = 4; + openmpt_read = openmpt_module_read_interleaved_float_quad; + } + + if (!use_float) { + float_buffer = (float *)malloc(sizeof(float) * kModBufferSize * requested_channels); + if (!float_buffer) { + openmpt_module_destroy(mod); + return 0; + } + } + + PlayParams *play_params = new PlayParams; + if (!play_params) { + openmpt_module_destroy(mod); + free(float_buffer); + return 0; + } + + play_params->mod = mod; + play_params->buffer = float_buffer; + play_params->bps = requested_bits; + play_params->channels = requested_channels; + play_params->use_float = use_float; + play_params->openmpt_read = openmpt_read; + play_params->sample_rate = requested_srate; + + *nch = requested_channels; + *srate = requested_srate; + *bps = requested_bits; + + *size = (int)(openmpt_module_get_duration_seconds(mod) * (double)requested_bits * (double)requested_srate * (double)requested_channels / 8.0); + + return play_params; +} +extern "C" __declspec(dllexport) intptr_t winampGetExtendedRead_openW_float(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) +{ + return (intptr_t)ExtendedOpen(fn, size, bps, nch, srate, true); +} + +extern "C" __declspec(dllexport) intptr_t winampGetExtendedRead_openW(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) +{ + return (intptr_t)ExtendedOpen(fn, size, bps, nch, srate, false); +} + +extern "C" __declspec(dllexport) size_t winampGetExtendedRead_getData(intptr_t handle, char *dest, size_t len, int *killswitch) +{ + PlayParams *play_params = (PlayParams *)handle; + size_t requested_samples = len / (play_params->channels * play_params->bps/8); + + if (play_params->use_float) { + return play_params->openmpt_read(play_params->mod, play_params->sample_rate, requested_samples, (float *)dest) * sizeof(float) * play_params->channels; + } else { + if (requested_samples > kModBufferSize) { + requested_samples = kModBufferSize; + } + size_t count = play_params->openmpt_read(play_params->mod, play_params->sample_rate, requested_samples, play_params->buffer); + nsutil_pcm_FloatToInt_Interleaved(dest, play_params->buffer, play_params->bps, play_params->channels*count); + return count * play_params->bps * play_params->channels / 8; + } +} + +extern "C" __declspec(dllexport) void winampGetExtendedRead_close(intptr_t handle) +{ + PlayParams *play_params = (PlayParams *)handle; + delete play_params; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mod-openmpt/MODPlayer.h b/Src/Plugins/Input/in_mod-openmpt/MODPlayer.h new file mode 100644 index 00000000..be8fca01 --- /dev/null +++ b/Src/Plugins/Input/in_mod-openmpt/MODPlayer.h @@ -0,0 +1,44 @@ +#pragma once +#include <bfc/platform/types.h> +#include <libopenmpt/libopenmpt.h> +#include "../nu/AudioOutput.h" + +class MODPlayer +{ +public: + MODPlayer(const wchar_t *_filename); + ~MODPlayer(); + DWORD CALLBACK ThreadFunction(); + + void Kill(); + void Seek(int seek_pos); + int GetOutputTime() const; +private: + enum + { + // values that you can return from OnXXXX() + MOD_CONTINUE = 0, // continue processing + MOD_ABORT = 1, // abort parsing gracefully (maybe 'stop' was pressed) + MOD_STOP = 2, // stop parsing completely - usually returned when mkv version is too new or codecs not supported + + // values returned from errors within the Step() function itself + MOD_EOF = 3, // end of file + MOD_ERROR = 4, // parsing error + }; + + HANDLE killswitch, seek_event; + wchar_t *filename; + volatile int m_needseek; + + /* AudioOutput implementation */ + class MODWait + { + public: + void Wait_SetEvents(HANDLE killswitch, HANDLE seek_event); + protected: + int WaitOrAbort(int time_in_ms); + private: + HANDLE handles[2]; + }; + nu::AudioOutput<MODWait> audio_output; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mod-openmpt/MODThread.cpp b/Src/Plugins/Input/in_mod-openmpt/MODThread.cpp new file mode 100644 index 00000000..40c67082 --- /dev/null +++ b/Src/Plugins/Input/in_mod-openmpt/MODThread.cpp @@ -0,0 +1,166 @@ +#include "api__in_mod.h" +#include "../Winamp/wa_ipc.h" +#include "MODPlayer.h" +#include <libopenmpt/libopenmpt_stream_callbacks_file.h> +#include <nx/nxuri.h> +#include <nx/nxstring.h> +#include <nx/nxfile.h> +#include "../nsutil/pcm.h" + +openmpt_module *OpenMod(const wchar_t *filename); + +extern int g_duration; +extern In_Module plugin; +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = +{ + 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } +}; + +static const size_t kModBufferSize = 512; +static const unsigned int kModSampleRate = 44100; // TODO(benski) configurable! + +void MODPlayer::MODWait::Wait_SetEvents(HANDLE killswitch, HANDLE seek_event) +{ + handles[0]=killswitch; + handles[1]=seek_event; +} + +int MODPlayer::MODWait::WaitOrAbort(int time_in_ms) +{ + switch(WaitForMultipleObjects(2, handles, FALSE, time_in_ms)) + { + case WAIT_TIMEOUT: // all good, wait successful + return 0; + case WAIT_OBJECT_0: // killswitch + return MODPlayer::MOD_STOP; + case WAIT_OBJECT_0+1: // seek event + return MODPlayer::MOD_ABORT; + default: // some OS error? + return MODPlayer::MOD_ERROR; + } +} + +MODPlayer::MODPlayer(const wchar_t *_filename) : audio_output(&plugin) +{ + filename = _wcsdup(_filename); + m_needseek = -1; + + killswitch = CreateEvent(NULL, TRUE, FALSE, NULL); + seek_event = CreateEvent(NULL, TRUE, FALSE, NULL); + + audio_output.Wait_SetEvents(killswitch, seek_event); +} + +MODPlayer::~MODPlayer() +{ + CloseHandle(killswitch); + CloseHandle(seek_event); + free(filename); +} + +void MODPlayer::Kill() +{ + SetEvent(killswitch); +} + +void MODPlayer::Seek(int seek_pos) +{ + m_needseek = seek_pos; + SetEvent(seek_event); +} + +int MODPlayer::GetOutputTime() const +{ + if (m_needseek != -1) + return m_needseek; + else + return plugin.outMod->GetOutputTime(); +} + +DWORD CALLBACK MODThread(LPVOID param) +{ + MODPlayer *player = (MODPlayer *)param; + DWORD ret = player->ThreadFunction(); + return ret; +} + +DWORD CALLBACK MODPlayer::ThreadFunction() +{ + float *float_buffer = 0; + void *int_buffer = 0; + + HANDLE handles[] = {killswitch, seek_event}; + size_t count = 0; + size_t (*openmpt_read)(openmpt_module * mod, int32_t samplerate, size_t count, float *interleaved_stereo)=openmpt_module_read_interleaved_float_stereo; + + int channels = 2; + bool force_mono = AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"mono", false); + bool surround = AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"surround", true); + int bits = (int)AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"bits", 16); + if (force_mono) { + channels = 1; + openmpt_read = openmpt_module_read_float_mono; + } else if (surround) { + channels = 4; + openmpt_read = openmpt_module_read_interleaved_float_quad; + } + + // ===== tell audio output helper object about the output plugin ===== + audio_output.Init(plugin.outMod); + + openmpt_module * mod = OpenMod(filename); + if (!mod) { + goto btfo; + } + openmpt_module_ctl_set(mod, "seek.sync_sample", "1"); + g_duration = (int)(openmpt_module_get_duration_seconds(mod) * 1000); + audio_output.Open(0, channels, kModSampleRate, bits); + + float_buffer = (float *)malloc(sizeof(float) * kModBufferSize * channels); + int_buffer = malloc(kModBufferSize * channels * bits/8); + + while (WaitForMultipleObjects(2, handles, FALSE, 0) != WAIT_OBJECT_0) { + count = openmpt_read(mod, kModSampleRate, kModBufferSize, float_buffer); + if (count == 0) { + break; + } + nsutil_pcm_FloatToInt_Interleaved(int_buffer, float_buffer, bits, channels*count); + int ret = audio_output.Write((char *)int_buffer, channels*count*bits/8); + + if (ret == MOD_STOP) { + break; + } else if (ret == MOD_ABORT) { + ResetEvent(seek_event); + openmpt_module_set_position_seconds(mod, m_needseek/1000.0); + audio_output.Flush(m_needseek); + m_needseek = -1; + } else if (ret != MOD_CONTINUE) { + ret = ret; + } + } + + if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0) { + audio_output.Write(0,0); + audio_output.WaitWhilePlaying(); + + if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0) { + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + } + } + audio_output.Close(); + openmpt_module_destroy(mod); + free(float_buffer); + free(int_buffer); + return 0; + +btfo: // bail the fuck out + if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0) { + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + } + audio_output.Close(); + openmpt_module_destroy(mod); + free(float_buffer); + free(int_buffer); + return 1; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mod-openmpt/api__in_mod.h b/Src/Plugins/Input/in_mod-openmpt/api__in_mod.h new file mode 100644 index 00000000..fdd31ee5 --- /dev/null +++ b/Src/Plugins/Input/in_mod-openmpt/api__in_mod.h @@ -0,0 +1,8 @@ +#pragma once + +#include "../Agave/Config/api_config.h" + +#include "api/application/api_application.h" +#define WASABI_API_APP applicationApi + +#include "../Agave/Language/api_language.h" diff --git a/Src/Plugins/Input/in_mod-openmpt/in_mod-openmpt.rc b/Src/Plugins/Input/in_mod-openmpt/in_mod-openmpt.rc new file mode 100644 index 00000000..77c1b8ac --- /dev/null +++ b/Src/Plugins/Input/in_mod-openmpt/in_mod-openmpt.rc @@ -0,0 +1,81 @@ +// 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 + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_MOD "Nullsoft Module Decoder v%s" + 65535 "{B5691276-95DB-4b5c-BA73-B904A38EFFBF}" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_MOD_OLD "Nullsoft Module Decoder" + IDS_ABOUT_TEXT "%s\nWritten by Ben Allison\n© 2015-2023 Ben Allison\nBuild date: %s\n\nUses libopenmpt v%S\n\n%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_mod-openmpt/in_mod-openmpt.sln b/Src/Plugins/Input/in_mod-openmpt/in_mod-openmpt.sln new file mode 100644 index 00000000..06db8a5e --- /dev/null +++ b/Src/Plugins/Input/in_mod-openmpt/in_mod-openmpt.sln @@ -0,0 +1,182 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29709.97 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_mod-openmpt", "in_mod-openmpt.vcxproj", "{C4CF3B23-75B1-410D-83B9-0C74C02BE13F}" + ProjectSection(ProjectDependencies) = postProject + {57C90706-B25D-4ACA-9B33-95CDB2427C27} = {57C90706-B25D-4ACA-9B33-95CDB2427C27} + {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F} + {1654FB18-FDE6-406F-9D84-BA12BFBD67B2} = {1654FB18-FDE6-406F-9D84-BA12BFBD67B2} + {D8D5E11C-F959-49EF-B741-B3F6DE52DED8} = {D8D5E11C-F959-49EF-B741-B3F6DE52DED8} + {F1397660-35F6-4734-837E-88DCE80E4837} = {F1397660-35F6-4734-837E-88DCE80E4837} + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} + {189B867F-FF4B-45A1-B741-A97492EE69AF} = {189B867F-FF4B-45A1-B741-A97492EE69AF} + {627CF18A-C8CA-451E-AFD0-8679CADFDA6B} = {627CF18A-C8CA-451E-AFD0-8679CADFDA6B} + {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D} + {B544DCB7-16E5-41BC-B51B-7EAD8CFDFA05} = {B544DCB7-16E5-41BC-B51B-7EAD8CFDFA05} + {7ADFAFB9-0A83-4D35-9891-FB24FDF30B53} = {7ADFAFB9-0A83-4D35-9891-FB24FDF30B53} + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} + {9C5101EF-3E20-4558-809B-277FDD50E878} = {9C5101EF-3E20-4558-809B-277FDD50E878} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsutil", "..\nsutil\nsutil.vcxproj", "{DABE6307-F8DD-416D-9DAC-673E2DECB73F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\replicant\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}" + ProjectSection(ProjectDependencies) = postProject + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\replicant\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\replicant\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}" + ProjectSection(ProjectDependencies) = postProject + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\replicant\zlib\zlib.vcxproj", "{0F9730E4-45DA-4BD2-A50A-403A4BC9751A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libopenmpt", "..\libopenmpt\libopenmpt.vcxproj", "{9C5101EF-3E20-4558-809B-277FDD50E878}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libopenmpt_modplug", "..\libopenmpt\libopenmpt_modplug.vcxproj", "{F1397660-35F6-4734-837E-88DCE80E4837}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openmpt-mpg123", "..\libopenmpt\openmpt-mpg123.vcxproj", "{7ADFAFB9-0A83-4D35-9891-FB24FDF30B53}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openmpt-ogg", "..\libopenmpt\openmpt-ogg.vcxproj", "{D8D5E11C-F959-49EF-B741-B3F6DE52DED8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openmpt-portaudio", "..\libopenmpt\openmpt-portaudio.vcxproj", "{189B867F-FF4B-45A1-B741-A97492EE69AF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openmpt-portaudiocpp", "..\libopenmpt\openmpt-portaudiocpp.vcxproj", "{627CF18A-C8CA-451E-AFD0-8679CADFDA6B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openmpt-vorbis", "..\libopenmpt\openmpt-vorbis.vcxproj", "{B544DCB7-16E5-41BC-B51B-7EAD8CFDFA05}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openmpt-zlib", "..\libopenmpt\openmpt-zlib.vcxproj", "{1654FB18-FDE6-406F-9D84-BA12BFBD67B2}" +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 + {C4CF3B23-75B1-410D-83B9-0C74C02BE13F}.Debug|Win32.ActiveCfg = Debug|Win32 + {C4CF3B23-75B1-410D-83B9-0C74C02BE13F}.Debug|Win32.Build.0 = Debug|Win32 + {C4CF3B23-75B1-410D-83B9-0C74C02BE13F}.Debug|x64.ActiveCfg = Debug|x64 + {C4CF3B23-75B1-410D-83B9-0C74C02BE13F}.Debug|x64.Build.0 = Debug|x64 + {C4CF3B23-75B1-410D-83B9-0C74C02BE13F}.Release|Win32.ActiveCfg = Release|Win32 + {C4CF3B23-75B1-410D-83B9-0C74C02BE13F}.Release|Win32.Build.0 = Release|Win32 + {C4CF3B23-75B1-410D-83B9-0C74C02BE13F}.Release|x64.ActiveCfg = Release|x64 + {C4CF3B23-75B1-410D-83B9-0C74C02BE13F}.Release|x64.Build.0 = Release|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.ActiveCfg = Debug|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.Build.0 = Debug|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.ActiveCfg = Debug|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.Build.0 = Debug|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.ActiveCfg = Release|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.Build.0 = Release|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.ActiveCfg = Release|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.Build.0 = Release|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.ActiveCfg = Debug|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.Build.0 = Debug|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.ActiveCfg = Debug|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.Build.0 = Debug|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.ActiveCfg = Release|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.Build.0 = Release|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.ActiveCfg = Release|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.Build.0 = Release|x64 + {9C5101EF-3E20-4558-809B-277FDD50E878}.Debug|Win32.ActiveCfg = Debug|Win32 + {9C5101EF-3E20-4558-809B-277FDD50E878}.Debug|Win32.Build.0 = Debug|Win32 + {9C5101EF-3E20-4558-809B-277FDD50E878}.Debug|x64.ActiveCfg = Debug|x64 + {9C5101EF-3E20-4558-809B-277FDD50E878}.Debug|x64.Build.0 = Debug|x64 + {9C5101EF-3E20-4558-809B-277FDD50E878}.Release|Win32.ActiveCfg = Release|Win32 + {9C5101EF-3E20-4558-809B-277FDD50E878}.Release|Win32.Build.0 = Release|Win32 + {9C5101EF-3E20-4558-809B-277FDD50E878}.Release|x64.ActiveCfg = Release|x64 + {9C5101EF-3E20-4558-809B-277FDD50E878}.Release|x64.Build.0 = Release|x64 + {F1397660-35F6-4734-837E-88DCE80E4837}.Debug|Win32.ActiveCfg = Debug|Win32 + {F1397660-35F6-4734-837E-88DCE80E4837}.Debug|Win32.Build.0 = Debug|Win32 + {F1397660-35F6-4734-837E-88DCE80E4837}.Debug|x64.ActiveCfg = Debug|x64 + {F1397660-35F6-4734-837E-88DCE80E4837}.Debug|x64.Build.0 = Debug|x64 + {F1397660-35F6-4734-837E-88DCE80E4837}.Release|Win32.ActiveCfg = Release|Win32 + {F1397660-35F6-4734-837E-88DCE80E4837}.Release|Win32.Build.0 = Release|Win32 + {F1397660-35F6-4734-837E-88DCE80E4837}.Release|x64.ActiveCfg = Release|x64 + {F1397660-35F6-4734-837E-88DCE80E4837}.Release|x64.Build.0 = Release|x64 + {7ADFAFB9-0A83-4D35-9891-FB24FDF30B53}.Debug|Win32.ActiveCfg = Debug|Win32 + {7ADFAFB9-0A83-4D35-9891-FB24FDF30B53}.Debug|Win32.Build.0 = Debug|Win32 + {7ADFAFB9-0A83-4D35-9891-FB24FDF30B53}.Debug|x64.ActiveCfg = Debug|x64 + {7ADFAFB9-0A83-4D35-9891-FB24FDF30B53}.Debug|x64.Build.0 = Debug|x64 + {7ADFAFB9-0A83-4D35-9891-FB24FDF30B53}.Release|Win32.ActiveCfg = Release|Win32 + {7ADFAFB9-0A83-4D35-9891-FB24FDF30B53}.Release|Win32.Build.0 = Release|Win32 + {7ADFAFB9-0A83-4D35-9891-FB24FDF30B53}.Release|x64.ActiveCfg = Release|x64 + {7ADFAFB9-0A83-4D35-9891-FB24FDF30B53}.Release|x64.Build.0 = Release|x64 + {D8D5E11C-F959-49EF-B741-B3F6DE52DED8}.Debug|Win32.ActiveCfg = Debug|Win32 + {D8D5E11C-F959-49EF-B741-B3F6DE52DED8}.Debug|Win32.Build.0 = Debug|Win32 + {D8D5E11C-F959-49EF-B741-B3F6DE52DED8}.Debug|x64.ActiveCfg = Debug|x64 + {D8D5E11C-F959-49EF-B741-B3F6DE52DED8}.Debug|x64.Build.0 = Debug|x64 + {D8D5E11C-F959-49EF-B741-B3F6DE52DED8}.Release|Win32.ActiveCfg = Release|Win32 + {D8D5E11C-F959-49EF-B741-B3F6DE52DED8}.Release|Win32.Build.0 = Release|Win32 + {D8D5E11C-F959-49EF-B741-B3F6DE52DED8}.Release|x64.ActiveCfg = Release|x64 + {D8D5E11C-F959-49EF-B741-B3F6DE52DED8}.Release|x64.Build.0 = Release|x64 + {189B867F-FF4B-45A1-B741-A97492EE69AF}.Debug|Win32.ActiveCfg = Debug|Win32 + {189B867F-FF4B-45A1-B741-A97492EE69AF}.Debug|Win32.Build.0 = Debug|Win32 + {189B867F-FF4B-45A1-B741-A97492EE69AF}.Debug|x64.ActiveCfg = Debug|x64 + {189B867F-FF4B-45A1-B741-A97492EE69AF}.Debug|x64.Build.0 = Debug|x64 + {189B867F-FF4B-45A1-B741-A97492EE69AF}.Release|Win32.ActiveCfg = Release|Win32 + {189B867F-FF4B-45A1-B741-A97492EE69AF}.Release|Win32.Build.0 = Release|Win32 + {189B867F-FF4B-45A1-B741-A97492EE69AF}.Release|x64.ActiveCfg = Release|x64 + {189B867F-FF4B-45A1-B741-A97492EE69AF}.Release|x64.Build.0 = Release|x64 + {627CF18A-C8CA-451E-AFD0-8679CADFDA6B}.Debug|Win32.ActiveCfg = Debug|Win32 + {627CF18A-C8CA-451E-AFD0-8679CADFDA6B}.Debug|Win32.Build.0 = Debug|Win32 + {627CF18A-C8CA-451E-AFD0-8679CADFDA6B}.Debug|x64.ActiveCfg = Debug|x64 + {627CF18A-C8CA-451E-AFD0-8679CADFDA6B}.Debug|x64.Build.0 = Debug|x64 + {627CF18A-C8CA-451E-AFD0-8679CADFDA6B}.Release|Win32.ActiveCfg = Release|Win32 + {627CF18A-C8CA-451E-AFD0-8679CADFDA6B}.Release|Win32.Build.0 = Release|Win32 + {627CF18A-C8CA-451E-AFD0-8679CADFDA6B}.Release|x64.ActiveCfg = Release|x64 + {627CF18A-C8CA-451E-AFD0-8679CADFDA6B}.Release|x64.Build.0 = Release|x64 + {B544DCB7-16E5-41BC-B51B-7EAD8CFDFA05}.Debug|Win32.ActiveCfg = Debug|Win32 + {B544DCB7-16E5-41BC-B51B-7EAD8CFDFA05}.Debug|Win32.Build.0 = Debug|Win32 + {B544DCB7-16E5-41BC-B51B-7EAD8CFDFA05}.Debug|x64.ActiveCfg = Debug|x64 + {B544DCB7-16E5-41BC-B51B-7EAD8CFDFA05}.Debug|x64.Build.0 = Debug|x64 + {B544DCB7-16E5-41BC-B51B-7EAD8CFDFA05}.Release|Win32.ActiveCfg = Release|Win32 + {B544DCB7-16E5-41BC-B51B-7EAD8CFDFA05}.Release|Win32.Build.0 = Release|Win32 + {B544DCB7-16E5-41BC-B51B-7EAD8CFDFA05}.Release|x64.ActiveCfg = Release|x64 + {B544DCB7-16E5-41BC-B51B-7EAD8CFDFA05}.Release|x64.Build.0 = Release|x64 + {1654FB18-FDE6-406F-9D84-BA12BFBD67B2}.Debug|Win32.ActiveCfg = Debug|Win32 + {1654FB18-FDE6-406F-9D84-BA12BFBD67B2}.Debug|Win32.Build.0 = Debug|Win32 + {1654FB18-FDE6-406F-9D84-BA12BFBD67B2}.Debug|x64.ActiveCfg = Debug|x64 + {1654FB18-FDE6-406F-9D84-BA12BFBD67B2}.Debug|x64.Build.0 = Debug|x64 + {1654FB18-FDE6-406F-9D84-BA12BFBD67B2}.Release|Win32.ActiveCfg = Release|Win32 + {1654FB18-FDE6-406F-9D84-BA12BFBD67B2}.Release|Win32.Build.0 = Release|Win32 + {1654FB18-FDE6-406F-9D84-BA12BFBD67B2}.Release|x64.ActiveCfg = Release|x64 + {1654FB18-FDE6-406F-9D84-BA12BFBD67B2}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B33DB141-83E8-409F-A02B-22328B6EAD6E} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_mod-openmpt/in_mod-openmpt.vcxproj b/Src/Plugins/Input/in_mod-openmpt/in_mod-openmpt.vcxproj new file mode 100644 index 00000000..608eac69 --- /dev/null +++ b/Src/Plugins/Input/in_mod-openmpt/in_mod-openmpt.vcxproj @@ -0,0 +1,271 @@ +<?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>{C4CF3B23-75B1-410D-83B9-0C74C02BE13F}</ProjectGuid> + <RootNamespace>in_modopenmpt</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> + <TargetName>in_mod</TargetName> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + <TargetName>in_mod</TargetName> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + <TargetName>in_mod</TargetName> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + <TargetName>in_mod</TargetName> + </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"> + <VcpkgEnableManifest>false</VcpkgEnableManifest> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>..\..\..\replicant;..\..\..\Wasabi;..\..\..\external_dependencies\openmpt-trunk;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;IN_MODOPENMPT_EXPORTS;UNICODE_INPUT_PLUGIN;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>nsutil.lib;nx.lib;libopenmpt.lib;openmpt-vorbis.lib;openmpt-ogg.lib;openmpt-mpg123.lib;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>..\..\..\external_dependencies\openmpt-trunk\build\lib\$(PlatformShortName)_$(Configuration);..\..\..\replicant\nx\$(PlatformShortName)_$(Configuration);..\..\..\nsutil\$(PlatformShortName)_$(Configuration);%(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>..\..\..\replicant;..\..\..\Wasabi;..\..\..\external_dependencies\openmpt-trunk;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;_DEBUG;_WINDOWS;_USRDLL;IN_MODOPENMPT_EXPORTS;UNICODE_INPUT_PLUGIN;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>nsutil.lib;nx.lib;libopenmpt.lib;openmpt-vorbis.lib;openmpt-ogg.lib;openmpt-mpg123.lib;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>..\..\..\external_dependencies\openmpt-trunk\build\lib\$(PlatformShortName)_$(Configuration);..\..\..\replicant\nx\$(PlatformShortName)_$(Configuration);..\..\..\nsutil\$(PlatformShortName)_$(Configuration);%(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>MaxSpeed</Optimization> + <IntrinsicFunctions>true</IntrinsicFunctions> + <AdditionalIncludeDirectories>..\..\..\replicant;..\..\..\Wasabi;..\..\..\external_dependencies\openmpt-trunk;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;IN_MODOPENMPT_EXPORTS;WIN32_LEAN_AND_MEAN;UNICODE_INPUT_PLUGIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>nsutil.lib;nx.lib;libopenmpt.lib;openmpt-vorbis.lib;openmpt-ogg.lib;openmpt-mpg123.lib;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> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <AdditionalLibraryDirectories>..\..\..\external_dependencies\openmpt-trunk\build\lib\$(PlatformShortName)_$(Configuration);..\..\..\replicant\nx\$(PlatformShortName)_$(Configuration);..\..\..\nsutil\$(PlatformShortName)_$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration> + </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>MaxSpeed</Optimization> + <IntrinsicFunctions>true</IntrinsicFunctions> + <AdditionalIncludeDirectories>..\..\..\replicant;..\..\..\Wasabi;..\..\..\external_dependencies\openmpt-trunk;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;IN_MODOPENMPT_EXPORTS;WIN32_LEAN_AND_MEAN;UNICODE_INPUT_PLUGIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4244;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>nsutil.lib;nx.lib;libopenmpt.lib;openmpt-vorbis.lib;openmpt-ogg.lib;openmpt-mpg123.lib;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> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <AdditionalLibraryDirectories>..\..\..\external_dependencies\openmpt-trunk\build\lib\$(PlatformShortName)_$(Configuration);..\..\..\replicant\nx\$(PlatformShortName)_$(Configuration);..\..\..\nsutil\$(PlatformShortName)_$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration> + </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="..\..\..\nu\SpillBuffer.cpp" /> + <ClCompile Include="ExtendedInfo.cpp" /> + <ClCompile Include="ExtendedRead.cpp" /> + <ClCompile Include="main.cpp" /> + <ClCompile Include="MODThread.cpp" /> + <ClCompile Include="nxfile-callbacks.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\nu\SpillBuffer.h" /> + <ClInclude Include="api__in_mod.h" /> + <ClInclude Include="MODPlayer.h" /> + <ClInclude Include="resource.h" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_mod-openmpt.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_mod-openmpt/in_mod-openmpt.vcxproj.filters b/Src/Plugins/Input/in_mod-openmpt/in_mod-openmpt.vcxproj.filters new file mode 100644 index 00000000..91ec5abd --- /dev/null +++ b/Src/Plugins/Input/in_mod-openmpt/in_mod-openmpt.vcxproj.filters @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="nxfile-callbacks.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="MODThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedRead.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\SpillBuffer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="api__in_mod.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="MODPlayer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\SpillBuffer.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{cc05d482-8e29-4ae1-abbf-739eacb1be71}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{908a3b0b-d02c-47ab-a57c-3c2777135350}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{b548883b-d979-442d-b28f-b5e52a90d483}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_mod-openmpt.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mod-openmpt/main.cpp b/Src/Plugins/Input/in_mod-openmpt/main.cpp new file mode 100644 index 00000000..c719d2b6 --- /dev/null +++ b/Src/Plugins/Input/in_mod-openmpt/main.cpp @@ -0,0 +1,258 @@ +#include "../Winamp/IN2.h" +#include "api__in_mod.h" +#include "../nu/ServiceBuilder.h" +#include "resource.h" +#include <strsafe.h> +#include "MODPlayer.h" +#include "../nu/AutoWide.h" +#include <libopenmpt/libopenmpt.h> + +static MODPlayer *player; +DWORD CALLBACK MODThread(LPVOID param); +extern In_Module plugin; + +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; +int g_duration=0; +int paused = 0; +static HANDLE play_thread = 0; + +static const wchar_t *MOD_PLUGIN_VERSION = L"3.05"; + +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = +{ + 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } +}; + +static wchar_t plugin_name[256]; + +/* Wasabi services */ +api_application *WASABI_API_APP=0; +api_config *AGAVE_API_CONFIG=0; +api_language *WASABI_API_LNG = 0; + +static int Init() +{ + if (!IsWindow(plugin.hMainWindow)) { + return IN_INIT_FAILURE; + } + + ServiceBuild(plugin.service, AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceBuild(plugin.service, WASABI_API_APP, applicationApiServiceGuid); + ServiceBuild(plugin.service, WASABI_API_LNG, languageApiGUID); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(plugin.hDllInstance, InModMPTLangGUID); + StringCbPrintfW(plugin_name,sizeof(plugin_name),WASABI_API_LNGSTRINGW(IDS_NULLSOFT_MOD), MOD_PLUGIN_VERSION); + plugin.description = (char *)plugin_name; + + + static char fileExtensionsString[2048] = ""; + char* end = fileExtensionsString; + size_t remaining=sizeof(fileExtensionsString); + const char *extensions = openmpt_get_supported_extensions(); + char *next_token; + for (const char *extension = strtok_s((char *)extensions, ";", &next_token); extension; extension = strtok_s(NULL, ";", &next_token)) { + StringCbCopyExA(end, remaining, extension, &end, &remaining, 0); + const char *tracker = openmpt_get_tracker_name(extension); + StringCbCopyExA(end+1, remaining-1, tracker, &end, &remaining, 0); + openmpt_free_string(tracker); + end++; remaining--; + + } + plugin.FileExtensions = fileExtensionsString; + *end = 0; + + openmpt_free_string(extensions); + return IN_INIT_SUCCESS; +} + +static void Quit() +{ + ServiceRelease(plugin.service, AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceRelease(plugin.service, WASABI_API_APP, applicationApiServiceGuid); + ServiceRelease(plugin.service, WASABI_API_LNG, languageApiGUID); +} + +static int InfoBox(const wchar_t *file, HWND hwndParent) +{ + return INFOBOX_UNCHANGED; +} + +static int IsOurFile(const wchar_t *file) +{ + return 0; +} + +static void GetFileInfo(const wchar_t *file, wchar_t *title, int *length_in_ms) +{ +} + + + +static int Play(const wchar_t *file) +{ + g_duration=-1000; + player = new MODPlayer(file); + play_thread = CreateThread(0, 0, MODThread, player, 0, 0); + SetThreadPriority(play_thread, (int)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + return 0; // success +} + +static void Pause() +{ + paused = 1; + plugin.outMod->Pause(1); +} + +static void UnPause() +{ + paused = 0; + plugin.outMod->Pause(0); +} + +static int IsPaused() +{ + return paused; +} + +static void Stop() +{ + if (player) { + player->Kill(); + if (play_thread) { + WaitForSingleObject(play_thread, INFINITE); + } + play_thread = 0; + delete player; + player=0; + } +} + +static int GetLength() +{ + return g_duration; +} + +static int GetOutputTime() +{ + if (plugin.outMod && player) { + return player->GetOutputTime(); + } else { + return 0; + } +} + +static void SetOutputTime(int time_in_ms) +{ + if (player) { + player->Seek(time_in_ms); + } +} + +static void SetVolume(int _volume) +{ + plugin.outMod->SetVolume(_volume); +} + +static void SetPan(int _pan) +{ + plugin.outMod->SetPan(_pan); +} + +static void EQSet(int on, char data[10], int preamp) +{ +} + +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); +} + +static void About(HWND hwndParent) +{ + wchar_t message[1024], text[1024]; + char license_trim[1024]; + WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_MOD_OLD,text,1024); + const char *library_version = openmpt_get_string("library_version"); + const char *license = openmpt_get_string("license"); + + // trim the license string + StringCbCopyA(license_trim, sizeof(license_trim), license); + char * trim = license_trim; + for (int i=0;i<4;i++) { + trim = strchr(trim, '\n'); + if (trim) { + trim++; + } + } + *trim=0; + + StringCchPrintfW(message, 1024, + WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), + plugin.description, + TEXT(__DATE__), + library_version, + license_trim + ); + + + DoAboutMessageBox(hwndParent,text,message); +} + + +extern In_Module plugin = +{ + IN_VER_RET, // defined in IN2.H + "nullsoft(in_mod.dll)", + 0, // hMainWindow (filled in by winamp) + 0, // hDllInstance (filled in by winamp) + "S3M\0Scream Tracker\0", // this is a double-null limited list. "EXT\0Description\0EXT\0Description\0" etc. + 1, // is_seekable + 1, // uses output plug-in system + About, // TODO(benski) config + 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, // visualization calls filled in by winamp + + 0,0, // dsp calls filled in by winamp + + EQSet, + + NULL, // setinfo call filled in by winamp + + 0, // out_mod filled in by winamp +}; + +// exported symbol. Returns output module. +extern "C" +{ + __declspec(dllexport) In_Module * winampGetInModule2() + { + return &plugin; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mod-openmpt/nxfile-callbacks.cpp b/Src/Plugins/Input/in_mod-openmpt/nxfile-callbacks.cpp new file mode 100644 index 00000000..571207ba --- /dev/null +++ b/Src/Plugins/Input/in_mod-openmpt/nxfile-callbacks.cpp @@ -0,0 +1,87 @@ +#include <nx/nxfile.h> +#include <libopenmpt/libopenmpt.h> +#include <assert.h> + +static size_t openmpt_nxfile_read(void *stream, void *dst, size_t bytes) +{ + nx_file_t f = (nx_file_t)stream; + size_t bytes_read; + ns_error_t err = NXFileRead(f, dst, bytes, &bytes_read); + if (err != NErr_Success) { + return 0; + } + return bytes_read; +} + +static int openmpt_nxfile_seek(void *stream, int64_t offset, int whence) +{ + nx_file_t f = (nx_file_t)stream; + uint64_t position; + if (whence == OPENMPT_STREAM_SEEK_SET) { + position = offset; + } else if (whence == OPENMPT_STREAM_SEEK_CUR) { + ns_error_t err = NXFileTell(f, &position); + if (err != NErr_Success) { + return -1; + } + position += offset; + } else if (whence == OPENMPT_STREAM_SEEK_END) { + assert(0); + } else { + return -1; + } + ns_error_t err = NXFileSeek(f, position); + if (err = NErr_Success) { + return 0; + } else { + return -1; + } +} + +static int64_t openmpt_nxfile_tell(void *stream) +{ + nx_file_t f = (nx_file_t)stream; + uint64_t position; + if (NXFileTell(f, &position) == NErr_Success) { + return (int64_t)position; + } else { + return -1; + } +} + +openmpt_stream_callbacks openmpt_stream_get_nxfile_callbacks(void) +{ + openmpt_stream_callbacks retval; + memset( &retval, 0, sizeof( openmpt_stream_callbacks ) ); + retval.read = openmpt_nxfile_read; + retval.seek = openmpt_nxfile_seek; + retval.tell = openmpt_nxfile_tell; + return retval; +} + +openmpt_module *OpenMod(const wchar_t *filename) +{ + openmpt_module * mod = 0; + + nx_string_t nx_filename=0; + nx_uri_t nx_uri=0; + NXStringCreateWithUTF16(&nx_filename, filename); + NXURICreateWithNXString(&nx_uri, nx_filename); + NXStringRelease(nx_filename); + + nx_file_t f=0; + ns_error_t nserr; + + nserr = NXFileOpenZip(&f, nx_uri, NULL); + if (nserr != NErr_Success) { + nserr = NXFileOpenFile(&f, nx_uri, nx_file_FILE_read_binary); + } + NXURIRelease(nx_uri); + if (nserr != NErr_Success) { + return 0; + } + + mod = openmpt_module_create(openmpt_stream_get_nxfile_callbacks(), f, NULL, NULL, NULL); + NXFileRelease(f); + return mod; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mod-openmpt/resource.h b/Src/Plugins/Input/in_mod-openmpt/resource.h new file mode 100644 index 00000000..2dc4d87b --- /dev/null +++ b/Src/Plugins/Input/in_mod-openmpt/resource.h @@ -0,0 +1,18 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by in_mod-openmpt.rc +// +#define IDS_NULLSOFT_MOD_OLD 0 +#define IDS_ABOUT_TEXT 2 +#define IDS_NULLSOFT_MOD 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 3 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_mod-openmpt/version.rc2 b/Src/Plugins/Input/in_mod-openmpt/version.rc2 new file mode 100644 index 00000000..fa5af207 --- /dev/null +++ b/Src/Plugins/Input/in_mod-openmpt/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 3,05,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", "3,05,0,0" + VALUE "InternalName", "Nullsoft Module Decoder" + VALUE "LegalCopyright", "Copyright © 2015-2023 Ben Allison, Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_mod.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_mod/fir_proc.cpp b/Src/Plugins/Input/in_mod/fir_proc.cpp new file mode 100644 index 00000000..f3c520a6 --- /dev/null +++ b/Src/Plugins/Input/in_mod/fir_proc.cpp @@ -0,0 +1,172 @@ +/* + * This program is free software; you can redistribute it and modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the license or (at your + * option) any later version. + * + * Authors: Markus Fick <webmaster@mark-f.de> fir-resampler, technical information + * Chris Moeller <chris@kode54.net> Table/struct file generator + * +*/ + +#include <math.h> +#include <stdio.h> + +/* + ------------------------------------------------------------------------------------------------ + fir interpolation doc, + (derived from "an engineer's guide to fir digital filters", n.j. loy) + + calculate coefficients for ideal lowpass filter (with cutoff = fc in 0..1 (mapped to 0..nyquist)) + c[-N..N] = (i==0) ? fc : sin(fc*pi*i)/(pi*i) + + then apply selected window to coefficients + c[-N..N] *= w(0..N) + with n in 2*N and w(n) being a window function (see loy) + + then calculate gain and scale filter coefs to have unity gain. + ------------------------------------------------------------------------------------------------ +*/ +// quantizer scale of window coefs +#define WFIR_QUANTBITS 14 +#define WFIR_QUANTSCALE (1L<<WFIR_QUANTBITS) +#define WFIR_8SHIFT (WFIR_QUANTBITS-8) +#define WFIR_16BITSHIFT (WFIR_QUANTBITS) +// log2(number)-1 of precalculated taps range is [4..12] +#define WFIR_FRACBITS 10 +#define WFIR_LUTLEN ((1L<<(WFIR_FRACBITS+1))+1) +// number of samples in window +#define WFIR_LOG2WIDTH 3 +#define WFIR_WIDTH (1L<<WFIR_LOG2WIDTH) +#define WFIR_SMPSPERWING ((WFIR_WIDTH-1)>>1) +// cutoff (1.0 == pi/2) +#define WFIR_CUTOFF 0.90f +#define WFIR_CUTOFFBITS 9 +#define WFIR_CUTOFFLEN ((1L<<(WFIR_CUTOFFBITS))+1) +// wfir type +#define WFIR_HANN 0 +#define WFIR_HAMMING 1 +#define WFIR_BLACKMANEXACT 2 +#define WFIR_BLACKMAN3T61 3 +#define WFIR_BLACKMAN3T67 4 +#define WFIR_BLACKMAN4T92 5 +#define WFIR_BLACKMAN4T74 6 +#define WFIR_KAISER4T 7 +#define WFIR_TYPE WFIR_BLACKMANEXACT +// wfir help +#ifndef M_zPI +#define M_zPI 3.1415926535897932384626433832795 +#endif +#define M_zEPS 1e-8 +#define M_zBESSELEPS 1e-21 + +class CzWINDOWEDFIR +{ public: + CzWINDOWEDFIR( ); + ~CzWINDOWEDFIR( ); + float coef( int _PCnr, float _POfs, float _PCut, int _PWidth, int _PType ) //float _PPos, float _PFc, int _PLen ) + { double _LWidthM1 = _PWidth-1; + double _LWidthM1Half = 0.5*_LWidthM1; + double _LPosU = ((double)_PCnr - _POfs); + double _LPos = _LPosU-_LWidthM1Half; + double _LPIdl = 2.0*M_zPI/_LWidthM1; + double _LWc,_LSi; + if( fabs(_LPos)<M_zEPS ) + { _LWc = 1.0; + _LSi = _PCut; + } + else + { switch( _PType ) + { case WFIR_HANN: + _LWc = 0.50 - 0.50 * cos(_LPIdl*_LPosU); + break; + case WFIR_HAMMING: + _LWc = 0.54 - 0.46 * cos(_LPIdl*_LPosU); + break; + case WFIR_BLACKMANEXACT: + _LWc = 0.42 - 0.50 * cos(_LPIdl*_LPosU) + 0.08 * cos(2.0*_LPIdl*_LPosU); + break; + case WFIR_BLACKMAN3T61: + _LWc = 0.44959 - 0.49364 * cos(_LPIdl*_LPosU) + 0.05677 * cos(2.0*_LPIdl*_LPosU); + break; + case WFIR_BLACKMAN3T67: + _LWc = 0.42323 - 0.49755 * cos(_LPIdl*_LPosU) + 0.07922 * cos(2.0*_LPIdl*_LPosU); + break; + case WFIR_BLACKMAN4T92: + _LWc = 0.35875 - 0.48829 * cos(_LPIdl*_LPosU) + 0.14128 * cos(2.0*_LPIdl*_LPosU) - 0.01168 * cos(3.0*_LPIdl*_LPosU); + break; + case WFIR_BLACKMAN4T74: + _LWc = 0.40217 - 0.49703 * cos(_LPIdl*_LPosU) + 0.09392 * cos(2.0*_LPIdl*_LPosU) - 0.00183 * cos(3.0*_LPIdl*_LPosU); + break; + case WFIR_KAISER4T: + _LWc = 0.40243 - 0.49804 * cos(_LPIdl*_LPosU) + 0.09831 * cos(2.0*_LPIdl*_LPosU) - 0.00122 * cos(3.0*_LPIdl*_LPosU); + break; + default: + _LWc = 1.0; + break; + } + _LPos *= M_zPI; + _LSi = sin(_PCut*_LPos)/_LPos; + } + return (float)(_LWc*_LSi); + } + static signed short lut[WFIR_LUTLEN*WFIR_WIDTH]; +}; + +signed short CzWINDOWEDFIR::lut[WFIR_LUTLEN*WFIR_WIDTH]; + +CzWINDOWEDFIR::CzWINDOWEDFIR() +{ int _LPcl; + float _LPcllen = (float)(1L<<WFIR_FRACBITS); // number of precalculated lines for 0..1 (-1..0) + float _LNorm = 1.0f / (float)(2.0f * _LPcllen); + float _LCut = WFIR_CUTOFF; + float _LScale = (float)WFIR_QUANTSCALE; + float _LGain,_LCoefs[WFIR_WIDTH]; + for( _LPcl=0;_LPcl<WFIR_LUTLEN;_LPcl++ ) + { + float _LOfs = ((float)_LPcl-_LPcllen)*_LNorm; + int _LCc,_LIdx = _LPcl<<WFIR_LOG2WIDTH; + for( _LCc=0,_LGain=0.0f;_LCc<WFIR_WIDTH;_LCc++ ) + { _LGain += (_LCoefs[_LCc] = coef( _LCc, _LOfs, _LCut, WFIR_WIDTH, WFIR_TYPE )); + } + _LGain = 1.0f/_LGain; + for( _LCc=0;_LCc<WFIR_WIDTH;_LCc++ ) + { float _LCoef = (float)floor( 0.5 + _LScale*_LCoefs[_LCc]*_LGain ); + lut[_LIdx+_LCc] = (signed short)( (_LCoef<-_LScale)?-_LScale:((_LCoef>_LScale)?_LScale:_LCoef) ); + } + } +} + +CzWINDOWEDFIR::~CzWINDOWEDFIR() +{ // nothing todo +} + +CzWINDOWEDFIR sfir; + +// extern "C" signed short *fir_lut = &CzWINDOWEDFIR::lut[0]; + +#define lut(a) CzWINDOWEDFIR::lut[a] + +int main() +{ + + FILE *f; + int i; + + f = fopen("fir_table.h","w"); + + fprintf(f,"static __int64 fir_lut[%d] = {\n",WFIR_LUTLEN * 2); + + for (i=0;i<(WFIR_LUTLEN*WFIR_WIDTH);i+=WFIR_WIDTH) + { + fprintf(f,"\t0x%.4hx%.4hx%.4hx%.4hx, 0x%.4hx%.4hx%.4hx%.4hx%s", + lut(i+3), lut(i+2), lut(i+1), lut(i), + lut(i+7), lut(i+6), lut(i+5), lut(i+4), + (i<((WFIR_LUTLEN-1)*WFIR_WIDTH)) ? ",\n" : "\n};\n"); + } + + fclose(f); + + return(0); +} + diff --git a/Src/Plugins/Input/in_mod/fir_table.h b/Src/Plugins/Input/in_mod/fir_table.h new file mode 100644 index 00000000..a4ec05eb --- /dev/null +++ b/Src/Plugins/Input/in_mod/fir_table.h @@ -0,0 +1,2051 @@ +static __int64 fir_lut[4098] = { + 0x39b70481fe95001b, 0xffe8001bfe950481, + 0x39b7047bfe96001b, 0xffe8001bfe930487, + 0x39b70475fe97001b, 0xffe8001cfe92048d, + 0x39b6046ffe98001b, 0xffe8001cfe910492, + 0x39b60469fe99001b, 0xffe8001cfe900498, + 0x39b60464fe9a001b, 0xffe8001cfe8f049e, + 0x39b6045efe9b001b, 0xffe8001cfe8e04a4, + 0x39b60458fe9c001b, 0xffe8001cfe8d04aa, + 0x39b60452fe9d001b, 0xffe8001cfe8c04b0, + 0x39b6044cfe9e001b, 0xffe8001cfe8b04b6, + 0x39b60447fe9f001a, 0xffe8001cfe8a04bc, + 0x39b60441fea0001a, 0xffe8001cfe8904c2, + 0x39b5043bfea1001a, 0xffe8001cfe8804c7, + 0x39b50435fea2001a, 0xffe8001cfe8704cd, + 0x39b50430fea3001a, 0xffe8001dfe8604d3, + 0x39b5042afea4001a, 0xffe8001dfe8504d9, + 0x39b50424fea5001a, 0xffe8001dfe8304df, + 0x39b4041efea6001a, 0xffe9001dfe8204e5, + 0x39b40419fea7001a, 0xffe9001dfe8104eb, + 0x39b40413fea9001a, 0xffe9001dfe8004f1, + 0x39b4040dfeaa001a, 0xffe9001dfe7f04f7, + 0x39b30407feab001a, 0xffe9001dfe7e04fd, + 0x39b30402feac0019, 0xffe9001dfe7d0503, + 0x39b303fcfead0019, 0xffe9001dfe7c0509, + 0x39b203f6feae0019, 0xffe9001efe7b050f, + 0x39b203f1feaf0019, 0xffe9001efe7a0515, + 0x39b203ebfeb00019, 0xffe9001efe79051b, + 0x39b103e5feb10019, 0xffe9001efe780521, + 0x39b103e0feb20019, 0xffe9001efe760527, + 0x39b103dafeb30019, 0xffe9001efe75052d, + 0x39b003d4feb40019, 0xffe9001efe740533, + 0x39b003cffeb50019, 0xffe9001efe730539, + 0x39b003c9feb60019, 0xffe9001efe72053f, + 0x39af03c3feb70018, 0xffe9001efe710545, + 0x39af03befeb80018, 0xffe9001efe70054b, + 0x39ae03b8feb90018, 0xffe9001ffe6f0552, + 0x39ae03b3feba0018, 0xffe9001ffe6e0558, + 0x39ad03adfebb0018, 0xffe9001ffe6d055e, + 0x39ad03a7febc0018, 0xffe9001ffe6c0564, + 0x39ac03a2febd0018, 0xffe9001ffe6a056a, + 0x39ac039cfebe0018, 0xffe9001ffe690570, + 0x39ab0397febf0018, 0xffe9001ffe680576, + 0x39ab0391fec00018, 0xffe9001ffe67057c, + 0x39aa038cfec10018, 0xffe9001ffe660582, + 0x39aa0386fec20018, 0xffea001ffe650589, + 0x39a90380fec30018, 0xffea001ffe64058f, + 0x39a9037bfec40017, 0xffea0020fe630595, + 0x39a80375fec50017, 0xffea0020fe62059b, + 0x39a80370fec60017, 0xffea0020fe6105a1, + 0x39a7036afec70017, 0xffea0020fe5f05a7, + 0x39a60365fec80017, 0xffea0020fe5e05ad, + 0x39a6035ffec90017, 0xffea0020fe5d05b4, + 0x39a5035afeca0017, 0xffea0020fe5c05ba, + 0x39a40354fecb0017, 0xffea0020fe5b05c0, + 0x39a4034ffecc0017, 0xffea0020fe5a05c6, + 0x39a30349fecd0017, 0xffea0020fe5905cc, + 0x39a20344fece0017, 0xffea0021fe5805d3, + 0x39a2033efecf0017, 0xffea0021fe5705d9, + 0x39a10339fed00016, 0xffea0021fe5505df, + 0x39a00333fed10016, 0xffea0021fe5405e5, + 0x399f032efed20016, 0xffea0021fe5305ec, + 0x399f0329fed30016, 0xffea0021fe5205f2, + 0x399e0323fed40016, 0xffea0021fe5105f8, + 0x399d031efed50016, 0xffea0021fe5005fe, + 0x399c0318fed60016, 0xffea0021fe4f0605, + 0x399c0313fed70016, 0xffea0021fe4e060b, + 0x399b030efed80016, 0xffea0022fe4c0611, + 0x399a0308fed90016, 0xffea0022fe4b0617, + 0x39990303feda0016, 0xffea0022fe4a061e, + 0x399802fdfedb0016, 0xffea0022fe490624, + 0x399802f8fedc0016, 0xffeb0022fe48062a, + 0x399702f3fedd0015, 0xffeb0022fe470631, + 0x399602edfede0015, 0xffeb0022fe460637, + 0x399502e8fedf0015, 0xffeb0022fe45063d, + 0x399402e3fee00015, 0xffeb0022fe430644, + 0x399302ddfee10015, 0xffeb0022fe42064a, + 0x399202d8fee20015, 0xffeb0023fe410650, + 0x399102d3fee30015, 0xffeb0023fe400657, + 0x399002cdfee40015, 0xffeb0023fe3f065d, + 0x398f02c8fee50015, 0xffeb0023fe3e0663, + 0x398e02c3fee60015, 0xffeb0023fe3d066a, + 0x398d02bdfee70015, 0xffeb0023fe3c0670, + 0x398c02b8fee80015, 0xffeb0023fe3a0676, + 0x398b02b3fee90015, 0xffeb0023fe39067d, + 0x398a02adfeea0014, 0xffeb0023fe380683, + 0x398902a8feeb0014, 0xffeb0023fe37068a, + 0x398802a3feec0014, 0xffeb0024fe360690, + 0x3987029efeed0014, 0xffeb0024fe350696, + 0x39860298feee0014, 0xffeb0024fe34069d, + 0x39850293feef0014, 0xffeb0024fe3206a3, + 0x3984028efef00014, 0xffeb0024fe3106aa, + 0x39830289fef10014, 0xffeb0024fe3006b0, + 0x39820283fef20014, 0xffeb0024fe2f06b7, + 0x3981027efef30014, 0xffeb0024fe2e06bd, + 0x39800279fef40014, 0xffeb0024fe2d06c3, + 0x397e0274fef50014, 0xffeb0024fe2c06ca, + 0x397d026ffef60014, 0xffec0025fe2a06d0, + 0x397c0269fef70013, 0xffec0025fe2906d7, + 0x397b0264fef70013, 0xffec0025fe2806dd, + 0x397a025ffef80013, 0xffec0025fe2706e4, + 0x3979025afef90013, 0xffec0025fe2606ea, + 0x39770255fefa0013, 0xffec0025fe2506f1, + 0x39760250fefb0013, 0xffec0025fe2406f7, + 0x3975024afefc0013, 0xffec0025fe2206fe, + 0x39740245fefd0013, 0xffec0025fe210704, + 0x39720240fefe0013, 0xffec0025fe20070b, + 0x3971023bfeff0013, 0xffec0026fe1f0711, + 0x39700236ff000013, 0xffec0026fe1e0718, + 0x396f0231ff010013, 0xffec0026fe1d071f, + 0x396d022cff020013, 0xffec0026fe1b0725, + 0x396c0227ff030013, 0xffec0026fe1a072c, + 0x396b0222ff040012, 0xffec0026fe190732, + 0x3969021cff050012, 0xffec0026fe180739, + 0x39680217ff060012, 0xffec0026fe17073f, + 0x39670212ff070012, 0xffec0026fe160746, + 0x3965020dff080012, 0xffec0027fe14074d, + 0x39640208ff080012, 0xffec0027fe130753, + 0x39620203ff090012, 0xffec0027fe12075a, + 0x396101feff0a0012, 0xffec0027fe110760, + 0x396001f9ff0b0012, 0xffec0027fe100767, + 0x395e01f4ff0c0012, 0xffec0027fe0f076e, + 0x395d01efff0d0012, 0xffed0027fe0d0774, + 0x395b01eaff0e0012, 0xffed0027fe0c077b, + 0x395a01e5ff0f0012, 0xffed0027fe0b0782, + 0x395801e0ff100012, 0xffed0027fe0a0788, + 0x395701dbff110011, 0xffed0028fe09078f, + 0x395501d6ff120011, 0xffed0028fe080795, + 0x395401d1ff130011, 0xffed0028fe06079c, + 0x395201ccff140011, 0xffed0028fe0507a3, + 0x395101c7ff150011, 0xffed0028fe0407a9, + 0x394f01c2ff150011, 0xffed0028fe0307b0, + 0x394e01bdff160011, 0xffed0028fe0207b7, + 0x394c01b8ff170011, 0xffed0028fe0107be, + 0x394a01b3ff180011, 0xffed0028fdff07c4, + 0x394901aeff190011, 0xffed0029fdfe07cb, + 0x394701a9ff1a0011, 0xffed0029fdfd07d2, + 0x394601a4ff1b0011, 0xffed0029fdfc07d8, + 0x394401a0ff1c0011, 0xffed0029fdfb07df, + 0x3942019bff1d0011, 0xffed0029fdf907e6, + 0x39410196ff1e0011, 0xffed0029fdf807ed, + 0x393f0191ff1f0010, 0xffed0029fdf707f3, + 0x393d018cff200010, 0xffed0029fdf607fa, + 0x393c0187ff200010, 0xffed0029fdf50801, + 0x393a0182ff210010, 0xffed002afdf40808, + 0x3938017dff220010, 0xffed002afdf2080e, + 0x39370179ff230010, 0xffed002afdf10815, + 0x39350174ff240010, 0xffed002afdf0081c, + 0x3933016fff250010, 0xffee002afdef0823, + 0x3931016aff260010, 0xffee002afdee082a, + 0x39300165ff270010, 0xffee002afdec0830, + 0x392e0160ff280010, 0xffee002afdeb0837, + 0x392c015cff290010, 0xffee002afdea083e, + 0x392a0157ff290010, 0xffee002bfde90845, + 0x39280152ff2a0010, 0xffee002bfde8084c, + 0x3927014dff2b0010, 0xffee002bfde60852, + 0x39250148ff2c0010, 0xffee002bfde50859, + 0x39230144ff2d000f, 0xffee002bfde40860, + 0x3921013fff2e000f, 0xffee002bfde30867, + 0x391f013aff2f000f, 0xffee002bfde2086e, + 0x391d0135ff30000f, 0xffee002bfde10875, + 0x391b0131ff31000f, 0xffee002bfddf087c, + 0x391a012cff31000f, 0xffee002cfdde0882, + 0x39180127ff32000f, 0xffee002cfddd0889, + 0x39160122ff33000f, 0xffee002cfddc0890, + 0x3914011eff34000f, 0xffee002cfddb0897, + 0x39120119ff35000f, 0xffee002cfdd9089e, + 0x39100114ff36000f, 0xffee002cfdd808a5, + 0x390e0110ff37000f, 0xffee002cfdd708ac, + 0x390c010bff38000f, 0xffee002cfdd608b3, + 0x390a0106ff38000f, 0xffee002cfdd508ba, + 0x39080101ff39000f, 0xffee002dfdd308c1, + 0x390600fdff3a000f, 0xffee002dfdd208c7, + 0x390400f8ff3b000e, 0xffee002dfdd108ce, + 0x390200f3ff3c000e, 0xffef002dfdd008d5, + 0x390000efff3d000e, 0xffef002dfdce08dc, + 0x38fe00eaff3e000e, 0xffef002dfdcd08e3, + 0x38fc00e6ff3e000e, 0xffef002dfdcc08ea, + 0x38f900e1ff3f000e, 0xffef002dfdcb08f1, + 0x38f700dcff40000e, 0xffef002dfdca08f8, + 0x38f500d8ff41000e, 0xffef002efdc808ff, + 0x38f300d3ff42000e, 0xffef002efdc70906, + 0x38f100ceff43000e, 0xffef002efdc6090d, + 0x38ef00caff44000e, 0xffef002efdc50914, + 0x38ed00c5ff44000e, 0xffef002efdc4091b, + 0x38eb00c1ff45000e, 0xffef002efdc20922, + 0x38e800bcff46000e, 0xffef002efdc10929, + 0x38e600b8ff47000e, 0xffef002efdc00930, + 0x38e400b3ff48000e, 0xffef002ffdbf0937, + 0x38e200aeff49000d, 0xffef002ffdbe093e, + 0x38e000aaff4a000d, 0xffef002ffdbc0945, + 0x38dd00a5ff4a000d, 0xffef002ffdbb094c, + 0x38db00a1ff4b000d, 0xffef002ffdba0953, + 0x38d9009cff4c000d, 0xffef002ffdb9095a, + 0x38d70098ff4d000d, 0xffef002ffdb70962, + 0x38d40093ff4e000d, 0xffef002ffdb60969, + 0x38d2008fff4f000d, 0xffef002ffdb50970, + 0x38d0008aff50000d, 0xffef0030fdb40977, + 0x38cd0086ff50000d, 0xffef0030fdb3097e, + 0x38cb0081ff51000d, 0xfff00030fdb10985, + 0x38c9007dff52000d, 0xfff00030fdb0098c, + 0x38c60078ff53000d, 0xfff00030fdaf0993, + 0x38c40074ff54000d, 0xfff00030fdae099a, + 0x38c2006fff55000d, 0xfff00030fdac09a1, + 0x38bf006bff55000d, 0xfff00030fdab09a8, + 0x38bd0067ff56000d, 0xfff00031fdaa09b0, + 0x38ba0062ff57000d, 0xfff00031fda909b7, + 0x38b8005eff58000c, 0xfff00031fda809be, + 0x38b60059ff59000c, 0xfff00031fda609c5, + 0x38b30055ff5a000c, 0xfff00031fda509cc, + 0x38b10050ff5a000c, 0xfff00031fda409d3, + 0x38ae004cff5b000c, 0xfff00031fda309da, + 0x38ac0048ff5c000c, 0xfff00031fda109e2, + 0x38a90043ff5d000c, 0xfff00031fda009e9, + 0x38a7003fff5e000c, 0xfff00032fd9f09f0, + 0x38a4003bff5e000c, 0xfff00032fd9e09f7, + 0x38a20036ff5f000c, 0xfff00032fd9d09fe, + 0x389f0032ff60000c, 0xfff00032fd9b0a06, + 0x389d002dff61000c, 0xfff00032fd9a0a0d, + 0x389a0029ff62000c, 0xfff00032fd990a14, + 0x38980025ff63000c, 0xfff00032fd980a1b, + 0x38950020ff63000c, 0xfff00032fd960a22, + 0x3892001cff64000c, 0xfff00033fd950a2a, + 0x38900018ff65000c, 0xfff00033fd940a31, + 0x388d0014ff66000c, 0xfff00033fd930a38, + 0x388b000fff67000b, 0xfff10033fd910a3f, + 0x3888000bff67000b, 0xfff10033fd900a47, + 0x38850007ff68000b, 0xfff10033fd8f0a4e, + 0x38830002ff69000b, 0xfff10033fd8e0a55, + 0x3880fffeff6a000b, 0xfff10033fd8c0a5c, + 0x387dfffaff6b000b, 0xfff10033fd8b0a64, + 0x387bfff6ff6b000b, 0xfff10034fd8a0a6b, + 0x3878fff1ff6c000b, 0xfff10034fd890a72, + 0x3875ffedff6d000b, 0xfff10034fd880a79, + 0x3872ffe9ff6e000b, 0xfff10034fd860a81, + 0x3870ffe5ff6f000b, 0xfff10034fd850a88, + 0x386dffe0ff6f000b, 0xfff10034fd840a8f, + 0x386affdcff70000b, 0xfff10034fd830a97, + 0x3867ffd8ff71000b, 0xfff10034fd810a9e, + 0x3865ffd4ff72000b, 0xfff10035fd800aa5, + 0x3862ffd0ff73000b, 0xfff10035fd7f0aad, + 0x385fffcbff73000b, 0xfff10035fd7e0ab4, + 0x385cffc7ff74000b, 0xfff10035fd7c0abb, + 0x385affc3ff75000b, 0xfff10035fd7b0ac3, + 0x3857ffbfff76000a, 0xfff10035fd7a0aca, + 0x3854ffbbff76000a, 0xfff10035fd790ad1, + 0x3851ffb7ff77000a, 0xfff10035fd770ad9, + 0x384effb2ff78000a, 0xfff10036fd760ae0, + 0x384bffaeff79000a, 0xfff10036fd750ae7, + 0x3848ffaaff7a000a, 0xfff10036fd740aef, + 0x3845ffa6ff7a000a, 0xfff10036fd720af6, + 0x3843ffa2ff7b000a, 0xfff10036fd710afe, + 0x3840ff9eff7c000a, 0xfff20036fd700b05, + 0x383dff9aff7d000a, 0xfff20036fd6f0b0c, + 0x383aff96ff7d000a, 0xfff20036fd6d0b14, + 0x3837ff91ff7e000a, 0xfff20037fd6c0b1b, + 0x3834ff8dff7f000a, 0xfff20037fd6b0b23, + 0x3831ff89ff80000a, 0xfff20037fd6a0b2a, + 0x382eff85ff80000a, 0xfff20037fd680b32, + 0x382bff81ff81000a, 0xfff20037fd670b39, + 0x3828ff7dff82000a, 0xfff20037fd660b40, + 0x3825ff79ff83000a, 0xfff20037fd650b48, + 0x3822ff75ff84000a, 0xfff20037fd630b4f, + 0x381fff71ff84000a, 0xfff20038fd620b57, + 0x381cff6dff85000a, 0xfff20038fd610b5e, + 0x3819ff69ff860009, 0xfff20038fd600b66, + 0x3816ff65ff870009, 0xfff20038fd5e0b6d, + 0x3813ff61ff870009, 0xfff20038fd5d0b75, + 0x380fff5dff880009, 0xfff20038fd5c0b7c, + 0x380cff59ff890009, 0xfff20038fd5b0b84, + 0x3809ff55ff8a0009, 0xfff20038fd590b8b, + 0x3806ff51ff8a0009, 0xfff20039fd580b93, + 0x3803ff4dff8b0009, 0xfff20039fd570b9a, + 0x3800ff49ff8c0009, 0xfff20039fd560ba2, + 0x37fdff45ff8d0009, 0xfff20039fd540ba9, + 0x37f9ff41ff8d0009, 0xfff20039fd530bb1, + 0x37f6ff3dff8e0009, 0xfff20039fd520bb8, + 0x37f3ff39ff8f0009, 0xfff20039fd510bc0, + 0x37f0ff35ff900009, 0xfff30039fd4f0bc7, + 0x37edff31ff900009, 0xfff3003afd4e0bcf, + 0x37e9ff2dff910009, 0xfff3003afd4d0bd6, + 0x37e6ff2aff920009, 0xfff3003afd4c0bde, + 0x37e3ff26ff920009, 0xfff3003afd4a0be5, + 0x37e0ff22ff930009, 0xfff3003afd490bed, + 0x37dcff1eff940009, 0xfff3003afd480bf4, + 0x37d9ff1aff950009, 0xfff3003afd470bfc, + 0x37d6ff16ff950008, 0xfff3003afd450c04, + 0x37d3ff12ff960008, 0xfff3003bfd440c0b, + 0x37cfff0eff970008, 0xfff3003bfd430c13, + 0x37ccff0bff980008, 0xfff3003bfd410c1a, + 0x37c9ff07ff980008, 0xfff3003bfd400c22, + 0x37c5ff03ff990008, 0xfff3003bfd3f0c2a, + 0x37c2feffff9a0008, 0xfff3003bfd3e0c31, + 0x37befefbff9b0008, 0xfff3003bfd3c0c39, + 0x37bbfef7ff9b0008, 0xfff3003bfd3b0c40, + 0x37b8fef4ff9c0008, 0xfff3003cfd3a0c48, + 0x37b4fef0ff9d0008, 0xfff3003cfd390c50, + 0x37b1feecff9d0008, 0xfff3003cfd370c57, + 0x37adfee8ff9e0008, 0xfff3003cfd360c5f, + 0x37aafee4ff9f0008, 0xfff3003cfd350c67, + 0x37a7fee1ffa00008, 0xfff3003cfd340c6e, + 0x37a3feddffa00008, 0xfff3003cfd320c76, + 0x37a0fed9ffa10008, 0xfff3003cfd310c7e, + 0x379cfed5ffa20008, 0xfff3003dfd300c85, + 0x3799fed2ffa20008, 0xfff3003dfd2f0c8d, + 0x3795feceffa30008, 0xfff3003dfd2d0c95, + 0x3792fecaffa40008, 0xfff4003dfd2c0c9c, + 0x378efec6ffa50008, 0xfff4003dfd2b0ca4, + 0x378bfec3ffa50008, 0xfff4003dfd290cac, + 0x3787febfffa60008, 0xfff4003dfd280cb3, + 0x3784febbffa70007, 0xfff4003efd270cbb, + 0x3780feb7ffa70007, 0xfff4003efd260cc3, + 0x377cfeb4ffa80007, 0xfff4003efd240cca, + 0x3779feb0ffa90007, 0xfff4003efd230cd2, + 0x3775feacffa90007, 0xfff4003efd220cda, + 0x3772fea9ffaa0007, 0xfff4003efd210ce2, + 0x376efea5ffab0007, 0xfff4003efd1f0ce9, + 0x376afea1ffab0007, 0xfff4003efd1e0cf1, + 0x3767fe9effac0007, 0xfff4003ffd1d0cf9, + 0x3763fe9affad0007, 0xfff4003ffd1c0d01, + 0x375ffe96ffae0007, 0xfff4003ffd1a0d08, + 0x375cfe93ffae0007, 0xfff4003ffd190d10, + 0x3758fe8fffaf0007, 0xfff4003ffd180d18, + 0x3754fe8cffb00007, 0xfff4003ffd160d20, + 0x3751fe88ffb00007, 0xfff4003ffd150d27, + 0x374dfe84ffb10007, 0xfff4003ffd140d2f, + 0x3749fe81ffb20007, 0xfff40040fd130d37, + 0x3746fe7dffb20007, 0xfff40040fd110d3f, + 0x3742fe7affb30007, 0xfff40040fd100d47, + 0x373efe76ffb40007, 0xfff40040fd0f0d4e, + 0x373afe72ffb40007, 0xfff40040fd0e0d56, + 0x3737fe6fffb50007, 0xfff40040fd0c0d5e, + 0x3733fe6bffb60007, 0xfff40040fd0b0d66, + 0x372ffe68ffb60007, 0xfff40040fd0a0d6e, + 0x372bfe64ffb70007, 0xfff50041fd080d75, + 0x3727fe61ffb80006, 0xfff50041fd070d7d, + 0x3724fe5dffb80006, 0xfff50041fd060d85, + 0x3720fe5affb90006, 0xfff50041fd050d8d, + 0x371cfe56ffba0006, 0xfff50041fd030d95, + 0x3718fe53ffba0006, 0xfff50041fd020d9d, + 0x3714fe4fffbb0006, 0xfff50041fd010da4, + 0x3710fe4cffbc0006, 0xfff50042fd000dac, + 0x370cfe48ffbc0006, 0xfff50042fcfe0db4, + 0x3708fe45ffbd0006, 0xfff50042fcfd0dbc, + 0x3705fe41ffbe0006, 0xfff50042fcfc0dc4, + 0x3701fe3effbe0006, 0xfff50042fcfa0dcc, + 0x36fdfe3affbf0006, 0xfff50042fcf90dd4, + 0x36f9fe37ffc00006, 0xfff50042fcf80ddb, + 0x36f5fe33ffc00006, 0xfff50042fcf70de3, + 0x36f1fe30ffc10006, 0xfff50043fcf50deb, + 0x36edfe2cffc20006, 0xfff50043fcf40df3, + 0x36e9fe29ffc20006, 0xfff50043fcf30dfb, + 0x36e5fe26ffc30006, 0xfff50043fcf20e03, + 0x36e1fe22ffc40006, 0xfff50043fcf00e0b, + 0x36ddfe1fffc40006, 0xfff50043fcef0e13, + 0x36d9fe1bffc50006, 0xfff50043fcee0e1b, + 0x36d5fe18ffc60006, 0xfff50044fcec0e23, + 0x36d1fe15ffc60006, 0xfff50044fceb0e2b, + 0x36cdfe11ffc70006, 0xfff50044fcea0e33, + 0x36c9fe0effc70006, 0xfff50044fce90e3a, + 0x36c4fe0bffc80006, 0xfff50044fce70e42, + 0x36c0fe07ffc90006, 0xfff50044fce60e4a, + 0x36bcfe04ffc90006, 0xfff50044fce50e52, + 0x36b8fe00ffca0005, 0xfff60044fce40e5a, + 0x36b4fdfdffcb0005, 0xfff60045fce20e62, + 0x36b0fdfaffcb0005, 0xfff60045fce10e6a, + 0x36acfdf6ffcc0005, 0xfff60045fce00e72, + 0x36a8fdf3ffcd0005, 0xfff60045fcde0e7a, + 0x36a3fdf0ffcd0005, 0xfff60045fcdd0e82, + 0x369ffdedffce0005, 0xfff60045fcdc0e8a, + 0x369bfde9ffce0005, 0xfff60045fcdb0e92, + 0x3697fde6ffcf0005, 0xfff60046fcd90e9a, + 0x3693fde3ffd00005, 0xfff60046fcd80ea2, + 0x368ffddfffd00005, 0xfff60046fcd70eaa, + 0x368afddcffd10005, 0xfff60046fcd60eb2, + 0x3686fdd9ffd20005, 0xfff60046fcd40eba, + 0x3682fdd6ffd20005, 0xfff60046fcd30ec2, + 0x367efdd2ffd30005, 0xfff60046fcd20eca, + 0x3679fdcfffd30005, 0xfff60046fcd00ed2, + 0x3675fdccffd40005, 0xfff60047fccf0eda, + 0x3671fdc9ffd50005, 0xfff60047fcce0ee2, + 0x366cfdc5ffd50005, 0xfff60047fccd0eea, + 0x3668fdc2ffd60005, 0xfff60047fccb0ef2, + 0x3664fdbfffd70005, 0xfff60047fcca0efa, + 0x3660fdbcffd70005, 0xfff60047fcc90f03, + 0x365bfdb9ffd80005, 0xfff60047fcc70f0b, + 0x3657fdb5ffd80005, 0xfff60048fcc60f13, + 0x3652fdb2ffd90005, 0xfff60048fcc50f1b, + 0x364efdafffda0005, 0xfff60048fcc40f23, + 0x364afdacffda0005, 0xfff60048fcc20f2b, + 0x3645fda9ffdb0005, 0xfff60048fcc10f33, + 0x3641fda6ffdb0005, 0xfff60048fcc00f3b, + 0x363dfda2ffdc0005, 0xfff60048fcbf0f43, + 0x3638fd9fffdd0005, 0xfff70048fcbd0f4b, + 0x3634fd9cffdd0005, 0xfff70049fcbc0f53, + 0x362ffd99ffde0004, 0xfff70049fcbb0f5b, + 0x362bfd96ffde0004, 0xfff70049fcb90f64, + 0x3626fd93ffdf0004, 0xfff70049fcb80f6c, + 0x3622fd90ffe00004, 0xfff70049fcb70f74, + 0x361dfd8dffe00004, 0xfff70049fcb60f7c, + 0x3619fd89ffe10004, 0xfff70049fcb40f84, + 0x3614fd86ffe10004, 0xfff7004afcb30f8c, + 0x3610fd83ffe20004, 0xfff7004afcb20f94, + 0x360bfd80ffe30004, 0xfff7004afcb10f9c, + 0x3607fd7dffe30004, 0xfff7004afcaf0fa5, + 0x3602fd7affe40004, 0xfff7004afcae0fad, + 0x35fefd77ffe40004, 0xfff7004afcad0fb5, + 0x35f9fd74ffe50004, 0xfff7004afcab0fbd, + 0x35f5fd71ffe50004, 0xfff7004afcaa0fc5, + 0x35f0fd6effe60004, 0xfff7004bfca90fcd, + 0x35ebfd6bffe70004, 0xfff7004bfca80fd6, + 0x35e7fd68ffe70004, 0xfff7004bfca60fde, + 0x35e2fd65ffe80004, 0xfff7004bfca50fe6, + 0x35defd62ffe80004, 0xfff7004bfca40fee, + 0x35d9fd5fffe90004, 0xfff7004bfca30ff6, + 0x35d4fd5cffe90004, 0xfff7004bfca10ffe, + 0x35d0fd59ffea0004, 0xfff7004cfca01007, + 0x35cbfd56ffeb0004, 0xfff7004cfc9f100f, + 0x35c6fd53ffeb0004, 0xfff7004cfc9d1017, + 0x35c2fd50ffec0004, 0xfff7004cfc9c101f, + 0x35bdfd4dffec0004, 0xfff7004cfc9b1028, + 0x35b8fd4affed0004, 0xfff7004cfc9a1030, + 0x35b4fd47ffed0004, 0xfff7004cfc981038, + 0x35affd44ffee0004, 0xfff7004cfc971040, + 0x35aafd41ffef0004, 0xfff8004dfc961048, + 0x35a5fd3effef0004, 0xfff8004dfc951051, + 0x35a1fd3bfff00004, 0xfff8004dfc931059, + 0x359cfd38fff00004, 0xfff8004dfc921061, + 0x3597fd35fff10004, 0xfff8004dfc911069, + 0x3592fd33fff10004, 0xfff8004dfc901072, + 0x358efd30fff20003, 0xfff8004dfc8e107a, + 0x3589fd2dfff20003, 0xfff8004efc8d1082, + 0x3584fd2afff30003, 0xfff8004efc8c108a, + 0x357ffd27fff40003, 0xfff8004efc8a1093, + 0x357afd24fff40003, 0xfff8004efc89109b, + 0x3576fd21fff50003, 0xfff8004efc8810a3, + 0x3571fd1efff50003, 0xfff8004efc8710ab, + 0x356cfd1cfff60003, 0xfff8004efc8510b4, + 0x3567fd19fff60003, 0xfff8004ffc8410bc, + 0x3562fd16fff70003, 0xfff8004ffc8310c4, + 0x355dfd13fff70003, 0xfff8004ffc8210cd, + 0x3558fd10fff80003, 0xfff8004ffc8010d5, + 0x3553fd0dfff80003, 0xfff8004ffc7f10dd, + 0x354ffd0bfff90003, 0xfff8004ffc7e10e6, + 0x354afd08fffa0003, 0xfff8004ffc7d10ee, + 0x3545fd05fffa0003, 0xfff8004ffc7b10f6, + 0x3540fd02fffb0003, 0xfff80050fc7a10fe, + 0x353bfcfffffb0003, 0xfff80050fc791107, + 0x3536fcfdfffc0003, 0xfff80050fc77110f, + 0x3531fcfafffc0003, 0xfff80050fc761117, + 0x352cfcf7fffd0003, 0xfff80050fc751120, + 0x3527fcf4fffd0003, 0xfff80050fc741128, + 0x3522fcf2fffe0003, 0xfff80050fc721130, + 0x351dfceffffe0003, 0xfff80051fc711139, + 0x3518fcecffff0003, 0xfff80051fc701141, + 0x3513fce9ffff0003, 0xfff80051fc6f114a, + 0x350efce700000003, 0xfff80051fc6d1152, + 0x3509fce400000003, 0xfff90051fc6c115a, + 0x3504fce100010003, 0xfff90051fc6b1163, + 0x34fffcdf00020003, 0xfff90051fc6a116b, + 0x34fafcdc00020003, 0xfff90052fc681173, + 0x34f4fcd900030003, 0xfff90052fc67117c, + 0x34effcd600030003, 0xfff90052fc661184, + 0x34eafcd400040003, 0xfff90052fc65118d, + 0x34e5fcd100040003, 0xfff90052fc631195, + 0x34e0fcce00050003, 0xfff90052fc62119d, + 0x34dbfccc00050003, 0xfff90052fc6111a6, + 0x34d6fcc900060003, 0xfff90052fc6011ae, + 0x34d1fcc700060003, 0xfff90053fc5e11b7, + 0x34cbfcc400070003, 0xfff90053fc5d11bf, + 0x34c6fcc100070003, 0xfff90053fc5c11c7, + 0x34c1fcbf00080003, 0xfff90053fc5a11d0, + 0x34bcfcbc00080003, 0xfff90053fc5911d8, + 0x34b7fcb900090002, 0xfff90053fc5811e1, + 0x34b1fcb700090002, 0xfff90053fc5711e9, + 0x34acfcb4000a0002, 0xfff90054fc5511f1, + 0x34a7fcb2000a0002, 0xfff90054fc5411fa, + 0x34a2fcaf000b0002, 0xfff90054fc531202, + 0x349dfcac000b0002, 0xfff90054fc52120b, + 0x3497fcaa000c0002, 0xfff90054fc501213, + 0x3492fca7000c0002, 0xfff90054fc4f121c, + 0x348dfca5000d0002, 0xfff90054fc4e1224, + 0x3487fca2000d0002, 0xfff90054fc4d122d, + 0x3482fca0000e0002, 0xfff90055fc4b1235, + 0x347dfc9d000e0002, 0xfff90055fc4a123d, + 0x3478fc9b000f0002, 0xfff90055fc491246, + 0x3472fc98000f0002, 0xfff90055fc48124e, + 0x346dfc9500100002, 0xfff90055fc461257, + 0x3468fc9300100002, 0xfff90055fc45125f, + 0x3462fc9000110002, 0xfff90055fc441268, + 0x345dfc8e00110002, 0xfff90056fc431270, + 0x3457fc8b00120002, 0xfff90056fc411279, + 0x3452fc8900120002, 0xfffa0056fc401281, + 0x344dfc8600130002, 0xfffa0056fc3f128a, + 0x3447fc8400130002, 0xfffa0056fc3e1292, + 0x3442fc8200130002, 0xfffa0056fc3c129b, + 0x343cfc7f00140002, 0xfffa0056fc3b12a3, + 0x3437fc7d00140002, 0xfffa0057fc3a12ac, + 0x3432fc7a00150002, 0xfffa0057fc3912b4, + 0x342cfc7800150002, 0xfffa0057fc3712bd, + 0x3427fc7500160002, 0xfffa0057fc3612c5, + 0x3421fc7300160002, 0xfffa0057fc3512ce, + 0x341cfc7000170002, 0xfffa0057fc3412d6, + 0x3416fc6e00170002, 0xfffa0057fc3312df, + 0x3411fc6c00180002, 0xfffa0057fc3112e7, + 0x340bfc6900180002, 0xfffa0058fc3012f0, + 0x3406fc6700190002, 0xfffa0058fc2f12f9, + 0x3400fc6400190002, 0xfffa0058fc2e1301, + 0x33fbfc62001a0002, 0xfffa0058fc2c130a, + 0x33f5fc60001a0002, 0xfffa0058fc2b1312, + 0x33f0fc5d001a0002, 0xfffa0058fc2a131b, + 0x33eafc5b001b0002, 0xfffa0058fc291323, + 0x33e5fc58001b0002, 0xfffa0059fc27132c, + 0x33dffc56001c0002, 0xfffa0059fc261334, + 0x33d9fc54001c0002, 0xfffa0059fc25133d, + 0x33d4fc51001d0002, 0xfffa0059fc241346, + 0x33cefc4f001d0002, 0xfffa0059fc22134e, + 0x33c9fc4d001e0002, 0xfffa0059fc211357, + 0x33c3fc4a001e0002, 0xfffa0059fc20135f, + 0x33bdfc48001f0002, 0xfffa005afc1f1368, + 0x33b8fc46001f0002, 0xfffa005afc1e1370, + 0x33b2fc43001f0002, 0xfffa005afc1c1379, + 0x33acfc4100200002, 0xfffa005afc1b1382, + 0x33a7fc3f00200002, 0xfffa005afc1a138a, + 0x33a1fc3d00210002, 0xfffa005afc191393, + 0x339bfc3a00210002, 0xfffa005afc17139b, + 0x3396fc3800220002, 0xfffa005afc1613a4, + 0x3390fc3600220001, 0xfffa005bfc1513ad, + 0x338afc3400230001, 0xfffa005bfc1413b5, + 0x3385fc3100230001, 0xfffa005bfc1213be, + 0x337ffc2f00230001, 0xfffb005bfc1113c6, + 0x3379fc2d00240001, 0xfffb005bfc1013cf, + 0x3373fc2b00240001, 0xfffb005bfc0f13d8, + 0x336efc2800250001, 0xfffb005bfc0e13e0, + 0x3368fc2600250001, 0xfffb005cfc0c13e9, + 0x3362fc2400260001, 0xfffb005cfc0b13f2, + 0x335cfc2200260001, 0xfffb005cfc0a13fa, + 0x3357fc1f00270001, 0xfffb005cfc091403, + 0x3351fc1d00270001, 0xfffb005cfc07140c, + 0x334bfc1b00270001, 0xfffb005cfc061414, + 0x3345fc1900280001, 0xfffb005cfc05141d, + 0x333ffc1700280001, 0xfffb005cfc041425, + 0x3339fc1400290001, 0xfffb005dfc03142e, + 0x3334fc1200290001, 0xfffb005dfc011437, + 0x332efc1000290001, 0xfffb005dfc00143f, + 0x3328fc0e002a0001, 0xfffb005dfbff1448, + 0x3322fc0c002a0001, 0xfffb005dfbfe1451, + 0x331cfc0a002b0001, 0xfffb005dfbfd1459, + 0x3316fc08002b0001, 0xfffb005dfbfb1462, + 0x3310fc05002c0001, 0xfffb005efbfa146b, + 0x330bfc03002c0001, 0xfffb005efbf91473, + 0x3305fc01002c0001, 0xfffb005efbf8147c, + 0x32fffbff002d0001, 0xfffb005efbf61485, + 0x32f9fbfd002d0001, 0xfffb005efbf5148e, + 0x32f3fbfb002e0001, 0xfffb005efbf41496, + 0x32edfbf9002e0001, 0xfffb005efbf3149f, + 0x32e7fbf7002e0001, 0xfffb005efbf214a8, + 0x32e1fbf5002f0001, 0xfffb005ffbf014b0, + 0x32dbfbf3002f0001, 0xfffb005ffbef14b9, + 0x32d5fbf000300001, 0xfffb005ffbee14c2, + 0x32cffbee00300001, 0xfffb005ffbed14ca, + 0x32c9fbec00300001, 0xfffb005ffbec14d3, + 0x32c3fbea00310001, 0xfffb005ffbea14dc, + 0x32bdfbe800310001, 0xfffb005ffbe914e5, + 0x32b7fbe600320001, 0xfffb0060fbe814ed, + 0x32b1fbe400320001, 0xfffb0060fbe714f6, + 0x32abfbe200320001, 0xfffb0060fbe614ff, + 0x32a5fbe000330001, 0xfffb0060fbe41507, + 0x329ffbde00330001, 0xfffb0060fbe31510, + 0x3299fbdc00340001, 0xfffb0060fbe21519, + 0x3293fbda00340001, 0xfffb0060fbe11522, + 0x328dfbd800340001, 0xfffb0060fbe0152a, + 0x3286fbd600350001, 0xfffc0061fbde1533, + 0x3280fbd400350001, 0xfffc0061fbdd153c, + 0x327afbd200360001, 0xfffc0061fbdc1545, + 0x3274fbd000360001, 0xfffc0061fbdb154d, + 0x326efbce00360001, 0xfffc0061fbda1556, + 0x3268fbcc00370001, 0xfffc0061fbd9155f, + 0x3262fbca00370001, 0xfffc0061fbd71568, + 0x325cfbc800380001, 0xfffc0062fbd61570, + 0x3255fbc600380001, 0xfffc0062fbd51579, + 0x324ffbc400380001, 0xfffc0062fbd41582, + 0x3249fbc300390001, 0xfffc0062fbd3158b, + 0x3243fbc100390001, 0xfffc0062fbd11593, + 0x323dfbbf00390001, 0xfffc0062fbd0159c, + 0x3237fbbd003a0001, 0xfffc0062fbcf15a5, + 0x3230fbbb003a0001, 0xfffc0062fbce15ae, + 0x322afbb9003b0001, 0xfffc0063fbcd15b7, + 0x3224fbb7003b0001, 0xfffc0063fbcc15bf, + 0x321efbb5003b0001, 0xfffc0063fbca15c8, + 0x3217fbb3003c0001, 0xfffc0063fbc915d1, + 0x3211fbb1003c0001, 0xfffc0063fbc815da, + 0x320bfbb0003c0001, 0xfffc0063fbc715e2, + 0x3205fbae003d0001, 0xfffc0063fbc615eb, + 0x31fefbac003d0001, 0xfffc0063fbc415f4, + 0x31f8fbaa003e0001, 0xfffc0064fbc315fd, + 0x31f2fba8003e0001, 0xfffc0064fbc21606, + 0x31ecfba6003e0001, 0xfffc0064fbc1160e, + 0x31e5fba4003f0001, 0xfffc0064fbc01617, + 0x31dffba3003f0001, 0xfffc0064fbbf1620, + 0x31d9fba1003f0001, 0xfffc0064fbbd1629, + 0x31d2fb9f00400001, 0xfffc0064fbbc1632, + 0x31ccfb9d00400001, 0xfffc0064fbbb163b, + 0x31c6fb9b00400001, 0xfffc0065fbba1643, + 0x31bffb9a00410001, 0xfffc0065fbb9164c, + 0x31b9fb9800410001, 0xfffc0065fbb81655, + 0x31b2fb9600420001, 0xfffc0065fbb7165e, + 0x31acfb9400420001, 0xfffc0065fbb51667, + 0x31a6fb9200420001, 0xfffc0065fbb4166f, + 0x319ffb9100430000, 0xfffc0065fbb31678, + 0x3199fb8f00430000, 0xfffc0066fbb21681, + 0x3192fb8d00430000, 0xfffc0066fbb1168a, + 0x318cfb8b00440000, 0xfffc0066fbb01693, + 0x3186fb8a00440000, 0xfffc0066fbae169c, + 0x317ffb8800440000, 0xfffc0066fbad16a4, + 0x3179fb8600450000, 0xfffc0066fbac16ad, + 0x3172fb8400450000, 0xfffc0066fbab16b6, + 0x316cfb8300450000, 0xfffc0066fbaa16bf, + 0x3165fb8100460000, 0xfffc0067fba916c8, + 0x315ffb7f00460000, 0xfffd0067fba816d1, + 0x3158fb7e00460000, 0xfffd0067fba616da, + 0x3152fb7c00470000, 0xfffd0067fba516e2, + 0x314bfb7a00470000, 0xfffd0067fba416eb, + 0x3145fb7900470000, 0xfffd0067fba316f4, + 0x313efb7700480000, 0xfffd0067fba216fd, + 0x3138fb7500480000, 0xfffd0067fba11706, + 0x3131fb7300480000, 0xfffd0068fba0170f, + 0x312bfb7200490000, 0xfffd0068fb9e1718, + 0x3124fb7000490000, 0xfffd0068fb9d1721, + 0x311efb6f00490000, 0xfffd0068fb9c1729, + 0x3117fb6d004a0000, 0xfffd0068fb9b1732, + 0x3110fb6b004a0000, 0xfffd0068fb9a173b, + 0x310afb6a004a0000, 0xfffd0068fb991744, + 0x3103fb68004b0000, 0xfffd0068fb98174d, + 0x30fdfb66004b0000, 0xfffd0069fb961756, + 0x30f6fb65004b0000, 0xfffd0069fb95175f, + 0x30f0fb63004c0000, 0xfffd0069fb941768, + 0x30e9fb62004c0000, 0xfffd0069fb931770, + 0x30e2fb60004c0000, 0xfffd0069fb921779, + 0x30dcfb5e004d0000, 0xfffd0069fb911782, + 0x30d5fb5d004d0000, 0xfffd0069fb90178b, + 0x30cefb5b004d0000, 0xfffd0069fb8f1794, + 0x30c8fb5a004e0000, 0xfffd006afb8e179d, + 0x30c1fb58004e0000, 0xfffd006afb8c17a6, + 0x30bafb56004e0000, 0xfffd006afb8b17af, + 0x30b4fb55004e0000, 0xfffd006afb8a17b8, + 0x30adfb53004f0000, 0xfffd006afb8917c1, + 0x30a6fb52004f0000, 0xfffd006afb8817c9, + 0x30a0fb50004f0000, 0xfffd006afb8717d2, + 0x3099fb4f00500000, 0xfffd006afb8617db, + 0x3092fb4d00500000, 0xfffd006bfb8517e4, + 0x308bfb4c00500000, 0xfffd006bfb8417ed, + 0x3085fb4a00510000, 0xfffd006bfb8217f6, + 0x307efb4900510000, 0xfffd006bfb8117ff, + 0x3077fb4700510000, 0xfffd006bfb801808, + 0x3070fb4600520000, 0xfffd006bfb7f1811, + 0x306afb4400520000, 0xfffd006bfb7e181a, + 0x3063fb4300520000, 0xfffd006bfb7d1823, + 0x305cfb4100520000, 0xfffd006cfb7c182c, + 0x3055fb4000530000, 0xfffd006cfb7b1835, + 0x304ffb3e00530000, 0xfffd006cfb7a183d, + 0x3048fb3d00530000, 0xfffd006cfb791846, + 0x3041fb3b00540000, 0xfffd006cfb77184f, + 0x303afb3a00540000, 0xfffd006cfb761858, + 0x3033fb3800540000, 0xfffd006cfb751861, + 0x302cfb3700550000, 0xfffd006cfb74186a, + 0x3026fb3500550000, 0xfffd006dfb731873, + 0x301ffb3400550000, 0xfffd006dfb72187c, + 0x3018fb3300550000, 0xfffd006dfb711885, + 0x3011fb3100560000, 0xfffd006dfb70188e, + 0x300afb3000560000, 0xfffd006dfb6f1897, + 0x3003fb2e00560000, 0xfffd006dfb6e18a0, + 0x2ffcfb2d00570000, 0xfffd006dfb6d18a9, + 0x2ff5fb2c00570000, 0xfffd006dfb6b18b2, + 0x2feffb2a00570000, 0xfffe006dfb6a18bb, + 0x2fe8fb2900570000, 0xfffe006efb6918c4, + 0x2fe1fb2700580000, 0xfffe006efb6818cd, + 0x2fdafb2600580000, 0xfffe006efb6718d6, + 0x2fd3fb2500580000, 0xfffe006efb6618df, + 0x2fccfb2300590000, 0xfffe006efb6518e8, + 0x2fc5fb2200590000, 0xfffe006efb6418f1, + 0x2fbefb2100590000, 0xfffe006efb6318f9, + 0x2fb7fb1f00590000, 0xfffe006efb621902, + 0x2fb0fb1e005a0000, 0xfffe006ffb61190b, + 0x2fa9fb1c005a0000, 0xfffe006ffb601914, + 0x2fa2fb1b005a0000, 0xfffe006ffb5f191d, + 0x2f9bfb1a005a0000, 0xfffe006ffb5e1926, + 0x2f94fb19005b0000, 0xfffe006ffb5d192f, + 0x2f8dfb17005b0000, 0xfffe006ffb5c1938, + 0x2f86fb16005b0000, 0xfffe006ffb5a1941, + 0x2f7ffb15005c0000, 0xfffe006ffb59194a, + 0x2f78fb13005c0000, 0xfffe006ffb581953, + 0x2f71fb12005c0000, 0xfffe0070fb57195c, + 0x2f6afb11005c0000, 0xfffe0070fb561965, + 0x2f63fb0f005d0000, 0xfffe0070fb55196e, + 0x2f5cfb0e005d0000, 0xfffe0070fb541977, + 0x2f55fb0d005d0000, 0xfffe0070fb531980, + 0x2f4efb0c005d0000, 0xfffe0070fb521989, + 0x2f47fb0a005e0000, 0xfffe0070fb511992, + 0x2f40fb09005e0000, 0xfffe0070fb50199b, + 0x2f39fb08005e0000, 0xfffe0071fb4f19a4, + 0x2f32fb07005e0000, 0xfffe0071fb4e19ad, + 0x2f2afb05005f0000, 0xfffe0071fb4d19b6, + 0x2f23fb04005f0000, 0xfffe0071fb4c19bf, + 0x2f1cfb03005f0000, 0xfffe0071fb4b19c8, + 0x2f15fb02005f0000, 0xfffe0071fb4a19d1, + 0x2f0efb0000600000, 0xfffe0071fb4919da, + 0x2f07faff00600000, 0xfffe0071fb4819e3, + 0x2f00fafe00600000, 0xfffe0071fb4719ec, + 0x2ef8fafd00600000, 0xfffe0072fb4619f5, + 0x2ef1fafc00610000, 0xfffe0072fb4519fe, + 0x2eeafafa00610000, 0xfffe0072fb441a07, + 0x2ee3faf900610000, 0xfffe0072fb431a10, + 0x2edcfaf800610000, 0xfffe0072fb421a19, + 0x2ed5faf700620000, 0xfffe0072fb411a22, + 0x2ecdfaf600620000, 0xfffe0072fb401a2b, + 0x2ec6faf500620000, 0xfffe0072fb3f1a34, + 0x2ebffaf300620000, 0xfffe0072fb3e1a3d, + 0x2eb8faf200630000, 0xfffe0073fb3d1a46, + 0x2eb1faf100630000, 0xfffe0073fb3c1a4f, + 0x2ea9faf000630000, 0xfffe0073fb3b1a58, + 0x2ea2faef00630000, 0xfffe0073fb3a1a61, + 0x2e9bfaee00640000, 0xfffe0073fb391a6a, + 0x2e94faed00640000, 0xfffe0073fb381a73, + 0x2e8cfaeb00640000, 0xfffe0073fb371a7c, + 0x2e85faea00640000, 0xfffe0073fb361a85, + 0x2e7efae900650000, 0xfffe0073fb351a8e, + 0x2e77fae800650000, 0xfffe0074fb341a97, + 0x2e6ffae700650000, 0xfffe0074fb331aa0, + 0x2e68fae600650000, 0xfffe0074fb321aa9, + 0x2e61fae500650000, 0xfffe0074fb311ab2, + 0x2e59fae400660000, 0xfffe0074fb301abb, + 0x2e52fae300660000, 0xfffe0074fb2f1ac4, + 0x2e4bfae200660000, 0xfffe0074fb2e1ace, + 0x2e43fae000660000, 0xfffe0074fb2d1ad7, + 0x2e3cfadf00670000, 0xfffe0074fb2c1ae0, + 0x2e35fade00670000, 0xfffe0074fb2b1ae9, + 0x2e2dfadd00670000, 0xfffe0075fb2a1af2, + 0x2e26fadc00670000, 0xfffe0075fb291afb, + 0x2e1ffadb00670000, 0xfffe0075fb281b04, + 0x2e17fada00680000, 0xfffe0075fb271b0d, + 0x2e10fad900680000, 0xfffe0075fb261b16, + 0x2e09fad800680000, 0xfffe0075fb251b1f, + 0x2e01fad700680000, 0xfffe0075fb241b28, + 0x2dfafad600690000, 0xffff0075fb231b31, + 0x2df2fad500690000, 0xffff0075fb221b3a, + 0x2debfad400690000, 0xffff0076fb211b43, + 0x2de4fad300690000, 0xffff0076fb201b4c, + 0x2ddcfad200690000, 0xffff0076fb1f1b55, + 0x2dd5fad1006a0000, 0xffff0076fb1e1b5e, + 0x2dcdfad0006a0000, 0xffff0076fb1d1b67, + 0x2dc6facf006a0000, 0xffff0076fb1c1b70, + 0x2dbfface006a0000, 0xffff0076fb1b1b79, + 0x2db7facd006a0000, 0xffff0076fb1a1b82, + 0x2db0facc006b0000, 0xffff0076fb191b8b, + 0x2da8facb006b0000, 0xffff0076fb181b94, + 0x2da1faca006b0000, 0xffff0077fb171b9d, + 0x2d99fac9006b0000, 0xffff0077fb171ba6, + 0x2d92fac8006b0000, 0xffff0077fb161baf, + 0x2d8afac7006c0000, 0xffff0077fb151bb8, + 0x2d83fac7006c0000, 0xffff0077fb141bc2, + 0x2d7bfac6006c0000, 0xffff0077fb131bcb, + 0x2d74fac5006c0000, 0xffff0077fb121bd4, + 0x2d6cfac4006c0000, 0xffff0077fb111bdd, + 0x2d65fac3006d0000, 0xffff0077fb101be6, + 0x2d5dfac2006d0000, 0xffff0078fb0f1bef, + 0x2d56fac1006d0000, 0xffff0078fb0e1bf8, + 0x2d4efac0006d0000, 0xffff0078fb0d1c01, + 0x2d47fabf006d0000, 0xffff0078fb0c1c0a, + 0x2d3ffabe006e0000, 0xffff0078fb0b1c13, + 0x2d38fabd006e0000, 0xffff0078fb0b1c1c, + 0x2d30fabd006e0000, 0xffff0078fb0a1c25, + 0x2d28fabc006e0000, 0xffff0078fb091c2e, + 0x2d21fabb006e0000, 0xffff0078fb081c37, + 0x2d19faba006f0000, 0xffff0078fb071c40, + 0x2d12fab9006f0000, 0xffff0078fb061c49, + 0x2d0afab8006f0000, 0xffff0079fb051c52, + 0x2d02fab7006f0000, 0xffff0079fb041c5b, + 0x2cfbfab7006f0000, 0xffff0079fb031c64, + 0x2cf3fab600700000, 0xffff0079fb021c6d, + 0x2cecfab500700000, 0xffff0079fb011c77, + 0x2ce4fab400700000, 0xffff0079fb011c80, + 0x2cdcfab300700000, 0xffff0079fb001c89, + 0x2cd5fab200700000, 0xffff0079faff1c92, + 0x2ccdfab200700000, 0xffff0079fafe1c9b, + 0x2cc5fab100710000, 0xffff0079fafd1ca4, + 0x2cbefab000710000, 0xffff007afafc1cad, + 0x2cb6faaf00710000, 0xffff007afafb1cb6, + 0x2caefaae00710000, 0xffff007afafa1cbf, + 0x2ca7faae00710000, 0xffff007afaf91cc8, + 0x2c9ffaad00720000, 0xffff007afaf91cd1, + 0x2c97faac00720000, 0xffff007afaf81cda, + 0x2c90faab00720000, 0xffff007afaf71ce3, + 0x2c88faab00720000, 0xffff007afaf61cec, + 0x2c80faaa00720000, 0xffff007afaf51cf5, + 0x2c79faa900720000, 0xffff007afaf41cfe, + 0x2c71faa800730000, 0xffff007afaf31d07, + 0x2c69faa800730000, 0xffff007bfaf31d10, + 0x2c62faa700730000, 0xffff007bfaf21d1a, + 0x2c5afaa600730000, 0xffff007bfaf11d23, + 0x2c52faa500730000, 0xffff007bfaf01d2c, + 0x2c4afaa500730000, 0xffff007bfaef1d35, + 0x2c43faa400740000, 0xffff007bfaee1d3e, + 0x2c3bfaa300740000, 0xffff007bfaed1d47, + 0x2c33faa200740000, 0xffff007bfaed1d50, + 0x2c2bfaa200740000, 0xffff007bfaec1d59, + 0x2c24faa100740000, 0xffff007bfaeb1d62, + 0x2c1cfaa000740000, 0xffff007bfaea1d6b, + 0x2c14faa000750000, 0xffff007cfae91d74, + 0x2c0cfa9f00750000, 0xffff007cfae81d7d, + 0x2c04fa9e00750000, 0xffff007cfae81d86, + 0x2bfdfa9e00750000, 0xffff007cfae71d8f, + 0x2bf5fa9d00750000, 0xffff007cfae61d98, + 0x2bedfa9c00750000, 0xffff007cfae51da1, + 0x2be5fa9c00750000, 0xffff007cfae41daa, + 0x2bddfa9b00760000, 0xffff007cfae31db3, + 0x2bd5fa9a00760000, 0xffff007cfae31dbd, + 0x2bcefa9a00760000, 0xffff007cfae21dc6, + 0x2bc6fa9900760000, 0xffff007cfae11dcf, + 0x2bbefa9800760000, 0xffff007cfae01dd8, + 0x2bb6fa9800760000, 0xffff007dfadf1de1, + 0x2baefa9700770000, 0xffff007dfadf1dea, + 0x2ba6fa9600770000, 0xffff007dfade1df3, + 0x2b9ffa9600770000, 0xffff007dfadd1dfc, + 0x2b97fa9500770000, 0xffff007dfadc1e05, + 0x2b8ffa9500770000, 0xffff007dfadb1e0e, + 0x2b87fa9400770000, 0xffff007dfadb1e17, + 0x2b7ffa9300770000, 0xffff007dfada1e20, + 0x2b77fa9300780000, 0xffff007dfad91e29, + 0x2b6ffa9200780000, 0xffff007dfad81e32, + 0x2b67fa9200780000, 0xffff007dfad71e3b, + 0x2b5ffa9100780000, 0xffff007dfad71e44, + 0x2b57fa9000780000, 0xffff007efad61e4d, + 0x2b50fa9000780000, 0xffff007efad51e56, + 0x2b48fa8f00780000, 0xffff007efad41e5f, + 0x2b40fa8f00790000, 0xffff007efad31e69, + 0x2b38fa8e00790000, 0xffff007efad31e72, + 0x2b30fa8e00790000, 0xffff007efad21e7b, + 0x2b28fa8d00790000, 0xffff007efad11e84, + 0x2b20fa8d00790000, 0xffff007efad01e8d, + 0x2b18fa8c00790000, 0xffff007efad01e96, + 0x2b10fa8b00790000, 0xffff007efacf1e9f, + 0x2b08fa8b00790000, 0xffff007eface1ea8, + 0x2b00fa8a007a0000, 0xffff007efacd1eb1, + 0x2af8fa8a007a0000, 0xffff007efacd1eba, + 0x2af0fa89007a0000, 0xffff007ffacc1ec3, + 0x2ae8fa89007a0000, 0xffff007ffacb1ecc, + 0x2ae0fa88007a0000, 0xffff007ffaca1ed5, + 0x2ad8fa88007a0000, 0xffff007ffaca1ede, + 0x2ad0fa87007a0000, 0xffff007ffac91ee7, + 0x2ac8fa87007a0000, 0xffff007ffac81ef0, + 0x2ac0fa86007b0000, 0x0000007ffac71ef9, + 0x2ab8fa86007b0000, 0x0000007ffac71f02, + 0x2ab0fa85007b0000, 0x0000007ffac61f0b, + 0x2aa8fa85007b0000, 0x0000007ffac51f14, + 0x2aa0fa84007b0000, 0x0000007ffac41f1d, + 0x2a98fa84007b0000, 0x0000007ffac41f26, + 0x2a90fa84007b0000, 0x0000007ffac31f2f, + 0x2a88fa83007b0000, 0x00000080fac21f38, + 0x2a80fa83007c0000, 0x00000080fac21f42, + 0x2a78fa82007c0000, 0x00000080fac11f4b, + 0x2a70fa82007c0000, 0x00000080fac01f54, + 0x2a68fa81007c0000, 0x00000080fabf1f5d, + 0x2a5ffa81007c0000, 0x00000080fabf1f66, + 0x2a57fa80007c0000, 0x00000080fabe1f6f, + 0x2a4ffa80007c0000, 0x00000080fabd1f78, + 0x2a47fa80007c0000, 0x00000080fabd1f81, + 0x2a3ffa7f007d0000, 0x00000080fabc1f8a, + 0x2a37fa7f007d0000, 0x00000080fabb1f93, + 0x2a2ffa7e007d0000, 0x00000080fabb1f9c, + 0x2a27fa7e007d0000, 0x00000080faba1fa5, + 0x2a1ffa7d007d0000, 0x00000080fab91fae, + 0x2a16fa7d007d0000, 0x00000080fab81fb7, + 0x2a0efa7d007d0000, 0x00000081fab81fc0, + 0x2a06fa7c007d0000, 0x00000081fab71fc9, + 0x29fefa7c007d0000, 0x00000081fab61fd2, + 0x29f6fa7c007d0000, 0x00000081fab61fdb, + 0x29eefa7b007e0000, 0x00000081fab51fe4, + 0x29e6fa7b007e0000, 0x00000081fab41fed, + 0x29ddfa7a007e0000, 0x00000081fab41ff6, + 0x29d5fa7a007e0000, 0x00000081fab31fff, + 0x29cdfa7a007e0000, 0x00000081fab22008, + 0x29c5fa79007e0000, 0x00000081fab22011, + 0x29bdfa79007e0000, 0x00000081fab1201a, + 0x29b5fa79007e0000, 0x00000081fab02023, + 0x29acfa78007e0000, 0x00000081fab0202c, + 0x29a4fa78007e0000, 0x00000081faaf2035, + 0x299cfa78007f0000, 0x00000081faaf203e, + 0x2994fa77007f0000, 0x00000081faae2047, + 0x298cfa77007f0000, 0x00000082faad2050, + 0x2983fa77007f0000, 0x00000082faad2059, + 0x297bfa76007f0000, 0x00000082faac2062, + 0x2973fa76007f0000, 0x00000082faab206b, + 0x296bfa76007f0000, 0x00000082faab2074, + 0x2963fa76007f0000, 0x00000082faaa207d, + 0x295afa75007f0000, 0x00000082faa92086, + 0x2952fa75007f0000, 0x00000082faa9208f, + 0x294afa7500800000, 0x00000082faa82098, + 0x2942fa7400800000, 0x00000082faa820a1, + 0x2939fa7400800000, 0x00000082faa720aa, + 0x2931fa7400800000, 0x00000082faa620b3, + 0x2929fa7400800000, 0x00000082faa620bc, + 0x2920fa7300800000, 0x00000082faa520c5, + 0x2918fa7300800000, 0x00000082faa520ce, + 0x2910fa7300800000, 0x00000082faa420d7, + 0x2908fa7300800000, 0x00000082faa320e0, + 0x28fffa7200800000, 0x00000083faa320e9, + 0x28f7fa7200800000, 0x00000083faa220f2, + 0x28effa7200800000, 0x00000083faa220fb, + 0x28e6fa7200810000, 0x00000083faa12104, + 0x28defa7100810000, 0x00000083faa0210d, + 0x28d6fa7100810000, 0x00000083faa02116, + 0x28cefa7100810000, 0x00000083fa9f211f, + 0x28c5fa7100810000, 0x00000083fa9f2128, + 0x28bdfa7000810000, 0x00000083fa9e2131, + 0x28b5fa7000810000, 0x00000083fa9e213a, + 0x28acfa7000810000, 0x00000083fa9d2143, + 0x28a4fa7000810000, 0x00000083fa9c214c, + 0x289cfa7000810000, 0x00000083fa9c2155, + 0x2893fa6f00810000, 0x00000083fa9b215e, + 0x288bfa6f00810000, 0x00000083fa9b2167, + 0x2883fa6f00810000, 0x00000083fa9a2170, + 0x287afa6f00820000, 0x00000083fa9a2179, + 0x2872fa6f00820000, 0x00000083fa992182, + 0x2869fa6f00820000, 0x00000083fa99218b, + 0x2861fa6e00820000, 0x00000083fa982194, + 0x2859fa6e00820000, 0x00000083fa97219d, + 0x2850fa6e00820000, 0x00000084fa9721a5, + 0x2848fa6e00820000, 0x00000084fa9621ae, + 0x2840fa6e00820000, 0x00000084fa9621b7, + 0x2837fa6e00820000, 0x00000084fa9521c0, + 0x282ffa6d00820000, 0x00000084fa9521c9, + 0x2826fa6d00820000, 0x00000084fa9421d2, + 0x281efa6d00820000, 0x00000084fa9421db, + 0x2816fa6d00820000, 0x00000084fa9321e4, + 0x280dfa6d00820000, 0x00000084fa9321ed, + 0x2805fa6d00820000, 0x00000084fa9221f6, + 0x27fcfa6d00830000, 0x00000084fa9221ff, + 0x27f4fa6d00830000, 0x00000084fa912208, + 0x27ecfa6c00830000, 0x00000084fa912211, + 0x27e3fa6c00830000, 0x00000084fa90221a, + 0x27dbfa6c00830000, 0x00000084fa902223, + 0x27d2fa6c00830000, 0x00000084fa8f222c, + 0x27cafa6c00830000, 0x00000084fa8f2234, + 0x27c1fa6c00830000, 0x00000084fa8e223d, + 0x27b9fa6c00830000, 0x00000084fa8e2246, + 0x27b0fa6c00830000, 0x00000084fa8d224f, + 0x27a8fa6c00830000, 0x00000084fa8d2258, + 0x279ffa6c00830000, 0x00000084fa8c2261, + 0x2797fa6c00830000, 0x00000084fa8c226a, + 0x278ffa6c00830000, 0x00000084fa8b2273, + 0x2786fa6b00830000, 0x00000084fa8b227c, + 0x277efa6b00830000, 0x00000084fa8b2285, + 0x2775fa6b00830000, 0x00000084fa8a228e, + 0x276dfa6b00830000, 0x00000085fa8a2297, + 0x2764fa6b00840000, 0x00000085fa89229f, + 0x275cfa6b00840000, 0x00000085fa8922a8, + 0x2753fa6b00840000, 0x00000085fa8822b1, + 0x274bfa6b00840000, 0x00000085fa8822ba, + 0x2742fa6b00840000, 0x00000085fa8722c3, + 0x273afa6b00840000, 0x00000085fa8722cc, + 0x2731fa6b00840000, 0x00000085fa8722d5, + 0x2729fa6b00840000, 0x00000085fa8622de, + 0x2720fa6b00840000, 0x00000085fa8622e7, + 0x2718fa6b00840000, 0x00000085fa8522f0, + 0x270ffa6b00840000, 0x00000085fa8522f8, + 0x2706fa6b00840000, 0x00000085fa842301, + 0x26fefa6b00840000, 0x00000085fa84230a, + 0x26f5fa6b00840000, 0x00000085fa842313, + 0x26edfa6b00840000, 0x00000085fa83231c, + 0x26e4fa6b00840000, 0x00000085fa832325, + 0x26dcfa6b00840000, 0x00000085fa82232e, + 0x26d3fa6b00840000, 0x00000085fa822337, + 0x26cbfa6b00840000, 0x00000085fa82233f, + 0x26c2fa6b00840000, 0x00000085fa812348, + 0x26bafa6b00840000, 0x00000085fa812351, + 0x26b1fa6b00840000, 0x00000085fa80235a, + 0x26a8fa6b00840000, 0x00000085fa802363, + 0x26a0fa6b00840000, 0x00000085fa80236c, + 0x2697fa6b00850000, 0x00000085fa7f2375, + 0x268ffa6b00850000, 0x00000085fa7f237d, + 0x2686fa6b00850000, 0x00000085fa7f2386, + 0x267dfa6c00850000, 0x00000085fa7e238f, + 0x2675fa6c00850000, 0x00000085fa7e2398, + 0x266cfa6c00850000, 0x00000085fa7d23a1, + 0x2664fa6c00850000, 0x00000085fa7d23aa, + 0x265bfa6c00850000, 0x00000085fa7d23b2, + 0x2652fa6c00850000, 0x00000085fa7c23bb, + 0x264afa6c00850000, 0x00000085fa7c23c4, + 0x2641fa6c00850000, 0x00000085fa7c23cd, + 0x2639fa6c00850000, 0x00000085fa7b23d6, + 0x2630fa6c00850000, 0x00000085fa7b23df, + 0x2627fa6c00850000, 0x00000085fa7b23e7, + 0x261ffa6c00850000, 0x00000085fa7a23f0, + 0x2616fa6d00850000, 0x00000085fa7a23f9, + 0x260dfa6d00850000, 0x00000085fa7a2402, + 0x2605fa6d00850000, 0x00000085fa79240b, + 0x25fcfa6d00850000, 0x00000085fa792413, + 0x25f4fa6d00850000, 0x00000085fa79241c, + 0x25ebfa6d00850000, 0x00000085fa782425, + 0x25e2fa6d00850000, 0x00000085fa78242e, + 0x25dafa6d00850000, 0x00000085fa782437, + 0x25d1fa6e00850000, 0x00000085fa78243f, + 0x25c8fa6e00850000, 0x00000085fa772448, + 0x25c0fa6e00850000, 0x00000085fa772451, + 0x25b7fa6e00850000, 0x00000085fa77245a, + 0x25aefa6e00850000, 0x00000085fa762463, + 0x25a6fa6e00850000, 0x00000085fa76246b, + 0x259dfa6e00850000, 0x00000085fa762474, + 0x2594fa6f00850000, 0x00000085fa76247d, + 0x258cfa6f00850000, 0x00000085fa752486, + 0x2583fa6f00850000, 0x00000085fa75248e, + 0x257afa6f00850000, 0x00000085fa752497, + 0x2572fa6f00850000, 0x00000085fa7424a0, + 0x2569fa6f00850000, 0x00000085fa7424a9, + 0x2560fa7000850000, 0x00000085fa7424b2, + 0x2557fa7000850000, 0x00000085fa7424ba, + 0x254ffa7000850000, 0x00000085fa7324c3, + 0x2546fa7000850000, 0x00000085fa7324cc, + 0x253dfa7000850000, 0x00000085fa7324d5, + 0x2535fa7100850000, 0x00000085fa7324dd, + 0x252cfa7100850000, 0x00000085fa7324e6, + 0x2523fa7100850000, 0x00000085fa7224ef, + 0x251afa7100850000, 0x00000085fa7224f8, + 0x2512fa7100850000, 0x00000085fa722500, + 0x2509fa7200850000, 0x00000085fa722509, + 0x2500fa7200850000, 0x00000085fa712512, + 0x24f8fa7200850000, 0x00000085fa71251a, + 0x24effa7200850000, 0x00000085fa712523, + 0x24e6fa7300850000, 0x00000085fa71252c, + 0x24ddfa7300850000, 0x00000085fa712535, + 0x24d5fa7300850000, 0x00000085fa70253d, + 0x24ccfa7300850000, 0x00000085fa702546, + 0x24c3fa7300850000, 0x00000085fa70254f, + 0x24bafa7400850000, 0x00000085fa702557, + 0x24b2fa7400850000, 0x00000085fa702560, + 0x24a9fa7400850000, 0x00000085fa6f2569, + 0x24a0fa7400850000, 0x00000085fa6f2572, + 0x2497fa7500850000, 0x00000085fa6f257a, + 0x248efa7500850000, 0x00000085fa6f2583, + 0x2486fa7500850000, 0x00000085fa6f258c, + 0x247dfa7600850000, 0x00000085fa6f2594, + 0x2474fa7600850000, 0x00000085fa6e259d, + 0x246bfa7600850000, 0x00000085fa6e25a6, + 0x2463fa7600850000, 0x00000085fa6e25ae, + 0x245afa7700850000, 0x00000085fa6e25b7, + 0x2451fa7700850000, 0x00000085fa6e25c0, + 0x2448fa7700850000, 0x00000085fa6e25c8, + 0x243ffa7800850000, 0x00000085fa6e25d1, + 0x2437fa7800850000, 0x00000085fa6d25da, + 0x242efa7800850000, 0x00000085fa6d25e2, + 0x2425fa7800850000, 0x00000085fa6d25eb, + 0x241cfa7900850000, 0x00000085fa6d25f4, + 0x2413fa7900850000, 0x00000085fa6d25fc, + 0x240bfa7900850000, 0x00000085fa6d2605, + 0x2402fa7a00850000, 0x00000085fa6d260d, + 0x23f9fa7a00850000, 0x00000085fa6d2616, + 0x23f0fa7a00850000, 0x00000085fa6c261f, + 0x23e7fa7b00850000, 0x00000085fa6c2627, + 0x23dffa7b00850000, 0x00000085fa6c2630, + 0x23d6fa7b00850000, 0x00000085fa6c2639, + 0x23cdfa7c00850000, 0x00000085fa6c2641, + 0x23c4fa7c00850000, 0x00000085fa6c264a, + 0x23bbfa7c00850000, 0x00000085fa6c2652, + 0x23b2fa7d00850000, 0x00000085fa6c265b, + 0x23aafa7d00850000, 0x00000085fa6c2664, + 0x23a1fa7d00850000, 0x00000085fa6c266c, + 0x2398fa7e00850000, 0x00000085fa6c2675, + 0x238ffa7e00850000, 0x00000085fa6c267d, + 0x2386fa7f00850000, 0x00000085fa6b2686, + 0x237dfa7f00850000, 0x00000085fa6b268f, + 0x2375fa7f00850000, 0x00000085fa6b2697, + 0x236cfa8000850000, 0x00000084fa6b26a0, + 0x2363fa8000850000, 0x00000084fa6b26a8, + 0x235afa8000850000, 0x00000084fa6b26b1, + 0x2351fa8100850000, 0x00000084fa6b26ba, + 0x2348fa8100850000, 0x00000084fa6b26c2, + 0x233ffa8200850000, 0x00000084fa6b26cb, + 0x2337fa8200850000, 0x00000084fa6b26d3, + 0x232efa8200850000, 0x00000084fa6b26dc, + 0x2325fa8300850000, 0x00000084fa6b26e4, + 0x231cfa8300850000, 0x00000084fa6b26ed, + 0x2313fa8400850000, 0x00000084fa6b26f5, + 0x230afa8400850000, 0x00000084fa6b26fe, + 0x2301fa8400850000, 0x00000084fa6b2706, + 0x22f8fa8500850000, 0x00000084fa6b270f, + 0x22f0fa8500850000, 0x00000084fa6b2718, + 0x22e7fa8600850000, 0x00000084fa6b2720, + 0x22defa8600850000, 0x00000084fa6b2729, + 0x22d5fa8700850000, 0x00000084fa6b2731, + 0x22ccfa8700850000, 0x00000084fa6b273a, + 0x22c3fa8700850000, 0x00000084fa6b2742, + 0x22bafa8800850000, 0x00000084fa6b274b, + 0x22b1fa8800850000, 0x00000084fa6b2753, + 0x22a8fa8900850000, 0x00000084fa6b275c, + 0x229ffa8900850000, 0x00000084fa6b2764, + 0x2297fa8a00850000, 0x00000083fa6b276d, + 0x228efa8a00840000, 0x00000083fa6b2775, + 0x2285fa8b00840000, 0x00000083fa6b277e, + 0x227cfa8b00840000, 0x00000083fa6b2786, + 0x2273fa8b00840000, 0x00000083fa6c278f, + 0x226afa8c00840000, 0x00000083fa6c2797, + 0x2261fa8c00840000, 0x00000083fa6c279f, + 0x2258fa8d00840000, 0x00000083fa6c27a8, + 0x224ffa8d00840000, 0x00000083fa6c27b0, + 0x2246fa8e00840000, 0x00000083fa6c27b9, + 0x223dfa8e00840000, 0x00000083fa6c27c1, + 0x2234fa8f00840000, 0x00000083fa6c27ca, + 0x222cfa8f00840000, 0x00000083fa6c27d2, + 0x2223fa9000840000, 0x00000083fa6c27db, + 0x221afa9000840000, 0x00000083fa6c27e3, + 0x2211fa9100840000, 0x00000083fa6c27ec, + 0x2208fa9100840000, 0x00000083fa6d27f4, + 0x21fffa9200840000, 0x00000083fa6d27fc, + 0x21f6fa9200840000, 0x00000082fa6d2805, + 0x21edfa9300840000, 0x00000082fa6d280d, + 0x21e4fa9300840000, 0x00000082fa6d2816, + 0x21dbfa9400840000, 0x00000082fa6d281e, + 0x21d2fa9400840000, 0x00000082fa6d2826, + 0x21c9fa9500840000, 0x00000082fa6d282f, + 0x21c0fa9500840000, 0x00000082fa6e2837, + 0x21b7fa9600840000, 0x00000082fa6e2840, + 0x21aefa9600840000, 0x00000082fa6e2848, + 0x21a5fa9700840000, 0x00000082fa6e2850, + 0x219dfa9700830000, 0x00000082fa6e2859, + 0x2194fa9800830000, 0x00000082fa6e2861, + 0x218bfa9900830000, 0x00000082fa6f2869, + 0x2182fa9900830000, 0x00000082fa6f2872, + 0x2179fa9a00830000, 0x00000082fa6f287a, + 0x2170fa9a00830000, 0x00000081fa6f2883, + 0x2167fa9b00830000, 0x00000081fa6f288b, + 0x215efa9b00830000, 0x00000081fa6f2893, + 0x2155fa9c00830000, 0x00000081fa70289c, + 0x214cfa9c00830000, 0x00000081fa7028a4, + 0x2143fa9d00830000, 0x00000081fa7028ac, + 0x213afa9e00830000, 0x00000081fa7028b5, + 0x2131fa9e00830000, 0x00000081fa7028bd, + 0x2128fa9f00830000, 0x00000081fa7128c5, + 0x211ffa9f00830000, 0x00000081fa7128ce, + 0x2116faa000830000, 0x00000081fa7128d6, + 0x210dfaa000830000, 0x00000081fa7128de, + 0x2104faa100830000, 0x00000081fa7228e6, + 0x20fbfaa200830000, 0x00000080fa7228ef, + 0x20f2faa200830000, 0x00000080fa7228f7, + 0x20e9faa300830000, 0x00000080fa7228ff, + 0x20e0faa300820000, 0x00000080fa732908, + 0x20d7faa400820000, 0x00000080fa732910, + 0x20cefaa500820000, 0x00000080fa732918, + 0x20c5faa500820000, 0x00000080fa732920, + 0x20bcfaa600820000, 0x00000080fa742929, + 0x20b3faa600820000, 0x00000080fa742931, + 0x20aafaa700820000, 0x00000080fa742939, + 0x20a1faa800820000, 0x00000080fa742942, + 0x2098faa800820000, 0x00000080fa75294a, + 0x208ffaa900820000, 0x0000007ffa752952, + 0x2086faa900820000, 0x0000007ffa75295a, + 0x207dfaaa00820000, 0x0000007ffa762963, + 0x2074faab00820000, 0x0000007ffa76296b, + 0x206bfaab00820000, 0x0000007ffa762973, + 0x2062faac00820000, 0x0000007ffa76297b, + 0x2059faad00820000, 0x0000007ffa772983, + 0x2050faad00820000, 0x0000007ffa77298c, + 0x2047faae00810000, 0x0000007ffa772994, + 0x203efaaf00810000, 0x0000007ffa78299c, + 0x2035faaf00810000, 0x0000007efa7829a4, + 0x202cfab000810000, 0x0000007efa7829ac, + 0x2023fab000810000, 0x0000007efa7929b5, + 0x201afab100810000, 0x0000007efa7929bd, + 0x2011fab200810000, 0x0000007efa7929c5, + 0x2008fab200810000, 0x0000007efa7a29cd, + 0x1ffffab300810000, 0x0000007efa7a29d5, + 0x1ff6fab400810000, 0x0000007efa7a29dd, + 0x1fedfab400810000, 0x0000007efa7b29e6, + 0x1fe4fab500810000, 0x0000007efa7b29ee, + 0x1fdbfab600810000, 0x0000007dfa7c29f6, + 0x1fd2fab600810000, 0x0000007dfa7c29fe, + 0x1fc9fab700810000, 0x0000007dfa7c2a06, + 0x1fc0fab800810000, 0x0000007dfa7d2a0e, + 0x1fb7fab800800000, 0x0000007dfa7d2a16, + 0x1faefab900800000, 0x0000007dfa7d2a1f, + 0x1fa5faba00800000, 0x0000007dfa7e2a27, + 0x1f9cfabb00800000, 0x0000007dfa7e2a2f, + 0x1f93fabb00800000, 0x0000007dfa7f2a37, + 0x1f8afabc00800000, 0x0000007dfa7f2a3f, + 0x1f81fabd00800000, 0x0000007cfa802a47, + 0x1f78fabd00800000, 0x0000007cfa802a4f, + 0x1f6ffabe00800000, 0x0000007cfa802a57, + 0x1f66fabf00800000, 0x0000007cfa812a5f, + 0x1f5dfabf00800000, 0x0000007cfa812a68, + 0x1f54fac000800000, 0x0000007cfa822a70, + 0x1f4bfac100800000, 0x0000007cfa822a78, + 0x1f42fac200800000, 0x0000007cfa832a80, + 0x1f38fac200800000, 0x0000007bfa832a88, + 0x1f2ffac3007f0000, 0x0000007bfa842a90, + 0x1f26fac4007f0000, 0x0000007bfa842a98, + 0x1f1dfac4007f0000, 0x0000007bfa842aa0, + 0x1f14fac5007f0000, 0x0000007bfa852aa8, + 0x1f0bfac6007f0000, 0x0000007bfa852ab0, + 0x1f02fac7007f0000, 0x0000007bfa862ab8, + 0x1ef9fac7007f0000, 0x0000007bfa862ac0, + 0x1ef0fac8007fffff, 0x0000007afa872ac8, + 0x1ee7fac9007fffff, 0x0000007afa872ad0, + 0x1edefaca007fffff, 0x0000007afa882ad8, + 0x1ed5faca007fffff, 0x0000007afa882ae0, + 0x1eccfacb007fffff, 0x0000007afa892ae8, + 0x1ec3facc007fffff, 0x0000007afa892af0, + 0x1ebafacd007effff, 0x0000007afa8a2af8, + 0x1eb1facd007effff, 0x0000007afa8a2b00, + 0x1ea8face007effff, 0x00000079fa8b2b08, + 0x1e9ffacf007effff, 0x00000079fa8b2b10, + 0x1e96fad0007effff, 0x00000079fa8c2b18, + 0x1e8dfad0007effff, 0x00000079fa8d2b20, + 0x1e84fad1007effff, 0x00000079fa8d2b28, + 0x1e7bfad2007effff, 0x00000079fa8e2b30, + 0x1e72fad3007effff, 0x00000079fa8e2b38, + 0x1e69fad3007effff, 0x00000079fa8f2b40, + 0x1e5ffad4007effff, 0x00000078fa8f2b48, + 0x1e56fad5007effff, 0x00000078fa902b50, + 0x1e4dfad6007effff, 0x00000078fa902b57, + 0x1e44fad7007dffff, 0x00000078fa912b5f, + 0x1e3bfad7007dffff, 0x00000078fa922b67, + 0x1e32fad8007dffff, 0x00000078fa922b6f, + 0x1e29fad9007dffff, 0x00000078fa932b77, + 0x1e20fada007dffff, 0x00000077fa932b7f, + 0x1e17fadb007dffff, 0x00000077fa942b87, + 0x1e0efadb007dffff, 0x00000077fa952b8f, + 0x1e05fadc007dffff, 0x00000077fa952b97, + 0x1dfcfadd007dffff, 0x00000077fa962b9f, + 0x1df3fade007dffff, 0x00000077fa962ba6, + 0x1deafadf007dffff, 0x00000077fa972bae, + 0x1de1fadf007dffff, 0x00000076fa982bb6, + 0x1dd8fae0007cffff, 0x00000076fa982bbe, + 0x1dcffae1007cffff, 0x00000076fa992bc6, + 0x1dc6fae2007cffff, 0x00000076fa9a2bce, + 0x1dbdfae3007cffff, 0x00000076fa9a2bd5, + 0x1db3fae3007cffff, 0x00000076fa9b2bdd, + 0x1daafae4007cffff, 0x00000075fa9c2be5, + 0x1da1fae5007cffff, 0x00000075fa9c2bed, + 0x1d98fae6007cffff, 0x00000075fa9d2bf5, + 0x1d8ffae7007cffff, 0x00000075fa9e2bfd, + 0x1d86fae8007cffff, 0x00000075fa9e2c04, + 0x1d7dfae8007cffff, 0x00000075fa9f2c0c, + 0x1d74fae9007cffff, 0x00000075faa02c14, + 0x1d6bfaea007bffff, 0x00000074faa02c1c, + 0x1d62faeb007bffff, 0x00000074faa12c24, + 0x1d59faec007bffff, 0x00000074faa22c2b, + 0x1d50faed007bffff, 0x00000074faa22c33, + 0x1d47faed007bffff, 0x00000074faa32c3b, + 0x1d3efaee007bffff, 0x00000074faa42c43, + 0x1d35faef007bffff, 0x00000073faa52c4a, + 0x1d2cfaf0007bffff, 0x00000073faa52c52, + 0x1d23faf1007bffff, 0x00000073faa62c5a, + 0x1d1afaf2007bffff, 0x00000073faa72c62, + 0x1d10faf3007bffff, 0x00000073faa82c69, + 0x1d07faf3007affff, 0x00000073faa82c71, + 0x1cfefaf4007affff, 0x00000072faa92c79, + 0x1cf5faf5007affff, 0x00000072faaa2c80, + 0x1cecfaf6007affff, 0x00000072faab2c88, + 0x1ce3faf7007affff, 0x00000072faab2c90, + 0x1cdafaf8007affff, 0x00000072faac2c97, + 0x1cd1faf9007affff, 0x00000072faad2c9f, + 0x1cc8faf9007affff, 0x00000071faae2ca7, + 0x1cbffafa007affff, 0x00000071faae2cae, + 0x1cb6fafb007affff, 0x00000071faaf2cb6, + 0x1cadfafc007affff, 0x00000071fab02cbe, + 0x1ca4fafd0079ffff, 0x00000071fab12cc5, + 0x1c9bfafe0079ffff, 0x00000070fab22ccd, + 0x1c92faff0079ffff, 0x00000070fab22cd5, + 0x1c89fb000079ffff, 0x00000070fab32cdc, + 0x1c80fb010079ffff, 0x00000070fab42ce4, + 0x1c77fb010079ffff, 0x00000070fab52cec, + 0x1c6dfb020079ffff, 0x00000070fab62cf3, + 0x1c64fb030079ffff, 0x0000006ffab72cfb, + 0x1c5bfb040079ffff, 0x0000006ffab72d02, + 0x1c52fb050079ffff, 0x0000006ffab82d0a, + 0x1c49fb060078ffff, 0x0000006ffab92d12, + 0x1c40fb070078ffff, 0x0000006ffaba2d19, + 0x1c37fb080078ffff, 0x0000006efabb2d21, + 0x1c2efb090078ffff, 0x0000006efabc2d28, + 0x1c25fb0a0078ffff, 0x0000006efabd2d30, + 0x1c1cfb0b0078ffff, 0x0000006efabd2d38, + 0x1c13fb0b0078ffff, 0x0000006efabe2d3f, + 0x1c0afb0c0078ffff, 0x0000006dfabf2d47, + 0x1c01fb0d0078ffff, 0x0000006dfac02d4e, + 0x1bf8fb0e0078ffff, 0x0000006dfac12d56, + 0x1beffb0f0078ffff, 0x0000006dfac22d5d, + 0x1be6fb100077ffff, 0x0000006dfac32d65, + 0x1bddfb110077ffff, 0x0000006cfac42d6c, + 0x1bd4fb120077ffff, 0x0000006cfac52d74, + 0x1bcbfb130077ffff, 0x0000006cfac62d7b, + 0x1bc2fb140077ffff, 0x0000006cfac72d83, + 0x1bb8fb150077ffff, 0x0000006cfac72d8a, + 0x1baffb160077ffff, 0x0000006bfac82d92, + 0x1ba6fb170077ffff, 0x0000006bfac92d99, + 0x1b9dfb170077ffff, 0x0000006bfaca2da1, + 0x1b94fb180076ffff, 0x0000006bfacb2da8, + 0x1b8bfb190076ffff, 0x0000006bfacc2db0, + 0x1b82fb1a0076ffff, 0x0000006afacd2db7, + 0x1b79fb1b0076ffff, 0x0000006aface2dbf, + 0x1b70fb1c0076ffff, 0x0000006afacf2dc6, + 0x1b67fb1d0076ffff, 0x0000006afad02dcd, + 0x1b5efb1e0076ffff, 0x0000006afad12dd5, + 0x1b55fb1f0076ffff, 0x00000069fad22ddc, + 0x1b4cfb200076ffff, 0x00000069fad32de4, + 0x1b43fb210076ffff, 0x00000069fad42deb, + 0x1b3afb220075ffff, 0x00000069fad52df2, + 0x1b31fb230075ffff, 0x00000069fad62dfa, + 0x1b28fb240075fffe, 0x00000068fad72e01, + 0x1b1ffb250075fffe, 0x00000068fad82e09, + 0x1b16fb260075fffe, 0x00000068fad92e10, + 0x1b0dfb270075fffe, 0x00000068fada2e17, + 0x1b04fb280075fffe, 0x00000067fadb2e1f, + 0x1afbfb290075fffe, 0x00000067fadc2e26, + 0x1af2fb2a0075fffe, 0x00000067fadd2e2d, + 0x1ae9fb2b0074fffe, 0x00000067fade2e35, + 0x1ae0fb2c0074fffe, 0x00000067fadf2e3c, + 0x1ad7fb2d0074fffe, 0x00000066fae02e43, + 0x1acefb2e0074fffe, 0x00000066fae22e4b, + 0x1ac4fb2f0074fffe, 0x00000066fae32e52, + 0x1abbfb300074fffe, 0x00000066fae42e59, + 0x1ab2fb310074fffe, 0x00000065fae52e61, + 0x1aa9fb320074fffe, 0x00000065fae62e68, + 0x1aa0fb330074fffe, 0x00000065fae72e6f, + 0x1a97fb340074fffe, 0x00000065fae82e77, + 0x1a8efb350073fffe, 0x00000065fae92e7e, + 0x1a85fb360073fffe, 0x00000064faea2e85, + 0x1a7cfb370073fffe, 0x00000064faeb2e8c, + 0x1a73fb380073fffe, 0x00000064faed2e94, + 0x1a6afb390073fffe, 0x00000064faee2e9b, + 0x1a61fb3a0073fffe, 0x00000063faef2ea2, + 0x1a58fb3b0073fffe, 0x00000063faf02ea9, + 0x1a4ffb3c0073fffe, 0x00000063faf12eb1, + 0x1a46fb3d0073fffe, 0x00000063faf22eb8, + 0x1a3dfb3e0072fffe, 0x00000062faf32ebf, + 0x1a34fb3f0072fffe, 0x00000062faf52ec6, + 0x1a2bfb400072fffe, 0x00000062faf62ecd, + 0x1a22fb410072fffe, 0x00000062faf72ed5, + 0x1a19fb420072fffe, 0x00000061faf82edc, + 0x1a10fb430072fffe, 0x00000061faf92ee3, + 0x1a07fb440072fffe, 0x00000061fafa2eea, + 0x19fefb450072fffe, 0x00000061fafc2ef1, + 0x19f5fb460072fffe, 0x00000060fafd2ef8, + 0x19ecfb470071fffe, 0x00000060fafe2f00, + 0x19e3fb480071fffe, 0x00000060faff2f07, + 0x19dafb490071fffe, 0x00000060fb002f0e, + 0x19d1fb4a0071fffe, 0x0000005ffb022f15, + 0x19c8fb4b0071fffe, 0x0000005ffb032f1c, + 0x19bffb4c0071fffe, 0x0000005ffb042f23, + 0x19b6fb4d0071fffe, 0x0000005ffb052f2a, + 0x19adfb4e0071fffe, 0x0000005efb072f32, + 0x19a4fb4f0071fffe, 0x0000005efb082f39, + 0x199bfb500070fffe, 0x0000005efb092f40, + 0x1992fb510070fffe, 0x0000005efb0a2f47, + 0x1989fb520070fffe, 0x0000005dfb0c2f4e, + 0x1980fb530070fffe, 0x0000005dfb0d2f55, + 0x1977fb540070fffe, 0x0000005dfb0e2f5c, + 0x196efb550070fffe, 0x0000005dfb0f2f63, + 0x1965fb560070fffe, 0x0000005cfb112f6a, + 0x195cfb570070fffe, 0x0000005cfb122f71, + 0x1953fb58006ffffe, 0x0000005cfb132f78, + 0x194afb59006ffffe, 0x0000005cfb152f7f, + 0x1941fb5a006ffffe, 0x0000005bfb162f86, + 0x1938fb5c006ffffe, 0x0000005bfb172f8d, + 0x192ffb5d006ffffe, 0x0000005bfb192f94, + 0x1926fb5e006ffffe, 0x0000005afb1a2f9b, + 0x191dfb5f006ffffe, 0x0000005afb1b2fa2, + 0x1914fb60006ffffe, 0x0000005afb1c2fa9, + 0x190bfb61006ffffe, 0x0000005afb1e2fb0, + 0x1902fb62006efffe, 0x00000059fb1f2fb7, + 0x18f9fb63006efffe, 0x00000059fb212fbe, + 0x18f1fb64006efffe, 0x00000059fb222fc5, + 0x18e8fb65006efffe, 0x00000059fb232fcc, + 0x18dffb66006efffe, 0x00000058fb252fd3, + 0x18d6fb67006efffe, 0x00000058fb262fda, + 0x18cdfb68006efffe, 0x00000058fb272fe1, + 0x18c4fb69006efffe, 0x00000057fb292fe8, + 0x18bbfb6a006dfffe, 0x00000057fb2a2fef, + 0x18b2fb6b006dfffd, 0x00000057fb2c2ff5, + 0x18a9fb6d006dfffd, 0x00000057fb2d2ffc, + 0x18a0fb6e006dfffd, 0x00000056fb2e3003, + 0x1897fb6f006dfffd, 0x00000056fb30300a, + 0x188efb70006dfffd, 0x00000056fb313011, + 0x1885fb71006dfffd, 0x00000055fb333018, + 0x187cfb72006dfffd, 0x00000055fb34301f, + 0x1873fb73006dfffd, 0x00000055fb353026, + 0x186afb74006cfffd, 0x00000055fb37302c, + 0x1861fb75006cfffd, 0x00000054fb383033, + 0x1858fb76006cfffd, 0x00000054fb3a303a, + 0x184ffb77006cfffd, 0x00000054fb3b3041, + 0x1846fb79006cfffd, 0x00000053fb3d3048, + 0x183dfb7a006cfffd, 0x00000053fb3e304f, + 0x1835fb7b006cfffd, 0x00000053fb403055, + 0x182cfb7c006cfffd, 0x00000052fb41305c, + 0x1823fb7d006bfffd, 0x00000052fb433063, + 0x181afb7e006bfffd, 0x00000052fb44306a, + 0x1811fb7f006bfffd, 0x00000052fb463070, + 0x1808fb80006bfffd, 0x00000051fb473077, + 0x17fffb81006bfffd, 0x00000051fb49307e, + 0x17f6fb82006bfffd, 0x00000051fb4a3085, + 0x17edfb84006bfffd, 0x00000050fb4c308b, + 0x17e4fb85006bfffd, 0x00000050fb4d3092, + 0x17dbfb86006afffd, 0x00000050fb4f3099, + 0x17d2fb87006afffd, 0x0000004ffb5030a0, + 0x17c9fb88006afffd, 0x0000004ffb5230a6, + 0x17c1fb89006afffd, 0x0000004ffb5330ad, + 0x17b8fb8a006afffd, 0x0000004efb5530b4, + 0x17affb8b006afffd, 0x0000004efb5630ba, + 0x17a6fb8c006afffd, 0x0000004efb5830c1, + 0x179dfb8e006afffd, 0x0000004efb5a30c8, + 0x1794fb8f0069fffd, 0x0000004dfb5b30ce, + 0x178bfb900069fffd, 0x0000004dfb5d30d5, + 0x1782fb910069fffd, 0x0000004dfb5e30dc, + 0x1779fb920069fffd, 0x0000004cfb6030e2, + 0x1770fb930069fffd, 0x0000004cfb6230e9, + 0x1768fb940069fffd, 0x0000004cfb6330f0, + 0x175ffb950069fffd, 0x0000004bfb6530f6, + 0x1756fb960069fffd, 0x0000004bfb6630fd, + 0x174dfb980068fffd, 0x0000004bfb683103, + 0x1744fb990068fffd, 0x0000004afb6a310a, + 0x173bfb9a0068fffd, 0x0000004afb6b3110, + 0x1732fb9b0068fffd, 0x0000004afb6d3117, + 0x1729fb9c0068fffd, 0x00000049fb6f311e, + 0x1721fb9d0068fffd, 0x00000049fb703124, + 0x1718fb9e0068fffd, 0x00000049fb72312b, + 0x170ffba00068fffd, 0x00000048fb733131, + 0x1706fba10067fffd, 0x00000048fb753138, + 0x16fdfba20067fffd, 0x00000048fb77313e, + 0x16f4fba30067fffd, 0x00000047fb793145, + 0x16ebfba40067fffd, 0x00000047fb7a314b, + 0x16e2fba50067fffd, 0x00000047fb7c3152, + 0x16dafba60067fffd, 0x00000046fb7e3158, + 0x16d1fba80067fffd, 0x00000046fb7f315f, + 0x16c8fba90067fffc, 0x00000046fb813165, + 0x16bffbaa0066fffc, 0x00000045fb83316c, + 0x16b6fbab0066fffc, 0x00000045fb843172, + 0x16adfbac0066fffc, 0x00000045fb863179, + 0x16a4fbad0066fffc, 0x00000044fb88317f, + 0x169cfbae0066fffc, 0x00000044fb8a3186, + 0x1693fbb00066fffc, 0x00000044fb8b318c, + 0x168afbb10066fffc, 0x00000043fb8d3192, + 0x1681fbb20066fffc, 0x00000043fb8f3199, + 0x1678fbb30065fffc, 0x00000043fb91319f, + 0x166ffbb40065fffc, 0x00010042fb9231a6, + 0x1667fbb50065fffc, 0x00010042fb9431ac, + 0x165efbb70065fffc, 0x00010042fb9631b2, + 0x1655fbb80065fffc, 0x00010041fb9831b9, + 0x164cfbb90065fffc, 0x00010041fb9a31bf, + 0x1643fbba0065fffc, 0x00010040fb9b31c6, + 0x163bfbbb0064fffc, 0x00010040fb9d31cc, + 0x1632fbbc0064fffc, 0x00010040fb9f31d2, + 0x1629fbbd0064fffc, 0x0001003ffba131d9, + 0x1620fbbf0064fffc, 0x0001003ffba331df, + 0x1617fbc00064fffc, 0x0001003ffba431e5, + 0x160efbc10064fffc, 0x0001003efba631ec, + 0x1606fbc20064fffc, 0x0001003efba831f2, + 0x15fdfbc30064fffc, 0x0001003efbaa31f8, + 0x15f4fbc40063fffc, 0x0001003dfbac31fe, + 0x15ebfbc60063fffc, 0x0001003dfbae3205, + 0x15e2fbc70063fffc, 0x0001003cfbb0320b, + 0x15dafbc80063fffc, 0x0001003cfbb13211, + 0x15d1fbc90063fffc, 0x0001003cfbb33217, + 0x15c8fbca0063fffc, 0x0001003bfbb5321e, + 0x15bffbcc0063fffc, 0x0001003bfbb73224, + 0x15b7fbcd0063fffc, 0x0001003bfbb9322a, + 0x15aefbce0062fffc, 0x0001003afbbb3230, + 0x15a5fbcf0062fffc, 0x0001003afbbd3237, + 0x159cfbd00062fffc, 0x00010039fbbf323d, + 0x1593fbd10062fffc, 0x00010039fbc13243, + 0x158bfbd30062fffc, 0x00010039fbc33249, + 0x1582fbd40062fffc, 0x00010038fbc4324f, + 0x1579fbd50062fffc, 0x00010038fbc63255, + 0x1570fbd60062fffc, 0x00010038fbc8325c, + 0x1568fbd70061fffc, 0x00010037fbca3262, + 0x155ffbd90061fffc, 0x00010037fbcc3268, + 0x1556fbda0061fffc, 0x00010036fbce326e, + 0x154dfbdb0061fffc, 0x00010036fbd03274, + 0x1545fbdc0061fffc, 0x00010036fbd2327a, + 0x153cfbdd0061fffc, 0x00010035fbd43280, + 0x1533fbde0061fffc, 0x00010035fbd63286, + 0x152afbe00060fffb, 0x00010034fbd8328d, + 0x1522fbe10060fffb, 0x00010034fbda3293, + 0x1519fbe20060fffb, 0x00010034fbdc3299, + 0x1510fbe30060fffb, 0x00010033fbde329f, + 0x1507fbe40060fffb, 0x00010033fbe032a5, + 0x14fffbe60060fffb, 0x00010032fbe232ab, + 0x14f6fbe70060fffb, 0x00010032fbe432b1, + 0x14edfbe80060fffb, 0x00010032fbe632b7, + 0x14e5fbe9005ffffb, 0x00010031fbe832bd, + 0x14dcfbea005ffffb, 0x00010031fbea32c3, + 0x14d3fbec005ffffb, 0x00010030fbec32c9, + 0x14cafbed005ffffb, 0x00010030fbee32cf, + 0x14c2fbee005ffffb, 0x00010030fbf032d5, + 0x14b9fbef005ffffb, 0x0001002ffbf332db, + 0x14b0fbf0005ffffb, 0x0001002ffbf532e1, + 0x14a8fbf2005efffb, 0x0001002efbf732e7, + 0x149ffbf3005efffb, 0x0001002efbf932ed, + 0x1496fbf4005efffb, 0x0001002efbfb32f3, + 0x148efbf5005efffb, 0x0001002dfbfd32f9, + 0x1485fbf6005efffb, 0x0001002dfbff32ff, + 0x147cfbf8005efffb, 0x0001002cfc013305, + 0x1473fbf9005efffb, 0x0001002cfc03330b, + 0x146bfbfa005efffb, 0x0001002cfc053310, + 0x1462fbfb005dfffb, 0x0001002bfc083316, + 0x1459fbfd005dfffb, 0x0001002bfc0a331c, + 0x1451fbfe005dfffb, 0x0001002afc0c3322, + 0x1448fbff005dfffb, 0x0001002afc0e3328, + 0x143ffc00005dfffb, 0x00010029fc10332e, + 0x1437fc01005dfffb, 0x00010029fc123334, + 0x142efc03005dfffb, 0x00010029fc143339, + 0x1425fc04005cfffb, 0x00010028fc17333f, + 0x141dfc05005cfffb, 0x00010028fc193345, + 0x1414fc06005cfffb, 0x00010027fc1b334b, + 0x140cfc07005cfffb, 0x00010027fc1d3351, + 0x1403fc09005cfffb, 0x00010027fc1f3357, + 0x13fafc0a005cfffb, 0x00010026fc22335c, + 0x13f2fc0b005cfffb, 0x00010026fc243362, + 0x13e9fc0c005cfffb, 0x00010025fc263368, + 0x13e0fc0e005bfffb, 0x00010025fc28336e, + 0x13d8fc0f005bfffb, 0x00010024fc2b3373, + 0x13cffc10005bfffb, 0x00010024fc2d3379, + 0x13c6fc11005bfffb, 0x00010023fc2f337f, + 0x13befc12005bfffa, 0x00010023fc313385, + 0x13b5fc14005bfffa, 0x00010023fc34338a, + 0x13adfc15005bfffa, 0x00010022fc363390, + 0x13a4fc16005afffa, 0x00020022fc383396, + 0x139bfc17005afffa, 0x00020021fc3a339b, + 0x1393fc19005afffa, 0x00020021fc3d33a1, + 0x138afc1a005afffa, 0x00020020fc3f33a7, + 0x1382fc1b005afffa, 0x00020020fc4133ac, + 0x1379fc1c005afffa, 0x0002001ffc4333b2, + 0x1370fc1e005afffa, 0x0002001ffc4633b8, + 0x1368fc1f005afffa, 0x0002001ffc4833bd, + 0x135ffc200059fffa, 0x0002001efc4a33c3, + 0x1357fc210059fffa, 0x0002001efc4d33c9, + 0x134efc220059fffa, 0x0002001dfc4f33ce, + 0x1346fc240059fffa, 0x0002001dfc5133d4, + 0x133dfc250059fffa, 0x0002001cfc5433d9, + 0x1334fc260059fffa, 0x0002001cfc5633df, + 0x132cfc270059fffa, 0x0002001bfc5833e5, + 0x1323fc290058fffa, 0x0002001bfc5b33ea, + 0x131bfc2a0058fffa, 0x0002001afc5d33f0, + 0x1312fc2b0058fffa, 0x0002001afc6033f5, + 0x130afc2c0058fffa, 0x0002001afc6233fb, + 0x1301fc2e0058fffa, 0x00020019fc643400, + 0x12f9fc2f0058fffa, 0x00020019fc673406, + 0x12f0fc300058fffa, 0x00020018fc69340b, + 0x12e7fc310057fffa, 0x00020018fc6c3411, + 0x12dffc330057fffa, 0x00020017fc6e3416, + 0x12d6fc340057fffa, 0x00020017fc70341c, + 0x12cefc350057fffa, 0x00020016fc733421, + 0x12c5fc360057fffa, 0x00020016fc753427, + 0x12bdfc370057fffa, 0x00020015fc78342c, + 0x12b4fc390057fffa, 0x00020015fc7a3432, + 0x12acfc3a0057fffa, 0x00020014fc7d3437, + 0x12a3fc3b0056fffa, 0x00020014fc7f343c, + 0x129bfc3c0056fffa, 0x00020013fc823442, + 0x1292fc3e0056fffa, 0x00020013fc843447, + 0x128afc3f0056fffa, 0x00020013fc86344d, + 0x1281fc400056fffa, 0x00020012fc893452, + 0x1279fc410056fff9, 0x00020012fc8b3457, + 0x1270fc430056fff9, 0x00020011fc8e345d, + 0x1268fc440055fff9, 0x00020011fc903462, + 0x125ffc450055fff9, 0x00020010fc933468, + 0x1257fc460055fff9, 0x00020010fc95346d, + 0x124efc480055fff9, 0x0002000ffc983472, + 0x1246fc490055fff9, 0x0002000ffc9b3478, + 0x123dfc4a0055fff9, 0x0002000efc9d347d, + 0x1235fc4b0055fff9, 0x0002000efca03482, + 0x122dfc4d0054fff9, 0x0002000dfca23487, + 0x1224fc4e0054fff9, 0x0002000dfca5348d, + 0x121cfc4f0054fff9, 0x0002000cfca73492, + 0x1213fc500054fff9, 0x0002000cfcaa3497, + 0x120bfc520054fff9, 0x0002000bfcac349d, + 0x1202fc530054fff9, 0x0002000bfcaf34a2, + 0x11fafc540054fff9, 0x0002000afcb234a7, + 0x11f1fc550054fff9, 0x0002000afcb434ac, + 0x11e9fc570053fff9, 0x00020009fcb734b1, + 0x11e1fc580053fff9, 0x00020009fcb934b7, + 0x11d8fc590053fff9, 0x00030008fcbc34bc, + 0x11d0fc5a0053fff9, 0x00030008fcbf34c1, + 0x11c7fc5c0053fff9, 0x00030007fcc134c6, + 0x11bffc5d0053fff9, 0x00030007fcc434cb, + 0x11b7fc5e0053fff9, 0x00030006fcc734d1, + 0x11aefc600052fff9, 0x00030006fcc934d6, + 0x11a6fc610052fff9, 0x00030005fccc34db, + 0x119dfc620052fff9, 0x00030005fcce34e0, + 0x1195fc630052fff9, 0x00030004fcd134e5, + 0x118dfc650052fff9, 0x00030004fcd434ea, + 0x1184fc660052fff9, 0x00030003fcd634ef, + 0x117cfc670052fff9, 0x00030003fcd934f4, + 0x1173fc680052fff9, 0x00030002fcdc34fa, + 0x116bfc6a0051fff9, 0x00030002fcdf34ff, + 0x1163fc6b0051fff9, 0x00030001fce13504, + 0x115afc6c0051fff9, 0x00030000fce43509, + 0x1152fc6d0051fff8, 0x00030000fce7350e, + 0x114afc6f0051fff8, 0x0003fffffce93513, + 0x1141fc700051fff8, 0x0003fffffcec3518, + 0x1139fc710051fff8, 0x0003fffefcef351d, + 0x1130fc720050fff8, 0x0003fffefcf23522, + 0x1128fc740050fff8, 0x0003fffdfcf43527, + 0x1120fc750050fff8, 0x0003fffdfcf7352c, + 0x1117fc760050fff8, 0x0003fffcfcfa3531, + 0x110ffc770050fff8, 0x0003fffcfcfd3536, + 0x1107fc790050fff8, 0x0003fffbfcff353b, + 0x10fefc7a0050fff8, 0x0003fffbfd023540, + 0x10f6fc7b004ffff8, 0x0003fffafd053545, + 0x10eefc7d004ffff8, 0x0003fffafd08354a, + 0x10e6fc7e004ffff8, 0x0003fff9fd0b354f, + 0x10ddfc7f004ffff8, 0x0003fff8fd0d3553, + 0x10d5fc80004ffff8, 0x0003fff8fd103558, + 0x10cdfc82004ffff8, 0x0003fff7fd13355d, + 0x10c4fc83004ffff8, 0x0003fff7fd163562, + 0x10bcfc84004ffff8, 0x0003fff6fd193567, + 0x10b4fc85004efff8, 0x0003fff6fd1c356c, + 0x10abfc87004efff8, 0x0003fff5fd1e3571, + 0x10a3fc88004efff8, 0x0003fff5fd213576, + 0x109bfc89004efff8, 0x0003fff4fd24357a, + 0x1093fc8a004efff8, 0x0003fff4fd27357f, + 0x108afc8c004efff8, 0x0003fff3fd2a3584, + 0x1082fc8d004efff8, 0x0003fff2fd2d3589, + 0x107afc8e004dfff8, 0x0003fff2fd30358e, + 0x1072fc90004dfff8, 0x0004fff1fd333592, + 0x1069fc91004dfff8, 0x0004fff1fd353597, + 0x1061fc92004dfff8, 0x0004fff0fd38359c, + 0x1059fc93004dfff8, 0x0004fff0fd3b35a1, + 0x1051fc95004dfff8, 0x0004ffeffd3e35a5, + 0x1048fc96004dfff8, 0x0004ffeffd4135aa, + 0x1040fc97004cfff7, 0x0004ffeefd4435af, + 0x1038fc98004cfff7, 0x0004ffedfd4735b4, + 0x1030fc9a004cfff7, 0x0004ffedfd4a35b8, + 0x1028fc9b004cfff7, 0x0004ffecfd4d35bd, + 0x101ffc9c004cfff7, 0x0004ffecfd5035c2, + 0x1017fc9d004cfff7, 0x0004ffebfd5335c6, + 0x100ffc9f004cfff7, 0x0004ffebfd5635cb, + 0x1007fca0004cfff7, 0x0004ffeafd5935d0, + 0x0ffefca1004bfff7, 0x0004ffe9fd5c35d4, + 0x0ff6fca3004bfff7, 0x0004ffe9fd5f35d9, + 0x0feefca4004bfff7, 0x0004ffe8fd6235de, + 0x0fe6fca5004bfff7, 0x0004ffe8fd6535e2, + 0x0fdefca6004bfff7, 0x0004ffe7fd6835e7, + 0x0fd6fca8004bfff7, 0x0004ffe7fd6b35eb, + 0x0fcdfca9004bfff7, 0x0004ffe6fd6e35f0, + 0x0fc5fcaa004afff7, 0x0004ffe5fd7135f5, + 0x0fbdfcab004afff7, 0x0004ffe5fd7435f9, + 0x0fb5fcad004afff7, 0x0004ffe4fd7735fe, + 0x0fadfcae004afff7, 0x0004ffe4fd7a3602, + 0x0fa5fcaf004afff7, 0x0004ffe3fd7d3607, + 0x0f9cfcb1004afff7, 0x0004ffe3fd80360b, + 0x0f94fcb2004afff7, 0x0004ffe2fd833610, + 0x0f8cfcb3004afff7, 0x0004ffe1fd863614, + 0x0f84fcb40049fff7, 0x0004ffe1fd893619, + 0x0f7cfcb60049fff7, 0x0004ffe0fd8d361d, + 0x0f74fcb70049fff7, 0x0004ffe0fd903622, + 0x0f6cfcb80049fff7, 0x0004ffdffd933626, + 0x0f64fcb90049fff7, 0x0004ffdefd96362b, + 0x0f5bfcbb0049fff7, 0x0004ffdefd99362f, + 0x0f53fcbc0049fff7, 0x0005ffddfd9c3634, + 0x0f4bfcbd0048fff7, 0x0005ffddfd9f3638, + 0x0f43fcbf0048fff6, 0x0005ffdcfda2363d, + 0x0f3bfcc00048fff6, 0x0005ffdbfda63641, + 0x0f33fcc10048fff6, 0x0005ffdbfda93645, + 0x0f2bfcc20048fff6, 0x0005ffdafdac364a, + 0x0f23fcc40048fff6, 0x0005ffdafdaf364e, + 0x0f1bfcc50048fff6, 0x0005ffd9fdb23652, + 0x0f13fcc60048fff6, 0x0005ffd8fdb53657, + 0x0f0bfcc70047fff6, 0x0005ffd8fdb9365b, + 0x0f03fcc90047fff6, 0x0005ffd7fdbc3660, + 0x0efafcca0047fff6, 0x0005ffd7fdbf3664, + 0x0ef2fccb0047fff6, 0x0005ffd6fdc23668, + 0x0eeafccd0047fff6, 0x0005ffd5fdc5366c, + 0x0ee2fcce0047fff6, 0x0005ffd5fdc93671, + 0x0edafccf0047fff6, 0x0005ffd4fdcc3675, + 0x0ed2fcd00046fff6, 0x0005ffd3fdcf3679, + 0x0ecafcd20046fff6, 0x0005ffd3fdd2367e, + 0x0ec2fcd30046fff6, 0x0005ffd2fdd63682, + 0x0ebafcd40046fff6, 0x0005ffd2fdd93686, + 0x0eb2fcd60046fff6, 0x0005ffd1fddc368a, + 0x0eaafcd70046fff6, 0x0005ffd0fddf368f, + 0x0ea2fcd80046fff6, 0x0005ffd0fde33693, + 0x0e9afcd90046fff6, 0x0005ffcffde63697, + 0x0e92fcdb0045fff6, 0x0005ffcefde9369b, + 0x0e8afcdc0045fff6, 0x0005ffcefded369f, + 0x0e82fcdd0045fff6, 0x0005ffcdfdf036a3, + 0x0e7afcde0045fff6, 0x0005ffcdfdf336a8, + 0x0e72fce00045fff6, 0x0005ffccfdf636ac, + 0x0e6afce10045fff6, 0x0005ffcbfdfa36b0, + 0x0e62fce20045fff6, 0x0005ffcbfdfd36b4, + 0x0e5afce40044fff6, 0x0005ffcafe0036b8, + 0x0e52fce50044fff5, 0x0006ffc9fe0436bc, + 0x0e4afce60044fff5, 0x0006ffc9fe0736c0, + 0x0e42fce70044fff5, 0x0006ffc8fe0b36c4, + 0x0e3afce90044fff5, 0x0006ffc7fe0e36c9, + 0x0e33fcea0044fff5, 0x0006ffc7fe1136cd, + 0x0e2bfceb0044fff5, 0x0006ffc6fe1536d1, + 0x0e23fcec0044fff5, 0x0006ffc6fe1836d5, + 0x0e1bfcee0043fff5, 0x0006ffc5fe1b36d9, + 0x0e13fcef0043fff5, 0x0006ffc4fe1f36dd, + 0x0e0bfcf00043fff5, 0x0006ffc4fe2236e1, + 0x0e03fcf20043fff5, 0x0006ffc3fe2636e5, + 0x0dfbfcf30043fff5, 0x0006ffc2fe2936e9, + 0x0df3fcf40043fff5, 0x0006ffc2fe2c36ed, + 0x0debfcf50043fff5, 0x0006ffc1fe3036f1, + 0x0de3fcf70042fff5, 0x0006ffc0fe3336f5, + 0x0ddbfcf80042fff5, 0x0006ffc0fe3736f9, + 0x0dd4fcf90042fff5, 0x0006ffbffe3a36fd, + 0x0dccfcfa0042fff5, 0x0006ffbefe3e3701, + 0x0dc4fcfc0042fff5, 0x0006ffbefe413705, + 0x0dbcfcfd0042fff5, 0x0006ffbdfe453708, + 0x0db4fcfe0042fff5, 0x0006ffbcfe48370c, + 0x0dacfd000042fff5, 0x0006ffbcfe4c3710, + 0x0da4fd010041fff5, 0x0006ffbbfe4f3714, + 0x0d9dfd020041fff5, 0x0006ffbafe533718, + 0x0d95fd030041fff5, 0x0006ffbafe56371c, + 0x0d8dfd050041fff5, 0x0006ffb9fe5a3720, + 0x0d85fd060041fff5, 0x0006ffb8fe5d3724, + 0x0d7dfd070041fff5, 0x0006ffb8fe613727, + 0x0d75fd080041fff5, 0x0007ffb7fe64372b, + 0x0d6efd0a0040fff4, 0x0007ffb6fe68372f, + 0x0d66fd0b0040fff4, 0x0007ffb6fe6b3733, + 0x0d5efd0c0040fff4, 0x0007ffb5fe6f3737, + 0x0d56fd0e0040fff4, 0x0007ffb4fe72373a, + 0x0d4efd0f0040fff4, 0x0007ffb4fe76373e, + 0x0d47fd100040fff4, 0x0007ffb3fe7a3742, + 0x0d3ffd110040fff4, 0x0007ffb2fe7d3746, + 0x0d37fd130040fff4, 0x0007ffb2fe813749, + 0x0d2ffd14003ffff4, 0x0007ffb1fe84374d, + 0x0d27fd15003ffff4, 0x0007ffb0fe883751, + 0x0d20fd16003ffff4, 0x0007ffb0fe8c3754, + 0x0d18fd18003ffff4, 0x0007ffaffe8f3758, + 0x0d10fd19003ffff4, 0x0007ffaefe93375c, + 0x0d08fd1a003ffff4, 0x0007ffaefe96375f, + 0x0d01fd1c003ffff4, 0x0007ffadfe9a3763, + 0x0cf9fd1d003ffff4, 0x0007ffacfe9e3767, + 0x0cf1fd1e003efff4, 0x0007ffabfea1376a, + 0x0ce9fd1f003efff4, 0x0007ffabfea5376e, + 0x0ce2fd21003efff4, 0x0007ffaafea93772, + 0x0cdafd22003efff4, 0x0007ffa9feac3775, + 0x0cd2fd23003efff4, 0x0007ffa9feb03779, + 0x0ccafd24003efff4, 0x0007ffa8feb4377c, + 0x0cc3fd26003efff4, 0x0007ffa7feb73780, + 0x0cbbfd27003efff4, 0x0007ffa7febb3784, + 0x0cb3fd28003dfff4, 0x0008ffa6febf3787, + 0x0cacfd29003dfff4, 0x0008ffa5fec3378b, + 0x0ca4fd2b003dfff4, 0x0008ffa5fec6378e, + 0x0c9cfd2c003dfff4, 0x0008ffa4feca3792, + 0x0c95fd2d003dfff3, 0x0008ffa3fece3795, + 0x0c8dfd2f003dfff3, 0x0008ffa2fed23799, + 0x0c85fd30003dfff3, 0x0008ffa2fed5379c, + 0x0c7efd31003cfff3, 0x0008ffa1fed937a0, + 0x0c76fd32003cfff3, 0x0008ffa0fedd37a3, + 0x0c6efd34003cfff3, 0x0008ffa0fee137a7, + 0x0c67fd35003cfff3, 0x0008ff9ffee437aa, + 0x0c5ffd36003cfff3, 0x0008ff9efee837ad, + 0x0c57fd37003cfff3, 0x0008ff9dfeec37b1, + 0x0c50fd39003cfff3, 0x0008ff9dfef037b4, + 0x0c48fd3a003cfff3, 0x0008ff9cfef437b8, + 0x0c40fd3b003bfff3, 0x0008ff9bfef737bb, + 0x0c39fd3c003bfff3, 0x0008ff9bfefb37be, + 0x0c31fd3e003bfff3, 0x0008ff9afeff37c2, + 0x0c2afd3f003bfff3, 0x0008ff99ff0337c5, + 0x0c22fd40003bfff3, 0x0008ff98ff0737c9, + 0x0c1afd41003bfff3, 0x0008ff98ff0b37cc, + 0x0c13fd43003bfff3, 0x0008ff97ff0e37cf, + 0x0c0bfd44003bfff3, 0x0008ff96ff1237d3, + 0x0c04fd45003afff3, 0x0008ff95ff1637d6, + 0x0bfcfd47003afff3, 0x0009ff95ff1a37d9, + 0x0bf4fd48003afff3, 0x0009ff94ff1e37dc, + 0x0bedfd49003afff3, 0x0009ff93ff2237e0, + 0x0be5fd4a003afff3, 0x0009ff92ff2637e3, + 0x0bdefd4c003afff3, 0x0009ff92ff2a37e6, + 0x0bd6fd4d003afff3, 0x0009ff91ff2d37e9, + 0x0bcffd4e003afff3, 0x0009ff90ff3137ed, + 0x0bc7fd4f0039fff3, 0x0009ff90ff3537f0, + 0x0bc0fd510039fff2, 0x0009ff8fff3937f3, + 0x0bb8fd520039fff2, 0x0009ff8eff3d37f6, + 0x0bb1fd530039fff2, 0x0009ff8dff4137f9, + 0x0ba9fd540039fff2, 0x0009ff8dff4537fd, + 0x0ba2fd560039fff2, 0x0009ff8cff493800, + 0x0b9afd570039fff2, 0x0009ff8bff4d3803, + 0x0b93fd580039fff2, 0x0009ff8aff513806, + 0x0b8bfd590038fff2, 0x0009ff8aff553809, + 0x0b84fd5b0038fff2, 0x0009ff89ff59380c, + 0x0b7cfd5c0038fff2, 0x0009ff88ff5d380f, + 0x0b75fd5d0038fff2, 0x0009ff87ff613813, + 0x0b6dfd5e0038fff2, 0x0009ff87ff653816, + 0x0b66fd600038fff2, 0x0009ff86ff693819, + 0x0b5efd610038fff2, 0x000aff85ff6d381c, + 0x0b57fd620038fff2, 0x000aff84ff71381f, + 0x0b4ffd630037fff2, 0x000aff84ff753822, + 0x0b48fd650037fff2, 0x000aff83ff793825, + 0x0b40fd660037fff2, 0x000aff82ff7d3828, + 0x0b39fd670037fff2, 0x000aff81ff81382b, + 0x0b32fd680037fff2, 0x000aff80ff85382e, + 0x0b2afd6a0037fff2, 0x000aff80ff893831, + 0x0b23fd6b0037fff2, 0x000aff7fff8d3834, + 0x0b1bfd6c0037fff2, 0x000aff7eff913837, + 0x0b14fd6d0036fff2, 0x000aff7dff96383a, + 0x0b0cfd6f0036fff2, 0x000aff7dff9a383d, + 0x0b05fd700036fff2, 0x000aff7cff9e3840, + 0x0afefd710036fff1, 0x000aff7bffa23843, + 0x0af6fd720036fff1, 0x000aff7affa63845, + 0x0aeffd740036fff1, 0x000aff7affaa3848, + 0x0ae7fd750036fff1, 0x000aff79ffae384b, + 0x0ae0fd760036fff1, 0x000aff78ffb2384e, + 0x0ad9fd770035fff1, 0x000aff77ffb73851, + 0x0ad1fd790035fff1, 0x000aff76ffbb3854, + 0x0acafd7a0035fff1, 0x000aff76ffbf3857, + 0x0ac3fd7b0035fff1, 0x000bff75ffc3385a, + 0x0abbfd7c0035fff1, 0x000bff74ffc7385c, + 0x0ab4fd7e0035fff1, 0x000bff73ffcb385f, + 0x0aadfd7f0035fff1, 0x000bff73ffd03862, + 0x0aa5fd800035fff1, 0x000bff72ffd43865, + 0x0a9efd810034fff1, 0x000bff71ffd83867, + 0x0a97fd830034fff1, 0x000bff70ffdc386a, + 0x0a8ffd840034fff1, 0x000bff6fffe0386d, + 0x0a88fd850034fff1, 0x000bff6fffe53870, + 0x0a81fd860034fff1, 0x000bff6effe93872, + 0x0a79fd880034fff1, 0x000bff6dffed3875, + 0x0a72fd890034fff1, 0x000bff6cfff13878, + 0x0a6bfd8a0034fff1, 0x000bff6bfff6387b, + 0x0a64fd8b0033fff1, 0x000bff6bfffa387d, + 0x0a5cfd8c0033fff1, 0x000bff6afffe3880, + 0x0a55fd8e0033fff1, 0x000bff6900023883, + 0x0a4efd8f0033fff1, 0x000bff6800073885, + 0x0a47fd900033fff1, 0x000bff67000b3888, + 0x0a3ffd910033fff1, 0x000bff67000f388b, + 0x0a38fd930033fff0, 0x000cff660014388d, + 0x0a31fd940033fff0, 0x000cff6500183890, + 0x0a2afd950033fff0, 0x000cff64001c3892, + 0x0a22fd960032fff0, 0x000cff6300203895, + 0x0a1bfd980032fff0, 0x000cff6300253898, + 0x0a14fd990032fff0, 0x000cff620029389a, + 0x0a0dfd9a0032fff0, 0x000cff61002d389d, + 0x0a06fd9b0032fff0, 0x000cff600032389f, + 0x09fefd9d0032fff0, 0x000cff5f003638a2, + 0x09f7fd9e0032fff0, 0x000cff5e003b38a4, + 0x09f0fd9f0032fff0, 0x000cff5e003f38a7, + 0x09e9fda00031fff0, 0x000cff5d004338a9, + 0x09e2fda10031fff0, 0x000cff5c004838ac, + 0x09dafda30031fff0, 0x000cff5b004c38ae, + 0x09d3fda40031fff0, 0x000cff5a005038b1, + 0x09ccfda50031fff0, 0x000cff5a005538b3, + 0x09c5fda60031fff0, 0x000cff59005938b6, + 0x09befda80031fff0, 0x000cff58005e38b8, + 0x09b7fda90031fff0, 0x000dff57006238ba, + 0x09b0fdaa0031fff0, 0x000dff56006738bd, + 0x09a8fdab0030fff0, 0x000dff55006b38bf, + 0x09a1fdac0030fff0, 0x000dff55006f38c2, + 0x099afdae0030fff0, 0x000dff54007438c4, + 0x0993fdaf0030fff0, 0x000dff53007838c6, + 0x098cfdb00030fff0, 0x000dff52007d38c9, + 0x0985fdb10030fff0, 0x000dff51008138cb, + 0x097efdb30030ffef, 0x000dff50008638cd, + 0x0977fdb40030ffef, 0x000dff50008a38d0, + 0x0970fdb5002fffef, 0x000dff4f008f38d2, + 0x0969fdb6002fffef, 0x000dff4e009338d4, + 0x0962fdb7002fffef, 0x000dff4d009838d7, + 0x095afdb9002fffef, 0x000dff4c009c38d9, + 0x0953fdba002fffef, 0x000dff4b00a138db, + 0x094cfdbb002fffef, 0x000dff4a00a538dd, + 0x0945fdbc002fffef, 0x000dff4a00aa38e0, + 0x093efdbe002fffef, 0x000dff4900ae38e2, + 0x0937fdbf002fffef, 0x000eff4800b338e4, + 0x0930fdc0002effef, 0x000eff4700b838e6, + 0x0929fdc1002effef, 0x000eff4600bc38e8, + 0x0922fdc2002effef, 0x000eff4500c138eb, + 0x091bfdc4002effef, 0x000eff4400c538ed, + 0x0914fdc5002effef, 0x000eff4400ca38ef, + 0x090dfdc6002effef, 0x000eff4300ce38f1, + 0x0906fdc7002effef, 0x000eff4200d338f3, + 0x08fffdc8002effef, 0x000eff4100d838f5, + 0x08f8fdca002dffef, 0x000eff4000dc38f7, + 0x08f1fdcb002dffef, 0x000eff3f00e138f9, + 0x08eafdcc002dffef, 0x000eff3e00e638fc, + 0x08e3fdcd002dffef, 0x000eff3e00ea38fe, + 0x08dcfdce002dffef, 0x000eff3d00ef3900, + 0x08d5fdd0002dffef, 0x000eff3c00f33902, + 0x08cefdd1002dffee, 0x000eff3b00f83904, + 0x08c7fdd2002dffee, 0x000fff3a00fd3906, + 0x08c1fdd3002dffee, 0x000fff3901013908, + 0x08bafdd5002cffee, 0x000fff380106390a, + 0x08b3fdd6002cffee, 0x000fff38010b390c, + 0x08acfdd7002cffee, 0x000fff370110390e, + 0x08a5fdd8002cffee, 0x000fff3601143910, + 0x089efdd9002cffee, 0x000fff3501193912, + 0x0897fddb002cffee, 0x000fff34011e3914, + 0x0890fddc002cffee, 0x000fff3301223916, + 0x0889fddd002cffee, 0x000fff3201273918, + 0x0882fdde002cffee, 0x000fff31012c391a, + 0x087cfddf002bffee, 0x000fff310131391b, + 0x0875fde1002bffee, 0x000fff300135391d, + 0x086efde2002bffee, 0x000fff2f013a391f, + 0x0867fde3002bffee, 0x000fff2e013f3921, + 0x0860fde4002bffee, 0x000fff2d01443923, + 0x0859fde5002bffee, 0x0010ff2c01483925, + 0x0852fde6002bffee, 0x0010ff2b014d3927, + 0x084cfde8002bffee, 0x0010ff2a01523928, + 0x0845fde9002bffee, 0x0010ff290157392a, + 0x083efdea002affee, 0x0010ff29015c392c, + 0x0837fdeb002affee, 0x0010ff280160392e, + 0x0830fdec002affee, 0x0010ff2701653930, + 0x082afdee002affee, 0x0010ff26016a3931, + 0x0823fdef002affee, 0x0010ff25016f3933, + 0x081cfdf0002affed, 0x0010ff2401743935, + 0x0815fdf1002affed, 0x0010ff2301793937, + 0x080efdf2002affed, 0x0010ff22017d3938, + 0x0808fdf4002affed, 0x0010ff210182393a, + 0x0801fdf50029ffed, 0x0010ff200187393c, + 0x07fafdf60029ffed, 0x0010ff20018c393d, + 0x07f3fdf70029ffed, 0x0010ff1f0191393f, + 0x07edfdf80029ffed, 0x0011ff1e01963941, + 0x07e6fdf90029ffed, 0x0011ff1d019b3942, + 0x07dffdfb0029ffed, 0x0011ff1c01a03944, + 0x07d8fdfc0029ffed, 0x0011ff1b01a43946, + 0x07d2fdfd0029ffed, 0x0011ff1a01a93947, + 0x07cbfdfe0029ffed, 0x0011ff1901ae3949, + 0x07c4fdff0028ffed, 0x0011ff1801b3394a, + 0x07befe010028ffed, 0x0011ff1701b8394c, + 0x07b7fe020028ffed, 0x0011ff1601bd394e, + 0x07b0fe030028ffed, 0x0011ff1501c2394f, + 0x07a9fe040028ffed, 0x0011ff1501c73951, + 0x07a3fe050028ffed, 0x0011ff1401cc3952, + 0x079cfe060028ffed, 0x0011ff1301d13954, + 0x0795fe080028ffed, 0x0011ff1201d63955, + 0x078ffe090028ffed, 0x0011ff1101db3957, + 0x0788fe0a0027ffed, 0x0012ff1001e03958, + 0x0782fe0b0027ffed, 0x0012ff0f01e5395a, + 0x077bfe0c0027ffed, 0x0012ff0e01ea395b, + 0x0774fe0d0027ffed, 0x0012ff0d01ef395d, + 0x076efe0f0027ffec, 0x0012ff0c01f4395e, + 0x0767fe100027ffec, 0x0012ff0b01f93960, + 0x0760fe110027ffec, 0x0012ff0a01fe3961, + 0x075afe120027ffec, 0x0012ff0902033962, + 0x0753fe130027ffec, 0x0012ff0802083964, + 0x074dfe140027ffec, 0x0012ff08020d3965, + 0x0746fe160026ffec, 0x0012ff0702123967, + 0x073ffe170026ffec, 0x0012ff0602173968, + 0x0739fe180026ffec, 0x0012ff05021c3969, + 0x0732fe190026ffec, 0x0012ff040222396b, + 0x072cfe1a0026ffec, 0x0013ff030227396c, + 0x0725fe1b0026ffec, 0x0013ff02022c396d, + 0x071ffe1d0026ffec, 0x0013ff010231396f, + 0x0718fe1e0026ffec, 0x0013ff0002363970, + 0x0711fe1f0026ffec, 0x0013feff023b3971, + 0x070bfe200025ffec, 0x0013fefe02403972, + 0x0704fe210025ffec, 0x0013fefd02453974, + 0x06fefe220025ffec, 0x0013fefc024a3975, + 0x06f7fe240025ffec, 0x0013fefb02503976, + 0x06f1fe250025ffec, 0x0013fefa02553977, + 0x06eafe260025ffec, 0x0013fef9025a3979, + 0x06e4fe270025ffec, 0x0013fef8025f397a, + 0x06ddfe280025ffec, 0x0013fef70264397b, + 0x06d7fe290025ffec, 0x0013fef70269397c, + 0x06d0fe2a0025ffec, 0x0014fef6026f397d, + 0x06cafe2c0024ffeb, 0x0014fef50274397e, + 0x06c3fe2d0024ffeb, 0x0014fef402793980, + 0x06bdfe2e0024ffeb, 0x0014fef3027e3981, + 0x06b7fe2f0024ffeb, 0x0014fef202833982, + 0x06b0fe300024ffeb, 0x0014fef102893983, + 0x06aafe310024ffeb, 0x0014fef0028e3984, + 0x06a3fe320024ffeb, 0x0014feef02933985, + 0x069dfe340024ffeb, 0x0014feee02983986, + 0x0696fe350024ffeb, 0x0014feed029e3987, + 0x0690fe360024ffeb, 0x0014feec02a33988, + 0x068afe370023ffeb, 0x0014feeb02a83989, + 0x0683fe380023ffeb, 0x0014feea02ad398a, + 0x067dfe390023ffeb, 0x0015fee902b3398b, + 0x0676fe3a0023ffeb, 0x0015fee802b8398c, + 0x0670fe3c0023ffeb, 0x0015fee702bd398d, + 0x066afe3d0023ffeb, 0x0015fee602c3398e, + 0x0663fe3e0023ffeb, 0x0015fee502c8398f, + 0x065dfe3f0023ffeb, 0x0015fee402cd3990, + 0x0657fe400023ffeb, 0x0015fee302d33991, + 0x0650fe410023ffeb, 0x0015fee202d83992, + 0x064afe420022ffeb, 0x0015fee102dd3993, + 0x0644fe430022ffeb, 0x0015fee002e33994, + 0x063dfe450022ffeb, 0x0015fedf02e83995, + 0x0637fe460022ffeb, 0x0015fede02ed3996, + 0x0631fe470022ffeb, 0x0015fedd02f33997, + 0x062afe480022ffeb, 0x0016fedc02f83998, + 0x0624fe490022ffea, 0x0016fedb02fd3998, + 0x061efe4a0022ffea, 0x0016feda03033999, + 0x0617fe4b0022ffea, 0x0016fed90308399a, + 0x0611fe4c0022ffea, 0x0016fed8030e399b, + 0x060bfe4e0021ffea, 0x0016fed70313399c, + 0x0605fe4f0021ffea, 0x0016fed60318399c, + 0x05fefe500021ffea, 0x0016fed5031e399d, + 0x05f8fe510021ffea, 0x0016fed40323399e, + 0x05f2fe520021ffea, 0x0016fed30329399f, + 0x05ecfe530021ffea, 0x0016fed2032e399f, + 0x05e5fe540021ffea, 0x0016fed1033339a0, + 0x05dffe550021ffea, 0x0016fed0033939a1, + 0x05d9fe570021ffea, 0x0017fecf033e39a2, + 0x05d3fe580021ffea, 0x0017fece034439a2, + 0x05ccfe590020ffea, 0x0017fecd034939a3, + 0x05c6fe5a0020ffea, 0x0017fecc034f39a4, + 0x05c0fe5b0020ffea, 0x0017fecb035439a4, + 0x05bafe5c0020ffea, 0x0017feca035a39a5, + 0x05b4fe5d0020ffea, 0x0017fec9035f39a6, + 0x05adfe5e0020ffea, 0x0017fec8036539a6, + 0x05a7fe5f0020ffea, 0x0017fec7036a39a7, + 0x05a1fe610020ffea, 0x0017fec6037039a8, + 0x059bfe620020ffea, 0x0017fec5037539a8, + 0x0595fe630020ffea, 0x0017fec4037b39a9, + 0x058ffe64001fffea, 0x0018fec3038039a9, + 0x0589fe65001fffea, 0x0018fec2038639aa, + 0x0582fe66001fffe9, 0x0018fec1038c39aa, + 0x057cfe67001fffe9, 0x0018fec0039139ab, + 0x0576fe68001fffe9, 0x0018febf039739ab, + 0x0570fe69001fffe9, 0x0018febe039c39ac, + 0x056afe6a001fffe9, 0x0018febd03a239ac, + 0x0564fe6c001fffe9, 0x0018febc03a739ad, + 0x055efe6d001fffe9, 0x0018febb03ad39ad, + 0x0558fe6e001fffe9, 0x0018feba03b339ae, + 0x0552fe6f001fffe9, 0x0018feb903b839ae, + 0x054bfe70001effe9, 0x0018feb803be39af, + 0x0545fe71001effe9, 0x0018feb703c339af, + 0x053ffe72001effe9, 0x0019feb603c939b0, + 0x0539fe73001effe9, 0x0019feb503cf39b0, + 0x0533fe74001effe9, 0x0019feb403d439b0, + 0x052dfe75001effe9, 0x0019feb303da39b1, + 0x0527fe76001effe9, 0x0019feb203e039b1, + 0x0521fe78001effe9, 0x0019feb103e539b1, + 0x051bfe79001effe9, 0x0019feb003eb39b2, + 0x0515fe7a001effe9, 0x0019feaf03f139b2, + 0x050ffe7b001effe9, 0x0019feae03f639b2, + 0x0509fe7c001dffe9, 0x0019fead03fc39b3, + 0x0503fe7d001dffe9, 0x0019feac040239b3, + 0x04fdfe7e001dffe9, 0x001afeab040739b3, + 0x04f7fe7f001dffe9, 0x001afeaa040d39b4, + 0x04f1fe80001dffe9, 0x001afea9041339b4, + 0x04ebfe81001dffe9, 0x001afea7041939b4, + 0x04e5fe82001dffe9, 0x001afea6041e39b4, + 0x04dffe83001dffe8, 0x001afea5042439b5, + 0x04d9fe85001dffe8, 0x001afea4042a39b5, + 0x04d3fe86001dffe8, 0x001afea3043039b5, + 0x04cdfe87001cffe8, 0x001afea2043539b5, + 0x04c7fe88001cffe8, 0x001afea1043b39b5, + 0x04c2fe89001cffe8, 0x001afea0044139b6, + 0x04bcfe8a001cffe8, 0x001afe9f044739b6, + 0x04b6fe8b001cffe8, 0x001bfe9e044c39b6, + 0x04b0fe8c001cffe8, 0x001bfe9d045239b6, + 0x04aafe8d001cffe8, 0x001bfe9c045839b6, + 0x04a4fe8e001cffe8, 0x001bfe9b045e39b6, + 0x049efe8f001cffe8, 0x001bfe9a046439b6, + 0x0498fe90001cffe8, 0x001bfe99046939b6, + 0x0492fe91001cffe8, 0x001bfe98046f39b6, + 0x048dfe92001cffe8, 0x001bfe97047539b7, + 0x0487fe93001bffe8, 0x001bfe96047b39b7, + 0x0481fe95001bffe8, 0x001bfe95048139b7 +}; diff --git a/Src/Plugins/Input/in_mod/mikamp/in_mod.rc b/Src/Plugins/Input/in_mod/mikamp/in_mod.rc new file mode 100644 index 00000000..16d37864 --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/in_mod.rc @@ -0,0 +1,391 @@ +// 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 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_PREFS DIALOGEX 0, 0, 293, 205 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + CONTROL "Tab1",MM_PREFTAB,"SysTabControl32",WS_TABSTOP,4,3,285,180 + DEFPUSHBUTTON "OK",IDOK,185,188,50,13 + PUSHBUTTON "Cancel",IDCANCEL,239,188,50,13 +END + +IDD_ID3EDIT DIALOGEX 0, 0, 273, 239 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Module Info" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_ID3_FN,4,4,265,12,ES_AUTOHSCROLL | ES_READONLY + RTEXT "Title",IDC_STATIC,4,22,14,8 + EDITTEXT IDC_TITLE,22,21,133,12,ES_AUTOHSCROLL | ES_READONLY + RTEXT "Type",IDC_STATIC,160,22,15,8 + EDITTEXT IDC_TYPE,180,21,89,12,ES_AUTOHSCROLL | ES_READONLY + GROUPBOX "Header Info",IDC_STATIC,4,38,130,57 + LTEXT "File Size:\nLength:\nChannels:\nSamples:\nInstruments:",IDC_STATIC,8,49,45,40 + LTEXT "",IDC_INFOLEFT,61,49,70,40 + GROUPBOX "Player Info",IDC_STATIC,139,38,130,57 + LTEXT "BPM:\nSong Spd:\nPosition:\nRow:\nVoices:",IDC_STATIC,142,49,45,40 + LTEXT "",IDC_INFORIGHT,195,49,70,40 + CONTROL "Tab1",IDC_TAB,"SysTabControl32",WS_TABSTOP,4,99,265,120 + CONTROL "Track song",IDC_TRACK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,4,224,52,10 + DEFPUSHBUTTON "Close",IDOK,227,223,42,13 +END + +IDD_INSTRUMENTS DIALOGEX 0, 0, 262, 103 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LISTBOX IDC_INSTLIST,2,3,128,98,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Instrument Header",IDC_STATIC,134,0,125,101 + LTEXT "Def. Volume:\nAuto-Vibrato:\nFade out:",IDC_STATIC,138,9,44,24 + LTEXT "",IDC_INSTHEAD,190,9,64,24 + GROUPBOX "Envelopes",IDC_STATIC,134,34,125,67 + LTEXT "Volume:\nPanning:\nPitch:",IDC_STATIC,138,42,44,24 + LTEXT "",IDC_INSTENV,190,42,64,24 + GROUPBOX "Samples Used",IDC_STATIC,134,67,125,34 + EDITTEXT TB_SAMPLELIST,137,77,119,21,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | WS_VSCROLL +END + +IDD_SAMPLES DIALOGEX 0, 0, 262, 103 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LISTBOX IDC_SAMPLIST,2,3,128,98,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Sample Header",IDC_STATIC,134,0,125,68 + LTEXT "Length:\nFormat:\nQuality:\nLooping:\nAuto-Vibrato:\nVolume:\nPanning:",IDC_STATIC,139,8,45,56 + LTEXT "",IDC_SAMPINFO,190,8,66,56 +END + +IDD_COMMENT DIALOGEX 0, 0, 262, 103 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + EDITTEXT CEMENT_BOX,2,3,258,98,ES_MULTILINE | ES_READONLY | WS_VSCROLL | WS_HSCROLL +END + +IDD_PREFTAB_DECODER DIALOGEX 0, 0, 282, 163 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "Song Looping",IDC_STATIC,4,1,273,64 + CONTROL "Slider1",IDC_LOOPS,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,7,15,127,12 + LTEXT "",IDC_LOOPTEXT,141,15,129,12 + CONTROL "Unconditional Looping\n(Forces all songs to loop even if they lack specific loop information)",IDC_LOOPALL, + "Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,9,32,126,27 + CONTROL "Continue Play After Loop\n(useful for some games' modules, that contain several songs indeed)",IDC_CONT_LOOP, + "Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,144,32,127,27 + CONTROL "Play Unlisted Patterns\n(Otherwise-unplayed pattern data is tacked onto the end of the song)",IDC_PLAYALL, + "Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_DISABLED | WS_TABSTOP,9,69,130,27 + CONTROL "Use Resonant Lowpass Filters\n(Used by some Impulse Tracker songs; CPU intensive)",IDC_RESONANCE, + "Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,144,69,131,27 + CONTROL "Strip Trailing Silence\n(May cause some songs to end or loop a few seconds prematurely)",IDC_STRIPSILENCE, + "Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,8,100,130,26 + CONTROL "Seek by orders (as opposed to seek by seconds; song length will be displayed in orders, too)",IDC_SEEKBYORDERS, + "Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,144,100,131,27 + LTEXT "Stereo Separation",IDC_STATIC,4,140,42,18 + LTEXT "0%",IDC_STATIC,48,132,17,9 + LTEXT "100%",IDC_STATIC,102,132,21,9 + LTEXT "400%",IDC_STATIC,159,132,22,9 + CONTROL "Slider1",IDC_PANSEP,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,48,145,123,9 + CONTROL "Fadeout for...",IDC_FADECHECK,"Button",BS_AUTOCHECKBOX | BS_TOP | WS_TABSTOP,205,132,69,10 + EDITTEXT IDC_FADEOUT,205,145,49,12,ES_RIGHT | ES_AUTOHSCROLL | WS_DISABLED + CONTROL "Spin1",IDC_FADESPIN,"msctls_updown32",UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS,248,145,8,13 + LTEXT "sec.",IDC_FADESEC,260,148,14,10,WS_DISABLED +END + +IDD_PREFTAB_MIXER DIALOGEX 0, 0, 282, 163 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_TRANSPARENT | WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + GROUPBOX "Output Mode",IDC_STATIC,4,1,134,47 + CONTROL "Reverse Stereo",OUTMODE_REVERSE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,9,15,72,10 + LTEXT "Max voices:",IDC_STATIC,12,31,39,8 + COMBOBOX IDC_VOICES,60,29,42,88,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Streaming",IDC_STATIC,4,49,134,29 + CONTROL "Prompt to save to disk",IDC_SAVESTR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,9,61,85,11 + GROUPBOX "Output Quality",IDC_STATIC,143,1,134,149 + LTEXT "Mixing Rate:",IDC_STATIC,149,17,41,9 + COMBOBOX OQ_QUALITY,195,15,66,76,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Interpolation\n(Disable for lower-quality sound and 20% improvement in mixer efficiency)",OQ_INTERP, + "Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,149,33,121,35 + CONTROL "FIR interpolation (high quality)",OQ_CUBIC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,149,71,109,10 + CONTROL "Micro Volume-Ramping\n(Helps remove clicks and pops from interpolated sound)",OQ_NOCLICK, + "Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,149,86,124,27 + LTEXT "Note: Try lower sample rates before disabling interpolation for slower machines. It sounds better that way.",IDC_STATIC,150,116,121,30 +END + +IDD_PREFTAB_LOADER DIALOGEX 0, 0, 282, 163 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_TRANSPARENT | WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + LTEXT "Available Loaders:",IDC_STATIC,4,2,80,9 + LISTBOX IDLDR_LIST,4,11,118,59,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Default Panning",IDC_STATIC,4,72,118,88 + LTEXT "Mono",IDC_STATIC,8,81,19,8 + LTEXT "Full Stereo",IDC_STATIC,83,81,34,9 + CONTROL "Slider1",IDLDR_PANPOS,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,14,92,96,12 + CTEXT "Default panning is only set at load-time and can be overridden by the song during replay",IDC_STATIC,8,111,108,25 + PUSHBUTTON "Reset",IDC_DEFPAN,30,141,65,13 + LTEXT "",IDLDR_DESCRIPTION,133,4,143,34 + CONTROL "Enabled\n(When disabled, this filetype is ignored by Mikamp)",IDLDR_ENABLED, + "Button",BS_3STATE | BS_TOP | BS_MULTILINE | WS_TABSTOP,133,42,137,27 + GROUPBOX "Advanced Effects Options",IDC_STATIC,128,72,149,88 + CONTROL "Disable DMP Panning Effects (8xx)\n(Select this for songs that seem to incorrectly pan to the left speaker)",IDLDR_EFFOPT1, + "Button",BS_3STATE | BS_TOP | BS_MULTILINE | WS_TABSTOP,133,84,137,25 + CONTROL "Disable Resonance Filter Effects (Zxx)",IDLDR_EFFOPT2, + "Button",BS_3STATE | BS_TOP | WS_TABSTOP,133,113,138,10 + CTEXT "It is not recommended to change the advanced options unless you really\nknow what you are doing",IDC_ADV_TEXT_INFO,132,129,138,25,WS_DISABLED +END + + +#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 + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_PREFS, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 289 + TOPMARGIN, 3 + BOTTOMMARGIN, 201 + END + + IDD_ID3EDIT, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 269 + TOPMARGIN, 2 + BOTTOMMARGIN, 236 + END + + IDD_INSTRUMENTS, DIALOG + BEGIN + LEFTMARGIN, 2 + RIGHTMARGIN, 259 + TOPMARGIN, 3 + BOTTOMMARGIN, 101 + END + + IDD_SAMPLES, DIALOG + BEGIN + LEFTMARGIN, 2 + RIGHTMARGIN, 259 + TOPMARGIN, 3 + BOTTOMMARGIN, 101 + END + + IDD_COMMENT, DIALOG + BEGIN + LEFTMARGIN, 2 + RIGHTMARGIN, 260 + TOPMARGIN, 3 + BOTTOMMARGIN, 100 + END + + IDD_PREFTAB_DECODER, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 278 + TOPMARGIN, 1 + BOTTOMMARGIN, 159 + END + + IDD_PREFTAB_MIXER, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 278 + BOTTOMMARGIN, 159 + END + + IDD_PREFTAB_LOADER, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 277 + TOPMARGIN, 2 + BOTTOMMARGIN, 160 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_MODULE_DECODER "Nullsoft Module Decoder v%s" + 65535 "{A1A39D49-671A-4c2f-AE42-BEA134EAF6A9}" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_MODULE_DECODER_OLD "Nullsoft Module Decoder" + IDS_PROTRACKER_AND_CLONES + "Protracker and clones [mod]:\nIncludes Protracker, Fasttracker 1, Taketracker, Startrekker. Limited to 31 samples." + IDS_OLD_SKOOL_AMIGA_MODULES + "Old-skool Amiga Modules [mod]:\nSoundtracker, Ultimate Soundtracker. Very old and extremely limited in many ways (4 channels, 15 samples)" + IDS_SCREAM_TRACKER_2XX "Scream Tracker 2.xx [stm]:\nSimilar to ST3 modules, but limited to 4 channels; Various format quirks." + IDS_SCREAM_TRACKER_3XX "Scream Tracker 3.xx [s3m]:\nUp to 32 channels; Features a wavetable/FM combo (albeit rarely used)." + IDS_IMPULSE_TRACKER "Impulse Tracker (all versions) [it]:\nSupports 64 channels, new note actions, and resonance filters." + IDS_FASTTRACKER_2XX "Fasttracker 2.xx [xm]:\nSupports 32 channels, 128 instruments, and volume/panning envelopes." + IDS_MULTITRACKER "Multitracker [mtm]:\nA ""superfied"" Protracker-based format, features std effects /w 32 channels." + IDS_ULTRA_TRACKER "Ultra Tracker [ult]:\nDesigned specifically for the Gravis Ultrasound, features 32 channels and two effects per row." + IDS_COMPOSER_669 "Composer 669/UNIS 669 [669]:\nOne of the first PC trackers. 8 channels, up to 64 samples, few effects." + IDS_FARANDOLE_COMPOSER "Farandole Composer [far]:\nYet another composer. The 16 channel version of the Composer 669, but with more messy effects." + IDS_DIGITAL_SOUND_AND_MUSIC_INTERFACE + "Digital Sound and Music Interface [amf]:\nAdvanced Module Format... May be even too advanced to support it... Anyway, here it goes." + IDS_AMIGA_OKTALYZER "Amiga Oktalyzer [okt]:\n4-8 voices, 36 samples (usually), rather strange effects. The source code is about 20000 lines!" + IDS_POLYTRACKER "PolyTracker [ptm]:\nA Scream Tracker 3 clone (with ""protrac- kerized"" effects and some extra features), created by Lone Ranger / AcmE." + IDS_MULTIPLE_ITEMS_SELECTED "Multiple items selected..." + IDS_MUSIC_MODULES "Music Modules" +END + +STRINGTABLE +BEGIN + IDS_MIXER "Mixer" + IDS_PLAYER "Player" + IDS_LOADERS "Loaders" + IDS_DEFAULT " (default)" + IDS_SELECT_ANY_LOADER "Select any loader(s) to edit their properties..." + IDS_DO_NOT_LOOP "Do not loop" + IDS_LOOP_FOREVER "Loop forever" + IDS_LOOP_X_TIMES "Loop %d times" + IDS_PREFERENCES_TITLE "%s Preferences" + IDS_SAMPLES "Samples" + IDS_INSTRUMENTS "Instruments" + IDS_COMMENT "Comment" + IDS_YES "Yes" + IDS_NO "No" + IDS_SUSTAIN "Sustain" +END + +STRINGTABLE +BEGIN + IDS_FINETUNE "(finetune)" + IDS_HZ_SIGNED "Hz signed" + IDS_HZ_UNSIGNED "Hz unsigned" + IDS_PING_PONG "Ping-Pong" + IDS_REVERSE "Reverse" + IDS_FORWARD "Forward" + IDS_SUSTAIN_PING_PONG "Sustain Ping-Pong" + IDS_NONE "None" + IDS_X_X_X_OF_X_NOT_PLAYING "%d\n%d\n0 of %d\n\nNot Playing..." + IDS_X_X_X_OF_X_X_OF_X_X_OF_X "%d\n%d\n%d of %d (%d)\n%d of %d\n%d (%d)" + IDS_X_BTYES_X_OF_X_MINUTES "%d bytes\n%d:%02d minutes\n%d\n%d\n%d" + IDS_URLS_ONLY_SUPPORTED_IN_2_10_PLUS "URLs only supported in Winamp 2.10+" + IDS_MOD_PLUGIN_ERROR "Module Plug-in Error" + IDS_SAVE_MODULE "Save Module" + IDS_ALL_FILES "All files" + IDS_RETRIEVING_MODULE "Retrieving Module" +END + +STRINGTABLE +BEGIN + IDS_ERROR_KILLING_DECODING_THREAD "Error killing decoding thread." + IDS_LOOP "Loop" + IDS_ON "On" + IDS_OFF "Off" + IDS_BYTES "bytes" + IDS_BITS "bits" + IDS_FAMILY_STRING_COMPOSER_669 "Composer 669" + IDS_FAMILY_STRING_DSMI_AMF "DSMI AMF" + IDS_FAMILY_STRING_FARANDOLE_COMPOSER "Farandole Composer" + IDS_FAMILY_STRING_IMPULSETRACKER "Impulsetracker" + IDS_FAMILY_STRING_SOUNDTRACKER "Soundtracker (15-inst)" + IDS_FAMILY_STRING_PROTRACKER "Protracker" + IDS_FAMILY_STRING_MULTITRACKER "Multitracker" + IDS_FAMILY_STRING_AMIGA_OKTALYZER "Amiga Oktalyzer" + IDS_FAMILY_STRING_POLYTRACKER "PolyTracker" + IDS_FAMILY_STRING_SCREAMTRACKER3 "Screamtracker 3" +END + +STRINGTABLE +BEGIN + IDS_FAMILY_STRING_SCREAMTRACKER2 "Screamtracker 2" + IDS_FAMILY_STRING_ULTRATRACKER "Ultratracker" + IDS_FAMILY_STRING_FASTRACKER2 "Fasttracker 2" + IDS_FAMILY_STRING_NOISETRACKER "NoiseTracker Module" + IDS_X_MODULE "%s Module" + IDS_X_COMPRESSED_MODULE "%s Compressed Module" + IDS_ABOUT_TEXT "%s\n© 1998-2014 Winamp SA\nWritten by: Justin Frankel & Jake Stine\nBuild date: %hs\n\nMikamp - Nullsoft Module Decoder plug-in\nbased on the Mikmod Sound System\n\nModule loading & rendering by: Jake Stine\nThanks to Jeffrey Lim for additional Impulse Tracker info.\nAdditional code by: Peter Pawlowski & X-Fixer" + IDS_CORRUPT_UNSUPPORTED_TYPE "Corrupt file or unsupported module type." +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_mod/mikamp/include/Main.h b/Src/Plugins/Input/in_mod/mikamp/include/Main.h new file mode 100644 index 00000000..9f9b9171 --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/include/Main.h @@ -0,0 +1,106 @@ +#ifndef __MIKAMP_MAIN_H__ +#define __MIKAMP_MAIN_H__ + +#include <windows.h> +#include <stdio.h> +#include "mikmod.h" +#include "mplayer.h" +#include "resource.h" +#include "in2.h" + +#define INFO_CPAGES 3 + +#define CPLAYFLG_LOOPALL (1ul<<0) // disables selective looping - loop everything! +#define CPLAYFLG_PLAYALL (1ul<<1) // plays hidden patterns (tack onto end of the song) +#define CPLAYFLG_FADEOUT (1ul<<2) // Fadeout the song before the end cometh? +#define CPLAYFLG_STRIPSILENCE (1ul<<3) // Strip silence at the end of the song? +#define CPLAYFLG_SEEKBYORDERS (1ul<<4) // Seek by orders instead of seconds +#define CPLAYFLG_CONT_LOOP (1ul<<5) // continue after loop + +typedef struct tag_dlghdr +{ + HWND hwndTab; // tab control + HWND hwndDisplay; // current child dialog box + int left,top; + HWND apRes[INFO_CPAGES]; + + UNIMOD *module; + MPLAYER *seeker; + int maxv; + + BOOL inUse, ownModule; + BOOL *suse; + +} DLGHDR; + + +typedef struct INFOBOX +{ + HWND hwnd; + DLGHDR dlg; + struct INFOBOX *next; +} INFOBOX; + + +#ifdef __cplusplus +extern "C" { +#endif + +extern UBYTE config_nopan, config_savestr; +extern MD_DEVICE drv_amp; +extern MD_DEVICE drv_buffer; +extern In_Module mikmod; +extern UNIMOD *mf; +extern MPLAYER *mp; + +// Defined in INFO.C +// ----------------- +extern INFOBOX *infobox_list; +extern void infoDlg(HWND hwnd, UNIMOD *m, BOOL activate, BOOL primiary); +extern int config_info_x, config_info_y, config_track; + + +// Defined in INFO.C +// ----------------- +// defined in config.c + +extern UBYTE config_interp; +extern UBYTE config_panrev; +extern UBYTE config_cpu; +extern uint config_srate, config_voices, config_playflag; +extern int config_pansep, config_loopcount; +extern UBYTE config_samplesize; + +extern UBYTE config_resonance; +extern int config_fadeout; +extern int config_tsel; + +extern int paused; + + +// config.c shizat +// --------------- +extern void set_priority(void); + +extern void __cdecl config(HWND hwndParent); +extern void __cdecl about(HWND hwndParent); + +extern void config_read(); +extern void config_write(); + +extern void info_killseeker(HWND hwnd); + +int GetNumChannels(); +int AllowSurround(); +int GetThreadPriorityConfig(); + +BOOL GetTypeInfo(LPCWSTR pszType, LPWSTR pszDest, INT cchDest); + +#ifdef __cplusplus +}; +#endif + +//#define PLUGIN_NAME "Nullsoft Module Decoder" +#define PLUGIN_VER L"2.94" + +#endif diff --git a/Src/Plugins/Input/in_mod/mikamp/include/in2.h b/Src/Plugins/Input/in_mod/mikamp/include/in2.h new file mode 100644 index 00000000..2c045b84 --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/include/in2.h @@ -0,0 +1 @@ +#include "../../../Winamp/in2.h"
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mod/mikamp/int64.lib b/Src/Plugins/Input/in_mod/mikamp/int64.lib Binary files differnew file mode 100644 index 00000000..92201d43 --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/int64.lib diff --git a/Src/Plugins/Input/in_mod/mikamp/mikamp.dsp b/Src/Plugins/Input/in_mod/mikamp/mikamp.dsp new file mode 100644 index 00000000..5107ca40 --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/mikamp.dsp @@ -0,0 +1,167 @@ +# Microsoft Developer Studio Project File - Name="mikamp" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=mikamp - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "mikamp.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "mikamp.mak" CFG="mikamp - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mikamp - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mikamp - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "mikamp - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /Z7 /Ox /Ow /Og /Oi /Os /Gf /Gy /I "." /I "include" /I "..\lib\mikmod\include" /I "..\lib\mmio\include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /Gs0 /FD /c +# SUBTRACT CPP /Ot /Fr +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib int64.lib /nologo /dll /machine:I386 /out:"c:\program files\winamp\plugins\in_mod.dll" /opt:nowin98 +# SUBTRACT LINK32 /pdb:none /nodefaultlib + +!ELSEIF "$(CFG)" == "mikamp - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "." /I "include" /I "..\lib\mikmod\include" /I "..\lib\mmio\include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib /nologo /dll /debug /machine:I386 /out:"c:\program files\winamp\plugins\in_mod.dll" /pdbtype:sept +# SUBTRACT LINK32 /nodefaultlib + +!ENDIF + +# Begin Target + +# Name "mikamp - Win32 Release" +# Name "mikamp - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\src\config.c +# End Source File +# Begin Source File + +SOURCE=.\src\drv_amp.c +# End Source File +# Begin Source File + +SOURCE=.\src\info.c +# End Source File +# Begin Source File + +SOURCE=.\src\main.c +# End Source File +# Begin Source File + +SOURCE=.\src\rf_wrapper.c +# End Source File +# Begin Source File + +SOURCE=.\src\tagz.cpp + +!IF "$(CFG)" == "mikamp - Win32 Release" + +# ADD CPP /GX- + +!ELSEIF "$(CFG)" == "mikamp - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\src\tagz.h +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\include\in2.h +# End Source File +# Begin Source File + +SOURCE=.\include\main.h +# End Source File +# Begin Source File + +SOURCE=.\include\out.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\Winamp\wa_ipc.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=.\resource.h +# End Source File +# Begin Source File + +SOURCE=.\script1.rc +# End Source File +# End Group +# End Target +# End Project diff --git a/Src/Plugins/Input/in_mod/mikamp/mikamp.sln b/Src/Plugins/Input/in_mod/mikamp/mikamp.sln new file mode 100644 index 00000000..b782d75b --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/mikamp.sln @@ -0,0 +1,31 @@ +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_mod (mikamp)", "mikamp.vcproj", "{3580EE92-4F7B-4322-A04C-B2B7B5578879}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_mod (mikmod)", "..\lib\mikmod\mikmod.vcproj", "{47FCA4AF-1F6B-4A1A-8AF2-1BD9E11672DD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_mod (mmio)", "..\lib\mmio\mmio.vcproj", "{BE408A85-4302-4EB8-8DE3-E3F9A5B25715}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3580EE92-4F7B-4322-A04C-B2B7B5578879}.Debug|Win32.ActiveCfg = Debug|Win32 + {3580EE92-4F7B-4322-A04C-B2B7B5578879}.Debug|Win32.Build.0 = Debug|Win32 + {3580EE92-4F7B-4322-A04C-B2B7B5578879}.Release|Win32.ActiveCfg = Release|Win32 + {3580EE92-4F7B-4322-A04C-B2B7B5578879}.Release|Win32.Build.0 = Release|Win32 + {47FCA4AF-1F6B-4A1A-8AF2-1BD9E11672DD}.Debug|Win32.ActiveCfg = Debug|Win32 + {47FCA4AF-1F6B-4A1A-8AF2-1BD9E11672DD}.Debug|Win32.Build.0 = Debug|Win32 + {47FCA4AF-1F6B-4A1A-8AF2-1BD9E11672DD}.Release|Win32.ActiveCfg = Release|Win32 + {47FCA4AF-1F6B-4A1A-8AF2-1BD9E11672DD}.Release|Win32.Build.0 = Release|Win32 + {BE408A85-4302-4EB8-8DE3-E3F9A5B25715}.Debug|Win32.ActiveCfg = Debug|Win32 + {BE408A85-4302-4EB8-8DE3-E3F9A5B25715}.Debug|Win32.Build.0 = Debug|Win32 + {BE408A85-4302-4EB8-8DE3-E3F9A5B25715}.Release|Win32.ActiveCfg = Release|Win32 + {BE408A85-4302-4EB8-8DE3-E3F9A5B25715}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_mod/mikamp/mikamp.vcproj b/Src/Plugins/Input/in_mod/mikamp/mikamp.vcproj new file mode 100644 index 00000000..3186535b --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/mikamp.vcproj @@ -0,0 +1,416 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="9.00" + Name="in_mod (mikamp)" + ProjectGUID="{3580EE92-4F7B-4322-A04C-B2B7B5578879}" + RootNamespace="in_mod (mikamp)" + TargetFrameworkVersion="131072" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="2" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="false" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + PreprocessorDefinitions="_DEBUG" + MkTypLibCompatible="true" + SuppressStartupBanner="true" + TargetEnvironment="1" + TypeLibraryName=".\Debug/mikamp.tlb" + HeaderFileName="" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="../../Wasabi;.;include;..\lib\mikmod\include;..\lib\mmio\include" + PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + PrecompiledHeaderFile=".\Debug/mikamp.pch" + AssemblerListingLocation=".\Debug/" + ObjectFile=".\Debug/" + ProgramDataBaseFileName=".\Debug/" + BrowseInformation="1" + WarningLevel="3" + SuppressStartupBanner="true" + DebugInformationFormat="4" + CompileAs="0" + DisableSpecificWarnings="4996" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="_DEBUG" + Culture="1033" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="comctl32.lib int64.lib shlwapi.lib ../../nsutil/nsutil.lib" + OutputFile="$(ProgramFiles)\winamp\plugins\in_mod.dll" + LinkIncremental="1" + SuppressStartupBanner="true" + GenerateDebugInformation="true" + ProgramDatabaseFile=".\Debug/in_mod.pdb" + RandomizedBaseAddress="1" + DataExecutionPrevention="0" + ImportLibrary=".\Debug/in_mod.lib" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="2" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="false" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + PreprocessorDefinitions="NDEBUG" + MkTypLibCompatible="true" + SuppressStartupBanner="true" + TargetEnvironment="1" + TypeLibraryName=".\Release/mikamp.tlb" + HeaderFileName="" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="1" + InlineFunctionExpansion="1" + EnableIntrinsicFunctions="true" + FavorSizeOrSpeed="2" + AdditionalIncludeDirectories="../../Wasabi;.;include;..\lib\mikmod\include;..\lib\mmio\include" + PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS" + StringPooling="true" + ExceptionHandling="0" + RuntimeLibrary="2" + BufferSecurityCheck="false" + UsePrecompiledHeader="0" + PrecompiledHeaderFile=".\Release/mikamp.pch" + AssemblerListingLocation=".\Release/" + ObjectFile="$(IntDir)/" + ProgramDataBaseFileName=".\Release/" + WarningLevel="3" + SuppressStartupBanner="true" + DebugInformationFormat="3" + CompileAs="0" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="NDEBUG" + Culture="1033" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + IgnoreImportLibrary="true" + AdditionalDependencies="comctl32.lib int64.lib shlwapi.lib ../../nsutil/nsutil.lib" + OutputFile="$(ProgramFiles)\winamp\plugins\in_mod.dll" + LinkIncremental="1" + SuppressStartupBanner="true" + GenerateManifest="false" + IgnoreDefaultLibraryNames="msvcprt.lib" + GenerateDebugInformation="true" + ProgramDatabaseFile="$(OutDir)/$(ProjectName).pdb" + OptimizeReferences="2" + EnableCOMDATFolding="2" + RandomizedBaseAddress="1" + ImportLibrary=".\Release/in_mod.lib" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + <ProjectReference + ReferencedProjectIdentifier="{47FCA4AF-1F6B-4A1A-8AF2-1BD9E11672DD}" + RelativePathToProject="..\in_mod\lib\mikmod\mikmod.vcproj" + /> + <ProjectReference + ReferencedProjectIdentifier="{BE408A85-4302-4EB8-8DE3-E3F9A5B25715}" + RelativePathToProject="..\in_mod\lib\mmio\mmio.vcproj" + /> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" + > + <File + RelativePath="src\config.c" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="" + PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;$(NoInherit)" + BasicRuntimeChecks="3" + BrowseInformation="1" + CompileAs="2" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="" + CompileAs="2" + /> + </FileConfiguration> + </File> + <File + RelativePath="src\drv_amp.c" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="" + PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;$(NoInherit)" + BasicRuntimeChecks="3" + BrowseInformation="1" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="" + /> + </FileConfiguration> + </File> + <File + RelativePath=".\src\drv_buffer.cpp" + > + </File> + <File + RelativePath=".\src\drv_buffer.h" + > + </File> + <File + RelativePath=".\src\ExtendedRead.cpp" + > + </File> + <File + RelativePath="src\info.c" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="" + PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;$(NoInherit)" + BasicRuntimeChecks="3" + BrowseInformation="1" + CompileAs="2" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="" + CompileAs="2" + /> + </FileConfiguration> + </File> + <File + RelativePath="src\main.c" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="" + PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;$(NoInherit)" + BasicRuntimeChecks="3" + BrowseInformation="1" + CompileAs="2" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="" + CompileAs="2" + /> + </FileConfiguration> + </File> + <File + RelativePath=".\playbackconfig.cpp" + > + </File> + <File + RelativePath="src\rf_wrapper.c" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="" + PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;$(NoInherit)" + BasicRuntimeChecks="3" + BrowseInformation="1" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="" + /> + </FileConfiguration> + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl" + > + <File + RelativePath=".\src\api.h" + > + </File> + <File + RelativePath="include\in2.h" + > + </File> + <File + RelativePath="include\main.h" + > + </File> + <File + RelativePath="include\out.h" + > + </File> + </Filter> + <Filter + Name="Resource Files" + Filter="ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" + > + <File + RelativePath=".\in_mod.rc" + > + </File> + <File + RelativePath="resource.h" + > + </File> + </Filter> + <File + RelativePath=".\int64.lib" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/Src/Plugins/Input/in_mod/mikamp/playbackconfig.cpp b/Src/Plugins/Input/in_mod/mikamp/playbackconfig.cpp new file mode 100644 index 00000000..24e7d16f --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/playbackconfig.cpp @@ -0,0 +1,78 @@ +#include "main.h" +#include "../winamp/wa_ipc.h" +#include <api/service/waServiceFactory.h> +#include "../../Agave/Config/api_config.h" + +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = +{ 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } }; + +static api_config *configApi=0; + +api_config *GetConfigAPI() +{ + if (mikmod.service && !configApi) + { + waServiceFactory *sf = (waServiceFactory *)mikmod.service->service_getServiceByGuid(AgaveConfigGUID); + configApi = (api_config *)sf->getInterface(); + } + + return configApi; +} +extern "C" +int GetSampleSizeFlag() +{ + api_config *config = GetConfigAPI(); + int bits=16; + if (config) + bits = config->GetUnsigned(playbackConfigGroupGUID, L"bits", 16); + + switch(bits) + { + case 8: + return 0; + case 24: + return DMODE_24BITS; + case 16: + default: + return DMODE_16BITS; + } + +} + +extern "C" +int GetNumChannels() +{ + api_config *config = GetConfigAPI(); + bool mono=false; + if (config) + mono = config->GetBool(playbackConfigGroupGUID, L"mono", false); + + if (mono) + return 1; + else + return 2; + +} + +extern "C" +int AllowSurround() +{ + api_config *config = GetConfigAPI(); + bool surround=true; + if (config) + surround= config->GetBool(playbackConfigGroupGUID, L"surround", true); + + return surround?1:0; +} + +extern "C" +int GetThreadPriorityConfig() +{ + api_config *config = GetConfigAPI(); + int priority=THREAD_PRIORITY_HIGHEST; + if (config) + priority = config->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST); + + return priority; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mod/mikamp/resource.h b/Src/Plugins/Input/in_mod/mikamp/resource.h new file mode 100644 index 00000000..d5f5721a --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/resource.h @@ -0,0 +1,149 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by script1.rc +// +#define IDS_NULLSOFT_MODULE_DECODER_OLD 0 +#define IDS_PROTRACKER_AND_CLONES 1 +#define IDS_OLD_SKOOL_AMIGA_MODULES 2 +#define IDS_SCREAM_TRACKER_2XX 3 +#define IDS_SCREAM_TRACKER_3XX 4 +#define IDS_IMPULSE_TRACKER 5 +#define IDS_FASTTRACKER_2XX 6 +#define IDS_MULTITRACKER 7 +#define IDS_ULTRA_TRACKER 8 +#define IDS_COMPOSER_669 9 +#define IDS_FARANDOLE_COMPOSER 10 +#define IDS_DIGITAL_SOUND_AND_MUSIC_INTERFACE 11 +#define IDS_AMIGA_OKTALYZER 12 +#define IDS_POLYTRACKER 13 +#define IDS_MULTIPLE_ITEMS_SELECTED 14 +#define IDS_MUSIC_MODULES 15 +#define IDS_MIXER 16 +#define IDS_PLAYER 17 +#define IDS_LOADERS 18 +#define IDS_DEFAULT 19 +#define IDS_SELECT_ANY_LOADER 20 +#define IDS_DO_NOT_LOOP 21 +#define IDS_LOOP_FOREVER 22 +#define IDS_LOOP_X_TIMES 23 +#define IDS_PREFERENCES_TITLE 24 +#define IDS_SAMPLES 26 +#define IDS_INSTRUMENTS 27 +#define IDS_COMMENT 28 +#define IDS_YES 29 +#define IDS_NO 30 +#define IDS_SUSTAIN 31 +#define IDS_FINETUNE 32 +#define IDS_HZ_SIGNED 33 +#define IDS_HZ_UNSIGNED 34 +#define IDS_PING_PONG 35 +#define IDS_REVERSE 36 +#define IDS_FORWARD 37 +#define IDS_SUSTAIN_PING_PONG 38 +#define IDS_NONE 39 +#define IDS_X_X_X_OF_X_NOT_PLAYING 40 +#define IDS_X_X_X_OF_X_X_OF_X_X_OF_X 41 +#define IDS_X_BTYES_X_OF_X_MINUTES 42 +#define IDS_URLS_ONLY_SUPPORTED_IN_2_10_PLUS 43 +#define IDS_MOD_PLUGIN_ERROR 44 +#define IDS_SAVE_MODULE 45 +#define IDS_ALL_FILES 46 +#define IDS_RETRIEVING_MODULE 47 +#define IDS_ERROR_KILLING_DECODING_THREAD 48 +#define IDS_LOOP 49 +#define IDS_ON 50 +#define IDS_OFF 51 +#define IDS_BYTES 52 +#define IDS_BYTES2 53 +#define IDS_BITS 53 +#define IDS_FAMILY_STRING_COMPOSER_669 54 +#define IDS_FAMILY_STRING_DSMI_AMF 55 +#define IDS_FAMILY_STRING_FARANDOLE_COMPOSER 56 +#define IDS_FAMILY_STRING_IMPULSETRACKER 57 +#define IDS_FAMILY_STRING_SOUNDTRACKER 58 +#define IDS_FAMILY_STRING_PROTRACKER 59 +#define IDS_FAMILY_STRING_MULTITRACKER 60 +#define IDS_FAMILY_STRING_AMIGA_OKTALYZER 61 +#define IDS_FAMILY_STRING_POLYTRACKER 62 +#define IDS_FAMILY_STRING_SCREAMTRACKER3 63 +#define IDS_FAMILY_STRING_SCREAMTRACKER2 64 +#define IDS_FAMILY_STRING_ULTRATRACKER 65 +#define IDS_FAMILY_STRING_FASTRACKER2 66 +#define IDS_FAMILY_STRING_NOISETRACKER 67 +#define IDS_X_MODULE 68 +#define IDS_X_COMPRESSED_MODULE 69 +#define IDS_ABOUT_TEXT 70 +#define IDS_CORRUPT_UNSUPPORTED_TYPE 71 +#define IDS_NULLSOFT_DS_OUTPUT 72 +#define IDD_ID3EDIT 101 +#define IDD_SAMPLES 108 +#define IDD_COMMENT 109 +#define IDD_INSTRUMENTS 111 +#define IDD_PREFS 116 +#define IDD_PREFTAB_DECODER 118 +#define IDD_PREFTAB_MIXER 119 +#define IDD_PREFTAB_LOADER 123 +#define IDC_PREFS_PRIORITY_DECODE 1076 +#define IDC_TYPE 1078 +#define IDC_TITLE 1079 +#define IDC_ID3_FN 1090 +#define OQ_INTERP 1100 +#define IDLDR_EFFOPT1 1102 +#define OQ_NOCLICK 1103 +#define IDC_SAVESTR 1105 +#define IDC_VOICES 1106 +#define IDC_INSTLIST 1108 +#define TB_SAMPLELIST 1109 +#define IDC_SAMPLIST 1110 +#define IDC_INFORIGHT 1111 +#define IDC_INFOLEFT 1112 +#define IDC_TAB 1113 +#define IDC_SAMPINFO 1115 +#define CEMENT_BOX 1116 +#define IDC_INSTENV 1118 +#define IDC_INSTHEAD 1120 +#define IDC_ID3FORMAT 1127 +#define MM_PREFTAB 1132 +#define IDC_PANSEP 1132 +#define IDC_DEFPAN 1137 +#define OQ_QUALITY 1139 +#define IDLDR_LIST 1140 +#define IDLDR_PANPOS 1141 +#define IDLDR_ENABLED 1142 +#define IDLDR_EFFOPT2 1144 +#define IDLDR_DESCRIPTION 1145 +#define IDC_LOOPALL 1146 +#define IDC_CONT_LOOP 1147 +#define IDC_FADEOUT 1148 +#define IDC_FADESPIN 1149 +#define IDC_PLAYALL 1150 +#define IDC_RESONANCE 1151 +#define IDC_FADECHECK 1152 +#define IDC_FADESEC 1153 +#define IDC_STRIPSILENCE 1154 +#define IDC_SEEKBYORDERS 1155 +#define IDC_TITLE_FORMAT 1156 +#define IDC_TAGZ_HELP 1157 +#define OQ_CUBIC 1158 +#define IDC_TAGZ_DEF 1158 +#define IDC_SAMPLESIZE 1159 +#define IDC_TITLE_FORMAT2 1159 +#define IDC_LOOPTEXT 1160 +#define IDC_LOOPS 1161 +#define IDC_TRACK 1162 +#define IDC_ADV_TEXT_INFO 1163 +#define OUTMODE_STEREO 1201 +#define OUTMODE_SURROUND 1202 +#define OUTMODE_REVERSE 1203 +#define IDS_NULLSOFT_MODULE_DECODER 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 130 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1164 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_mod/mikamp/src/Config.c b/Src/Plugins/Input/in_mod/mikamp/src/Config.c new file mode 100644 index 00000000..75ba79e4 --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/src/Config.c @@ -0,0 +1,1320 @@ +/* + Mikmod for Winamp + + By Jake Stine and Justin Frankel. Really. + + Done in 1998, 1999. The millenium cometh! + Done more in 2000. The millenium passeth by! + Redone lots in 2001. The REAL millenium cometh! haha. +*/ + +#include "api.h" +extern "C" { +#include "main.h" +} +#include <shlobj.h> +#include <commctrl.h> +#include "../../winamp/wa_ipc.h" +#include <strsafe.h> +#include "../nu/AutoWide.h" + +#define TabCtrl_InsertItemW(hwnd, iItem, pitem) \ + (int)SNDMSG((hwnd), TCM_INSERTITEMW, (WPARAM)(int)(iItem), (LPARAM)(const TC_ITEMW *)(pitem)) + +#define C_PAGES 4 + +#define CFG_UNCHANGED (-1) + +#define EFFECT_8XX (1ul<<0) +#define EFFECT_ZXX (1ul<<1) + +#define CPUTBL_COUNT 23 +#define MIXTBL_COUNT 7 +#define VOICETBL_COUNT 8 +#define DEF_SRATE 44100 + + +typedef struct tag_cdlghdr +{ + HWND hwndTab; // tab control + HWND hwndDisplay; // current child dialog box + int left,top; + HWND apRes[C_PAGES]; + +} CFG_DLGHDR; + + +// Winamp Stuff +UBYTE config_savestr = 0; // save stream to disk + +// Output Settings +UBYTE config_nch = 2; +UBYTE config_cpu; + +// Player Settings... +// ------------------ + +int config_loopcount = 0; // Auto-looping count, if the song has a natural loop. +uint config_playflag = 0; // See CPLAYFLG_* defines above for flags +int config_pansep = 128; // master panning separation (0 == mono, 128 == stereo, 512 = way-separate) +UBYTE config_resonance = 1; +int config_fadeout = 1000; // fadeout when the song is ending last loop (in ms) + +// Mixer Settings... +// ----------------- + +UBYTE config_panrev = 0, // Reverse panning (left<->right) + config_interp = 3; // interpolation (bit 0) / noclick (bit 1) / cubic (bit 2) +uint config_srate = DEF_SRATE; +uint config_voices = 96; // maximum voices player can use. + + +// Local Crud... +// ------------- + +static const char *INI_FILE; +static const char app_name[] = "Nullsoft Module Decoder"; + +static int l_quality, l_srate, // l_quality indexes mixertbl. l_srate is the actual number. + l_voices; +static BOOL l_useres, l_nch, + l_iflags; +int config_tsel = 0; + + +static uint voicetable[VOICETBL_COUNT] = { 24, 32, 48, 64, 96, 128, 256, 512 }; +static uint mixertbl[MIXTBL_COUNT] = +{ + 48000, + 44100, + 33075, + 22050, + 16000, + 11025, + 8000, +}; + + +// Define Extended Loader Details/Options +// -------------------------------------- + +typedef struct _MLCONF +{ + CHAR *cfgname; // configuration name prefix. + INT desc_id; // long-style multi-line description! + CHAR *exts; + MLOADER *loader; // mikmod loader to register + BOOL enabled; + uint defpan, // default panning separation \ range 0 - 128 + pan; // current panning separation / (mono to stereo) + uint optavail, // available advanced effects options + optset; // current settings for those options. +} MLCONF; + + +static MLCONF c_mod[] = +{ + "mod", + IDS_PROTRACKER_AND_CLONES, + "mod;mdz;nst", + &load_mod, + TRUE, + + 0,0, + EFFECT_8XX, 0, + + "m15", + IDS_OLD_SKOOL_AMIGA_MODULES, + "mod;mdz", + &load_m15, + TRUE, + + 0,0, + EFFECT_8XX, 0, + + "stm", + IDS_SCREAM_TRACKER_2XX, + "stm;stz", + &load_stm, + TRUE, + + 0,0, + EFFECT_8XX | EFFECT_ZXX, 0, + + "st3", + IDS_SCREAM_TRACKER_3XX, + "s3m;s3z", + &load_s3m, + TRUE, + + 32,32, + EFFECT_8XX | EFFECT_ZXX, 0, + + + "it", + IDS_IMPULSE_TRACKER, + "it;itz", + &load_it, + TRUE, + + 0,0, + EFFECT_ZXX, 0, + + "ft2", + IDS_FASTTRACKER_2XX, + "xm;xmz", + &load_xm, + TRUE, + + 0,0, + 0, 0, + + "mtm", + IDS_MULTITRACKER, + "mtm", + &load_mtm, + TRUE, + + 0,0, + EFFECT_8XX, 0, + + "ult", + IDS_ULTRA_TRACKER, + "ult", + &load_ult, + TRUE, + + 0,0, + EFFECT_8XX, 0, + + "669", + IDS_COMPOSER_669, + "669", + &load_669, + TRUE, + + 0,0, + 0, 0, + + "far", + IDS_FARANDOLE_COMPOSER, + "far", + &load_far, + TRUE, + + 0,0, + 0, 0, + + "amf", + IDS_DIGITAL_SOUND_AND_MUSIC_INTERFACE, + "amf", + &load_amf, + TRUE, + + 0,0, + 0, 0, + + "okt", + IDS_AMIGA_OKTALYZER, + "okt", + &load_okt, + TRUE, + + 0, 0, + 0, 0, + + "ptm", + IDS_POLYTRACKER, + "ptm", + &load_ptm, + TRUE, + + 0, 0, + EFFECT_8XX, 0, +}; + +#define C_NUMLOADERS (sizeof(c_mod)/sizeof(c_mod[0])) + +static MLCONF l_mod[C_NUMLOADERS]; // local copy, for cancelability + + +// ===================================================================================== + static int _r_i(char *name, int def) +// ===================================================================================== +{ + name += 7; + return GetPrivateProfileInt(app_name,name,def,INI_FILE); +} +#define RI(x) (( x ) = _r_i(#x,( x ))) + + +// ===================================================================================== + static void _w_i(char *name, int d) +// ===================================================================================== +{ + char str[120] = {0}; + StringCchPrintf(str,100,"%d",d); + name += 7; + WritePrivateProfileString(app_name,name,str,INI_FILE); +} +#define WI(x) _w_i(#x,( x )) + + +// ===================================================================================== + static void config_buildbindings(CHAR *datext) +// ===================================================================================== +// Creates the binding string. +{ +#define SEP_CHAR ';' + + uint i; + char *base = datext; + + base[0] = 0; + + for (i=0; i<C_NUMLOADERS; i++) + if (c_mod[i].enabled) + { + char *ext = c_mod[i].exts; + + for (;;) + { + char temp[64] = {0}; + int len; + // find next + char *next = strchr(ext, SEP_CHAR), *s; + + if (next) + len = next - ext; + else len = strlen(ext); + memcpy(temp, ext, len); + temp[len++] = SEP_CHAR; + temp[len] = 0; + // check for duplicate exts + s = strstr(base, temp); + if (!s || (s!=base && *(s-1)!=SEP_CHAR)) + { + memcpy(datext, temp, len); + datext += len; + *datext = 0; // prevent using old content + } + // advance + if (!next) + break; + ext = next + 1; + } + } + + if (datext > base) + { + *(datext-1) = 0; + StringCchCopy(datext,4096,WASABI_API_LNGSTRING(IDS_MUSIC_MODULES)); + } +} + + +// ===================================================================================== + static void config_init(void) +// ===================================================================================== +{ + INI_FILE = (const char *)SendMessage(mikmod.hMainWindow, WM_WA_IPC, 0, IPC_GETINIFILE); +} + + +// ===================================================================================== + void config_read(void) +// ===================================================================================== +{ + uint t; + + config_init(); + + + RI(config_savestr); + + RI(config_nch); + RI(config_srate); + RI(config_interp); + + RI(config_voices); + + RI(config_loopcount); + RI(config_playflag); + RI(config_resonance); + RI(config_fadeout); + + RI(config_pansep); + RI(config_panrev); + + RI(config_info_x); + RI(config_info_y); + RI(config_track); + RI(config_tsel); + + config_cpu = _mm_cpudetect(); + + // Load settings for each of the individual loaders + // ------------------------------------------------ + + for(t=0; t<C_NUMLOADERS; t++) + { + CHAR stmp[72] = {0}; + + StringCchPrintf(stmp,72,"%s%s",c_mod[t].cfgname,"enabled"); + c_mod[t].enabled = GetPrivateProfileInt(app_name,stmp,c_mod[t].enabled,INI_FILE); + + StringCchPrintf(stmp,72,"%s%s",c_mod[t].cfgname,"panning"); + c_mod[t].pan = GetPrivateProfileInt(app_name, stmp, c_mod[t].defpan, INI_FILE); + c_mod[t].pan = _mm_boundscheck(c_mod[t].pan, 0, 128); + + StringCchPrintf(stmp,72,"%s%s",c_mod[t].cfgname,"effects"); + c_mod[t].optset = GetPrivateProfileInt(app_name,stmp,c_mod[t].optset,INI_FILE); + + // configure the loaders + c_mod[t].loader->enabled = c_mod[t].enabled; + c_mod[t].loader->defpan = c_mod[t].pan; + c_mod[t].loader->nopaneff = (c_mod[t].optset | EFFECT_8XX) ? FALSE : TRUE; + c_mod[t].loader->noreseff = (c_mod[t].optset | EFFECT_ZXX) ? FALSE : TRUE; + } + + config_buildbindings(mikmod.FileExtensions); + + // Bounds checking! + // ---------------- + // This is important to ensure stability of the product in case some + // doof goes and starts hacking the ini values carelessly - or if some sort + // of version conflict or corruption causes skewed readings. + + config_pansep = _mm_boundscheck(config_pansep, 0, 512); + config_voices = _mm_boundscheck(config_voices, 2, 1024); + + config_fadeout = _mm_boundscheck(config_fadeout, 0, 1000l*1000l); + config_loopcount = _mm_boundscheck(config_loopcount, -1, 63); +} + + +// ===================================================================================== + void config_write(void) +// ===================================================================================== +{ + uint t = 0; + + WI(config_savestr); + + WI(config_nch); + WI(config_srate); + WI(config_interp); + WI(config_voices); + + WI(config_loopcount); + WI(config_playflag); + WI(config_resonance); + WI(config_fadeout); + + WI(config_pansep); + WI(config_panrev); + + WI(config_info_x); + WI(config_info_y); + WI(config_track); + WI(config_tsel); + + // Save settings for each of the individual loaders + // ------------------------------------------------ + + for(; t < C_NUMLOADERS; t++) + { + CHAR stmp[72] = {0}, sint[12] = {0}; + + StringCchPrintf(stmp,72,"%s%s",c_mod[t].cfgname,"enabled"); + StringCchPrintf(sint,12,"%d",c_mod[t].enabled); + WritePrivateProfileString(app_name,stmp,sint,INI_FILE); + + StringCchPrintf(stmp,72,"%s%s",c_mod[t].cfgname,"panning"); + StringCchPrintf(sint,12,"%d",c_mod[t].pan); + WritePrivateProfileString(app_name,stmp,sint,INI_FILE); + + StringCchPrintf(stmp,72,"%s%s",c_mod[t].cfgname,"effects"); + StringCchPrintf(sint,12,"%d",c_mod[t].optset); + WritePrivateProfileString(app_name,stmp,sint,INI_FILE); + + // configure the loaders + c_mod[t].loader->enabled = c_mod[t].enabled; + c_mod[t].loader->nopaneff = (c_mod[t].optset | EFFECT_8XX) ? FALSE : TRUE; + c_mod[t].loader->noreseff = (c_mod[t].optset | EFFECT_ZXX) ? FALSE : TRUE; + } + + config_buildbindings(mikmod.FileExtensions); +} + + +static BOOL CALLBACK prefsProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam); + + +// ===================================================================================== + void __cdecl config(HWND hwndParent) +// ===================================================================================== +{ + WASABI_API_DIALOGBOXW(IDD_PREFS,hwndParent,prefsProc); + config_write(); +} + + +static BOOL CALLBACK tabProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +static BOOL CALLBACK mixerProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +static BOOL CALLBACK loaderProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +static void OnSelChanged(HWND hwndDlg, int initonly); + +// ===================================================================================== + static void prefsTabInit(HWND hwndDlg, CFG_DLGHDR *pHdr) +// ===================================================================================== +{ + DWORD dwDlgBase = GetDialogBaseUnits(); + int cxMargin = LOWORD(dwDlgBase) / 4, + cyMargin = HIWORD(dwDlgBase) / 8; + TC_ITEMW tie; + int tabCounter; + + tie.mask = TCIF_TEXT | TCIF_IMAGE; + tie.iImage = -1; + + tabCounter = 0; + + SendMessage(mikmod.hMainWindow,WM_WA_IPC,(WPARAM)pHdr->hwndTab,IPC_USE_UXTHEME_FUNC); + + tie.pszText = WASABI_API_LNGSTRINGW(IDS_MIXER); + TabCtrl_InsertItemW(pHdr->hwndTab, tabCounter, &tie); + pHdr->apRes[tabCounter] = WASABI_API_CREATEDIALOGPARAMW(IDD_PREFTAB_MIXER, hwndDlg, mixerProc, IDD_PREFTAB_MIXER); + SetWindowPos(pHdr->apRes[tabCounter], HWND_TOP, pHdr->left, pHdr->top, 0, 0, SWP_NOSIZE); + SendMessage(mikmod.hMainWindow,WM_WA_IPC,(WPARAM)pHdr->apRes[tabCounter],IPC_USE_UXTHEME_FUNC); + ShowWindow(pHdr->apRes[tabCounter++], SW_HIDE); + + tie.pszText = WASABI_API_LNGSTRINGW(IDS_PLAYER); + TabCtrl_InsertItemW(pHdr->hwndTab, tabCounter, &tie); + pHdr->apRes[tabCounter] = WASABI_API_CREATEDIALOGPARAMW(IDD_PREFTAB_DECODER, hwndDlg, tabProc, IDD_PREFTAB_DECODER); + SetWindowPos(pHdr->apRes[tabCounter], HWND_TOP, pHdr->left, pHdr->top, 0, 0, SWP_NOSIZE); + SendMessage(mikmod.hMainWindow,WM_WA_IPC,(WPARAM)pHdr->apRes[tabCounter],IPC_USE_UXTHEME_FUNC); + ShowWindow(pHdr->apRes[tabCounter++], SW_HIDE); + + tie.pszText = WASABI_API_LNGSTRINGW(IDS_LOADERS); + TabCtrl_InsertItemW(pHdr->hwndTab, tabCounter, &tie); + pHdr->apRes[tabCounter] = WASABI_API_CREATEDIALOGPARAMW(IDD_PREFTAB_LOADER, hwndDlg, loaderProc, IDD_PREFTAB_LOADER); + SetWindowPos(pHdr->apRes[tabCounter], HWND_TOP, pHdr->left, pHdr->top, 0, 0, SWP_NOSIZE); + SendMessage(mikmod.hMainWindow,WM_WA_IPC,(WPARAM)pHdr->apRes[tabCounter],IPC_USE_UXTHEME_FUNC); + ShowWindow(pHdr->apRes[tabCounter++], SW_HIDE); + + // Simulate selection of the first item. + OnSelChanged(hwndDlg,1); +} + + +// ===================================================================================== + static void OnSelChanged(HWND hwndDlg, int initonly) +// ===================================================================================== +{ + CFG_DLGHDR *pHdr = (CFG_DLGHDR *) GetWindowLong(hwndDlg, GWL_USERDATA); + if(!initonly) + config_tsel = TabCtrl_GetCurSel(pHdr->hwndTab); + else + TabCtrl_SetCurSel(pHdr->hwndTab,config_tsel); + + if(pHdr->hwndDisplay) ShowWindow(pHdr->hwndDisplay,SW_HIDE); + ShowWindow(pHdr->apRes[config_tsel],SW_SHOW); + pHdr->hwndDisplay = pHdr->apRes[config_tsel]; + +} + + +// ===================================================================================== + static void FadeOutSetup(HWND hwndDlg, BOOL enabled) +// ===================================================================================== +{ + EnableWindow(GetDlgItem(hwndDlg, IDC_FADEOUT), enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_FADESEC), enabled); +} + + +// ===================================================================================== + static void Stereo_Dependencies(HWND hwndDlg) +// ===================================================================================== +// Enable or Disable the options which are dependant on stereo being enabled +{ + BOOL val = (l_nch==1) ? 0 : 1; + EnableWindow(GetDlgItem(hwndDlg,OUTMODE_REVERSE), val); + EnableWindow(GetDlgItem(hwndDlg,OUTMODE_SURROUND),val); +} + + +// ===================================================================================== + static void SetVoiceList(HWND hwndMisc) +// ===================================================================================== +// erg is the current quality mode (indexes mixertbl). +{ + uint i,k; + BOOL picked = FALSE; + uint cfgv; + + cfgv = (l_voices==CFG_UNCHANGED) ? config_voices : voicetable[l_voices]; + + SendMessage(hwndMisc,CB_RESETCONTENT,0,0); + + for(i=0; i<VOICETBL_COUNT; i++) + { CHAR buf[24] = {0}; + + // Find the appropriate megaherz, by doing a pretty wacky little logic snippet + // which matches up the multipled mhz with the closet legit CPU bracket (as + // listed in cputable). + + StringCchPrintf(buf,24,"%s%d",(voicetable[i]<100) ? " " : "", voicetable[i]); + + SendMessage(hwndMisc,CB_ADDSTRING,0,(LPARAM)buf); + if(!picked && (voicetable[i] >= cfgv)) + { k = i; picked = TRUE; } + } + + // If picked is false, then set to 96 (default) + + if(!picked) k = 4; + + SendMessage(hwndMisc,CB_SETCURSEL,k,0); +} + + +// ===================================================================================== + static BOOL cmod_and_the_moo(HWND dagnergit, int *moo, int count, uint flag) +// ===================================================================================== +{ + int l; + BOOL oneway, otherway, thisway, thatway; + + oneway = otherway = thisway = thatway = FALSE; + + // Set oneway/otherway true for selected/unselected options. + // Set thisway/thatway true for avail/nonawail options. + + for(l=0; l<count; l++) + { if(l_mod[moo[l]].optavail & flag) + { thisway = TRUE; + if(l_mod[moo[l]].optset & flag) oneway = TRUE; else otherway = TRUE; + } else thatway = TRUE; + } + + EnableWindow(dagnergit, thisway); + SendMessage(dagnergit, BM_SETCHECK, (!thisway || (oneway != otherway)) ? (oneway ? BST_CHECKED : BST_UNCHECKED) : BST_INDETERMINATE, 0); + return thisway; +} + + +// ===================================================================================== + static BOOL CALLBACK mixerProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +// ===================================================================================== +{ + + switch (uMsg) + { + // ============================================================================= + case WM_INITDIALOG: + // ============================================================================= + // Windows dialog box startup message. This is messaged for each tab form created. + // Initialize all of the controls on each of those forms! + { + HWND hwndMisc; + CFG_DLGHDR *pHdr = (CFG_DLGHDR *)GetWindowLong(GetParent(hwndDlg), GWL_USERDATA); + + CheckDlgButton(hwndDlg, IDC_SAVESTR, config_savestr ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg,OQ_INTERP, (config_interp & 1) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg,OQ_NOCLICK, (config_interp & 2) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg,OQ_CUBIC, (config_interp & 4) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg,OUTMODE_REVERSE, config_panrev ? BST_CHECKED : BST_UNCHECKED); + + l_voices = CFG_UNCHANGED; + l_srate = config_srate; + l_iflags = config_interp; + l_nch = GetNumChannels(); + l_useres = config_resonance; + + hwndMisc = GetDlgItem(hwndDlg,OQ_QUALITY); + + { + uint erg, i, def=0; + BOOL picked = FALSE; + + for(i=0; i<MIXTBL_COUNT; i++) + { + wchar_t buf[24] = {0}; + StringCchPrintfW(buf,24,L"%d%s", mixertbl[i], mixertbl[i]==DEF_SRATE ? (def=i, WASABI_API_LNGSTRINGW(IDS_DEFAULT)) : L""); + SendMessageW(hwndMisc,CB_ADDSTRING,0,(LPARAM)buf); + if (!picked && mixertbl[i]<config_srate) + { + erg = i ? (i-1) : 0; + picked = TRUE; + } + } + // If picked is false, then set to default + if(!picked) erg = def; + SendMessage(hwndMisc,CB_SETCURSEL,erg,0); + l_quality = erg; + } + + SetVoiceList(GetDlgItem(hwndDlg,IDC_VOICES)); + Stereo_Dependencies(hwndDlg); + + return TRUE; + } + break; + + // ============================================================================= + case WM_COMMAND: + // ============================================================================= + // Process commands and notification messages recieved from our child controls. + + switch(LOWORD(wParam)) + { case IDOK: + { + + if(l_voices != CFG_UNCHANGED) config_voices = voicetable[l_voices]; + + config_srate = l_srate; + config_interp = l_iflags; + config_resonance = l_useres; + + config_panrev = IsDlgButtonChecked(hwndDlg,OUTMODE_REVERSE) ? 1 : 0; + + config_voices = _mm_boundscheck(config_voices, 2,1024); + + config_savestr = IsDlgButtonChecked(hwndDlg,IDC_SAVESTR) ? 1 : 0; + } + break; + + // Output Quality / Mixing Performance + // ----------------------------------- + // From here on down we handle the messages from those controls which affect + // the performance (and therefore the cpu requirements) of the module decoder. + // Each one assigns values into a temp variable (l_*) so that if the user + // cancels the changes are not saved. + + + case OQ_INTERP: + case OQ_CUBIC: + case OQ_NOCLICK: + if(HIWORD(wParam) == BN_CLICKED) + { l_iflags = IsDlgButtonChecked(hwndDlg,OQ_INTERP) ? 1 : 0; + l_iflags |= IsDlgButtonChecked(hwndDlg,OQ_NOCLICK) ? 2 : 0; + l_iflags |= IsDlgButtonChecked(hwndDlg,OQ_CUBIC) ? 4 : 0; + SetVoiceList(GetDlgItem(hwndDlg,IDC_VOICES)); + } + break; + + case IDC_VOICES: + if(HIWORD(wParam) == CBN_SELCHANGE) + { int taxi = SendMessage((HWND)lParam,CB_GETCURSEL,0,0); + l_voices = taxi; + } + break; + + case OQ_QUALITY: + if(HIWORD(wParam) == CBN_SELCHANGE) + { int taxi = SendMessage((HWND)lParam,CB_GETCURSEL,0,0); + l_quality = taxi; + l_srate = mixertbl[l_quality]; + SetVoiceList(GetDlgItem(hwndDlg,IDC_VOICES)); + } + break; + } + break; + } + return 0; +} + + +// ===================================================================================== + static BOOL CALLBACK loaderProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +// ===================================================================================== +// This is the callback procedure used by each of the three forms under the tab control +// on the Preferences dialog box. It handles all the messages for all of the controls +// within those dialog boxes. +{ + switch (uMsg) + { + // ============================================================================= + case WM_INITDIALOG: + // ============================================================================= + // Windows dialog box startup message. This is messaged for each tab form created. + // Initialize all of the controls on each of those forms! + { + HWND hwndMisc; + CFG_DLGHDR *pHdr = (CFG_DLGHDR *)GetWindowLong(GetParent(hwndDlg), GWL_USERDATA); + + uint i; + + // Set the range on the panning slider (0 to 16) + + hwndMisc = GetDlgItem(hwndDlg,IDLDR_PANPOS); + SendMessage(hwndMisc,TBM_SETRANGEMAX,0,16); + SendMessage(hwndMisc,TBM_SETRANGEMIN,0,0); + SendMessage(hwndMisc,TBM_SETPOS, 1, 16); + + // Build our list of loaders in the loader box + // ------------------------------------------- + // TODO: eventually I would like to display little checkmarks (or llamas or + // something) in this list to indicate (at a glance) which are enabled and + // which are not. + + hwndMisc = GetDlgItem(hwndDlg,IDLDR_LIST); + + for (i=0; i<C_NUMLOADERS; i++) + { + l_mod[i] = c_mod[i]; + SendMessageW(hwndMisc, LB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW(c_mod[i].loader->DescStrID)); + } + + SendMessage(hwndMisc, LB_SETCURSEL, 0, 0); + loaderProc(hwndDlg, WM_COMMAND, (WPARAM)((LBN_SELCHANGE << 16) + IDLDR_LIST), (LPARAM)hwndMisc); + + if (NULL != WASABI_API_APP) + WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(hwndMisc, TRUE); + } + return TRUE; + + case WM_DESTROY: + { + HWND hwndMisc = GetDlgItem(hwndDlg, IDLDR_LIST); + if (NULL != WASABI_API_APP) + WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(hwndMisc, FALSE); + } + break; + + // ============================================================================= + case WM_COMMAND: + // ============================================================================= + // Process commands and notification messages recieved from our child controls. + + switch(LOWORD(wParam)) + { + case IDOK: + { uint i; + for (i=0; i<C_NUMLOADERS; i++) + { + c_mod[i] = l_mod[i]; + c_mod[i].loader->defpan = c_mod[i].pan; + } + } + break; + + case IDLDR_LIST: + + // The Loader Box Update Balloofie + // ------------------------------- + // Updates the various controls on the 'loader tab' dialog box. Involves + // enabling/disabling advanced-effects boxes, checking the Enabled box, and + // setting the panning position. Also: extra care is taken to allow proper + // and intuitive support for multiple selections! + + if(HIWORD(wParam) == LBN_SELCHANGE) + { int moo[C_NUMLOADERS], count,l; + BOOL oneway, otherway, opt1, opt2; + HWND beiownd; + + // Fetch the array of selected items! + + count = SendMessage((HWND)lParam, LB_GETSELITEMS, C_NUMLOADERS, (LPARAM)moo); + if(!count || (count == LB_ERR)) + { + // Something's not right, so just disable all the controls. + + SetWindowTextW(GetDlgItem(hwndDlg, IDLDR_DESCRIPTION), WASABI_API_LNGSTRINGW(IDS_SELECT_ANY_LOADER)); + EnableWindow(beiownd = GetDlgItem(hwndDlg, IDLDR_PANPOS), FALSE); + EnableWindow(beiownd = GetDlgItem(hwndDlg, IDLDR_ENABLED), FALSE); + SendMessage(hwndDlg, BM_GETCHECK, BST_UNCHECKED,0); + EnableWindow(beiownd = GetDlgItem(hwndDlg, IDLDR_EFFOPT1), FALSE); + SendMessage(hwndDlg, BM_GETCHECK, BST_UNCHECKED,0); + EnableWindow(beiownd = GetDlgItem(hwndDlg, IDLDR_EFFOPT2), FALSE); + SendMessage(hwndDlg, BM_GETCHECK, BST_UNCHECKED,0); + EnableWindow(beiownd = GetDlgItem(hwndDlg, IDC_DEFPAN), FALSE); + + break; + } + + SetWindowTextW(GetDlgItem(hwndDlg, IDLDR_DESCRIPTION), + WASABI_API_LNGSTRINGW( + (count==1) ? l_mod[moo[0]].desc_id : IDS_MULTIPLE_ITEMS_SELECTED)); + + // Enabled Box : First of Many + // --------------------------- + + oneway = otherway = FALSE; + for(l=0; l<count; l++) + if(l_mod[moo[l]].enabled) oneway = TRUE; else otherway = TRUE; + + EnableWindow(beiownd = GetDlgItem(hwndDlg, IDLDR_ENABLED), TRUE); + SendMessage(beiownd, BM_SETCHECK, (oneway != otherway) ? (oneway ? BST_CHECKED : BST_UNCHECKED) : BST_INDETERMINATE, 0); + + + // The PanningPos : Second in Command + // ---------------------------------- + // Only set it if we have a single format selected, otherwise... erm.. + // do something (to be determined!) + + beiownd = GetDlgItem(hwndDlg, IDC_DEFPAN); + EnableWindow(beiownd, TRUE); + beiownd = GetDlgItem(hwndDlg, IDLDR_PANPOS); + EnableWindow(beiownd, TRUE); + if(count==1) + SendMessage(beiownd, TBM_SETPOS, TRUE, 16-((l_mod[moo[0]].pan+1)>>3)); + + + // 8xx Panning Disable: Third of Four + // Zxx Resonance: All the Duckies are in a Row! + // -------------------------------------------- + + opt1 = cmod_and_the_moo(GetDlgItem(hwndDlg, IDLDR_EFFOPT1), moo, count, EFFECT_8XX); + opt2 = cmod_and_the_moo(GetDlgItem(hwndDlg, IDLDR_EFFOPT2), moo, count, EFFECT_ZXX); + EnableWindow(GetDlgItem(hwndDlg, IDC_ADV_TEXT_INFO), opt1 || opt2); + } + break; + + case IDLDR_ENABLED: + case IDC_DEFPAN: + case IDLDR_EFFOPT1: + case IDLDR_EFFOPT2: + + if(HIWORD(wParam) == BN_CLICKED) + { int moo[C_NUMLOADERS],count; + int res = SendMessage((HWND)lParam,BM_GETCHECK,0,0); + + count = SendMessage(GetDlgItem(hwndDlg, IDLDR_LIST), LB_GETSELITEMS, C_NUMLOADERS, (LPARAM)moo); + + + switch(res) + { + case BST_CHECKED: + SendMessage((HWND)lParam,BM_SETCHECK,BST_UNCHECKED,0); + res = 0; + break; + + case BST_INDETERMINATE: + case BST_UNCHECKED: + SendMessage((HWND)lParam,BM_SETCHECK,BST_CHECKED,0); + res = 1; + break; + } + + if (LOWORD(wParam) == IDLDR_ENABLED) + { int l; + for(l=0; l<count; l++) l_mod[moo[l]].enabled = res; + } + else if (LOWORD(wParam) == IDC_DEFPAN) + { + int l; + + for (l=0; l<count; l++) + l_mod[moo[l]].pan = l_mod[moo[l]].defpan; + + if (count==1) + SendMessage(GetDlgItem(hwndDlg, IDLDR_PANPOS), TBM_SETPOS, TRUE, 16-((l_mod[moo[0]].pan+1)>>3)); + } + else + { + uint flag = (LOWORD(wParam) == IDLDR_EFFOPT1) ? EFFECT_8XX : EFFECT_ZXX; + int l; + + for(l=0; l<count; l++) + { if(l_mod[moo[l]].optavail & flag) + { if(res) + l_mod[moo[l]].optset |= flag; + else + l_mod[moo[l]].optset &= ~flag; + } + } + } + } + break; + } + break; + + // ============================================================================= + case WM_HSCROLL: + // ============================================================================= + // Whitness Stupidness! + // Microsoft decides it would be this "brilliant move' to make trackbars send + // WM_HSCROLL and WM_VSCROLL messages only! Like, who the hell uses a trackbar + // as a scroller anyway? Whatever happened to the 'standard' system of command/ + // notify messages? Grrr. + + // Oh look, the LOWORD is the command this time, as opposed to notifies, where the + // HIWORD is the command. Jesus fucking Christ I'm in loonyland around here. + + if(LOWORD(wParam) == TB_THUMBPOSITION) + { + int moo[C_NUMLOADERS],count,l; + count = SendMessage(GetDlgItem(hwndDlg, IDLDR_LIST), LB_GETSELITEMS, C_NUMLOADERS, (LPARAM)moo); + + for(l=0; l<count; l++) + { l_mod[moo[l]].pan = (16 - HIWORD(wParam)) << 3; + l_mod[moo[l]].pan = _mm_boundscheck(l_mod[moo[l]].pan,0,128); + } + } + } + + const int controls[] = + { + IDC_LOOPS, + IDC_PANSEP, + IDLDR_PANPOS, + }; + if (FALSE != WASABI_API_APP->DirectMouseWheel_ProcessDialogMessage(hwndDlg, uMsg, wParam, lParam, controls, ARRAYSIZE(controls))) + { + return TRUE; + } + + return 0; +} + + +// ===================================================================================== + static void FadeoutSetText(HWND hwndDlg) +// ===================================================================================== +{ + CHAR work[32] = {0}; + StringCchPrintf(work, 32, "%.02f",config_fadeout/1000.0f); + SetDlgItemText(hwndDlg, IDC_FADEOUT, work); +} + + +// ===================================================================================== + static BOOL CALLBACK tabProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +// ===================================================================================== +{ + switch (uMsg) + { + // ============================================================================= + case WM_INITDIALOG: + // ============================================================================= + // Windows dialog box startup message. This is messaged for each tab form created. + // Initialize all of the controls on each of those forms! + { + HWND hwndMisc; + CFG_DLGHDR *pHdr = (CFG_DLGHDR *)GetWindowLong(GetParent(hwndDlg), GWL_USERDATA); + SetWindowLong(hwndDlg,DWL_USER,lParam); + + switch(lParam) + { + case IDD_PREFTAB_DECODER: + SendMessage(GetDlgItem(hwndDlg,IDC_FADEOUT), EM_SETLIMITTEXT, 10,0); + FadeoutSetText(hwndDlg); + + hwndMisc = GetDlgItem(hwndDlg,IDC_PANSEP); + SendMessage(hwndMisc,TBM_SETRANGEMIN,0,0); + SendMessage(hwndMisc,TBM_SETRANGEMAX,0,32); + + { + int erg = config_pansep; + + if (erg <= 128) + erg *= 2; + else erg = 256 + ((erg - 128) * 256) / 384; + + SendMessage(hwndMisc,TBM_SETPOS,1, (erg>>4)&31); + } + + hwndMisc = GetDlgItem(hwndDlg, IDC_LOOPS); + SendMessage(hwndMisc, TBM_SETRANGEMIN, 0, 0); + SendMessage(hwndMisc, TBM_SETRANGEMAX, 0, 64); + SendMessage(hwndMisc, TBM_SETTICFREQ, 4, 0); + SendMessage(hwndMisc, TBM_SETPOS, 1, config_loopcount>=0 ? config_loopcount : 64); + SendMessage(hwndDlg, WM_HSCROLL, 0, (LPARAM)hwndMisc); + + CheckDlgButton(hwndDlg,IDC_LOOPALL, (config_playflag & CPLAYFLG_LOOPALL) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_CONT_LOOP, (config_playflag & CPLAYFLG_CONT_LOOP) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_PLAYALL, (config_playflag & CPLAYFLG_PLAYALL) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_FADECHECK, (config_playflag & CPLAYFLG_FADEOUT) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_STRIPSILENCE,(config_playflag & CPLAYFLG_STRIPSILENCE) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_SEEKBYORDERS,(config_playflag & CPLAYFLG_SEEKBYORDERS) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_RESONANCE,config_resonance ? BST_CHECKED : BST_UNCHECKED); + + FadeOutSetup(hwndDlg, config_playflag & CPLAYFLG_FADEOUT); + return TRUE; + + } + } + break; + + // ============================================================================= + case WM_COMMAND: + // ============================================================================= + // Process commands and notification messages recieved from our child controls. + + switch(LOWORD(wParam)) + { + case IDOK: + { + + switch(lParam) + { + case IDD_PREFTAB_DECODER: + { + CHAR stmp[32] = {0}; + double ftmp; + config_loopcount = SendMessage(GetDlgItem(hwndDlg, IDC_LOOPS), TBM_GETPOS, 0, 0); + if (config_loopcount == 64) config_loopcount = -1; + + GetDlgItemText(hwndDlg, IDC_FADEOUT, stmp, 12); + ftmp = atof(stmp); + config_fadeout = (int)(ftmp * 1000l); + config_fadeout = _mm_boundscheck(config_fadeout, 0, 1000l*1000l); // bound to 1000 seconds. + + { + int erg = SendMessage(GetDlgItem(hwndDlg,IDC_PANSEP),TBM_GETPOS,0,0)<<4; + + if (erg <= 256) + erg /= 2; + else erg = 128 + ((erg - 256) * 384) / 256; + + config_pansep = erg; + } + + config_playflag = IsDlgButtonChecked(hwndDlg,IDC_LOOPALL) ? CPLAYFLG_LOOPALL : 0; + config_playflag |= IsDlgButtonChecked(hwndDlg,IDC_CONT_LOOP) ? CPLAYFLG_CONT_LOOP : 0; + config_playflag |= IsDlgButtonChecked(hwndDlg,IDC_PLAYALL) ? CPLAYFLG_PLAYALL : 0; + config_playflag |= IsDlgButtonChecked(hwndDlg,IDC_FADECHECK) ? CPLAYFLG_FADEOUT : 0; + config_playflag |= IsDlgButtonChecked(hwndDlg,IDC_STRIPSILENCE) ? CPLAYFLG_STRIPSILENCE : 0; + config_playflag |= IsDlgButtonChecked(hwndDlg,IDC_SEEKBYORDERS) ? CPLAYFLG_SEEKBYORDERS : 0; + config_resonance = IsDlgButtonChecked(hwndDlg,IDC_RESONANCE) ? 1 : 0; + } + break; + } + } + break; + + case IDC_FADECHECK: // hide/unhide fadeout controls + if(HIWORD(wParam) == BN_CLICKED) + { int res = SendMessage((HWND)lParam,BM_GETCHECK,0,0); + FadeOutSetup(hwndDlg, res); + } + break; + } + break; + + // ============================================================================= + case WM_HSCROLL: + // ============================================================================= + switch (GetDlgCtrlID((HWND)lParam)) + { + case IDC_LOOPS: + { + wchar_t foo[64] = {0}; + int pos = SendMessage((HWND)lParam, TBM_GETPOS, 0, 0); + HWND hText = GetDlgItem(hwndDlg, IDC_LOOPTEXT); + + if (!pos) + WASABI_API_LNGSTRINGW_BUF(IDS_DO_NOT_LOOP,foo,64); + else if (pos == 64) + WASABI_API_LNGSTRINGW_BUF(IDS_LOOP_FOREVER,foo,64); + else + StringCchPrintfW(foo, 64, WASABI_API_LNGSTRINGW(IDS_LOOP_X_TIMES), pos + 1); + + SetWindowTextW(hText, foo); + } + break; + } + break; + + // ============================================================================= + case WM_NOTIFY: + // ============================================================================= + + switch(LOWORD(wParam)) + { + case IDC_FADESPIN: + { + NMUPDOWN *mud = (NMUPDOWN *) lParam; + + if(mud->hdr.code == UDN_DELTAPOS) + { + // bounds check things between 0-1000secs (added in 2.2.6) + if(mud->iDelta > 0) + { + if(config_fadeout > 0) + config_fadeout -= 250; + } + else + { + if(config_fadeout < 1000l*1000l) + config_fadeout += 250; + } + FadeoutSetText(hwndDlg); + } + } + return TRUE; + } + break; + } + + const int controls[] = + { + IDC_LOOPS, + IDC_PANSEP, + IDLDR_PANPOS, + }; + if (FALSE != WASABI_API_APP->DirectMouseWheel_ProcessDialogMessage(hwndDlg, uMsg, wParam, lParam, controls, ARRAYSIZE(controls))) + { + return TRUE; + } + + return 0; +} + + +// ===================================================================================== + static BOOL CALLBACK prefsProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) +// ===================================================================================== +// This is the procedure which initializes the various forms that make up the tabs in +// our preferences box! This also contains the message handler for the OK and Cancel +// buttons. After that, all messaging is handled by the tab forms themselves in tabProc(); +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + CFG_DLGHDR *pHdr = (CFG_DLGHDR*)calloc(1, sizeof(CFG_DLGHDR)); + SetWindowLong(hwndDlg, GWL_USERDATA, (LONG) pHdr); + pHdr->hwndTab = GetDlgItem(hwndDlg,MM_PREFTAB); + pHdr->left = 8; + pHdr->top = 30; + + prefsTabInit(hwndDlg, pHdr); + + { + wchar_t title[128] = {0}, temp[128] = {0}; + StringCchPrintfW(title, 128, WASABI_API_LNGSTRINGW(IDS_PREFERENCES_TITLE),WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_MODULE_DECODER_OLD,temp,128)); + SetWindowTextW(hwndDlg,title); + } + } + return FALSE; + + + case WM_COMMAND: + switch (LOWORD(wParam)) + { case IDOK: + { + // Send an IDOK command to both tabcontrol children to let them know + // that the world is about to end! + CFG_DLGHDR *pHdr = (CFG_DLGHDR*)GetWindowLong(hwndDlg, GWL_USERDATA); + + SendMessage(pHdr->apRes[0], WM_COMMAND, (WPARAM)IDOK, (LPARAM)IDD_PREFTAB_MIXER); + SendMessage(pHdr->apRes[1], WM_COMMAND, (WPARAM)IDOK, (LPARAM)IDD_PREFTAB_DECODER); + SendMessage(pHdr->apRes[2], WM_COMMAND, (WPARAM)IDOK, (LPARAM)IDD_PREFTAB_LOADER); + + EndDialog(hwndDlg,0); + return 0; + } + + case IDCANCEL: + EndDialog(hwndDlg,0); + return FALSE; + + case OQ_QUALITY: + uMsg = 8; + break; + } + break; + + case WM_NOTIFY: + { NMHDR *notice = (NMHDR *) lParam; + + NMHDR *ack; + uint k; + ack = (NMHDR *)lParam; + + if(ack->hwndFrom == GetDlgItem(hwndDlg,OQ_QUALITY)) + { + switch(ack->code) + { + case CBEN_GETDISPINFO: + k = 1; + break; + } + } + + switch(notice->code) + { case TCN_SELCHANGE: + OnSelChanged(hwndDlg,0); + return TRUE; + } + } + return FALSE; + + case WM_DESTROY: + { + // free local data + free((CFG_DLGHDR*)GetWindowLong(hwndDlg, GWL_USERDATA)); + } + break; + } + return FALSE; +} + + +static int DoAboutMessageBox(HWND parent, wchar_t* title, wchar_t* message) +{ + MSGBOXPARAMSW msgbx = {sizeof(MSGBOXPARAMSW),0}; + msgbx.lpszText = message; + msgbx.lpszCaption = title; + msgbx.lpszIcon = MAKEINTRESOURCEW(102); + msgbx.hInstance = GetModuleHandle(0); + msgbx.dwStyle = MB_USERICON; + msgbx.hwndOwner = parent; + return MessageBoxIndirectW(&msgbx); +} + + +// ===================================================================================== + void __cdecl about(HWND hwndParent) +// ===================================================================================== +{ + wchar_t message[1024] = {0}, text[1024] = {0}; + WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_MODULE_DECODER_OLD,text,1024); + StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), + mikmod.description, TEXT(__DATE__)); + DoAboutMessageBox(hwndParent,text,message); +} + + +static const wchar_t *pExtCompress[] = { L"ITZ", L"MDZ", L"S3Z", L"STZ", L"XMZ" }; +static const wchar_t *pExtReplace[] = { L"IT", L"MOD", L"S3M", L"STM", L"XM" }; + +BOOL GetTypeInfo(LPCWSTR pszType, LPWSTR pszDest, INT cchDest) // return TRUE if typ was found ok +{ + DWORD lcid; + LPCWSTR p(NULL); + wchar_t buf[128]={0}; + int i; + BOOL bCompressed(FALSE); + + lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); + for (i = sizeof(pExtCompress)/sizeof(wchar_t*) - 1; i >= 0 && CSTR_EQUAL != CompareStringW(lcid, NORM_IGNORECASE, pszType, -1,pExtCompress[i], -1); i--); + if (-1 != i) + { + pszType = pExtReplace[i]; + bCompressed = TRUE; + } + + for (i = 0; i < C_NUMLOADERS && !p; i++) + { + if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, pszType, -1, AutoWide(c_mod[i].loader->Type), -1)) + { + p = WASABI_API_LNGSTRINGW_BUF(c_mod[i].loader->DescStrID, buf, 128); + } + } + + if (!p) + { + if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, pszType, -1, L"NST", -1)) + { + p = WASABI_API_LNGSTRINGW_BUF(IDS_FAMILY_STRING_NOISETRACKER, buf, 128); + } + } + + if (p) return (S_OK == StringCchPrintfW(pszDest, cchDest, WASABI_API_LNGSTRINGW((bCompressed?IDS_X_COMPRESSED_MODULE:IDS_X_MODULE)), p)); + return FALSE; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mod/mikamp/src/ExtendedRead.cpp b/Src/Plugins/Input/in_mod/mikamp/src/ExtendedRead.cpp new file mode 100644 index 00000000..71db9ea4 --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/src/ExtendedRead.cpp @@ -0,0 +1,123 @@ +extern "C" +{ +#include "main.h" +} +#include "drv_buffer.h" +#include <bfc/platform/types.h> + +typedef struct +{ + const char *cmd; + const char *file; + const char *title; + int titleLength; + int start; + int startUnit; + int loops; + int flags; +} PlayParams; +extern "C" int GetSampleSizeFlag(); +extern "C" MMSTREAM *_mm_fopen_rf(const CHAR *fname); //rf_wrapper.c +BOOL GetPlayParams(const char *fileName, BOOL open, PlayParams *params); +BOOL InitPlayer(UNIMOD *mf, MPLAYER **ps, const PlayParams *params, BOOL quick); +// TODO; is there a way to get floating point out of this stuff? +extern "C" +{ + __declspec(dllexport) intptr_t winampGetExtendedRead_open(const char *fn, int *size, int *bps, int *nch, int *srate) + { + PlayParams params; + if (!GetPlayParams(fn, FALSE, ¶ms)) + return 0; + + int requested_channels = *nch; + int requested_bits = *bps; + int requested_srate = *srate; + + uint md_mode = 0; + if (config_interp & 1) md_mode |= DMODE_INTERP; + if (config_interp & 2) md_mode |= DMODE_NOCLICK; + if (config_interp & 4) md_mode |= DMODE_FIR; + switch(requested_bits) + { + case 0: + md_mode |= GetSampleSizeFlag(); + break; + case 16: + md_mode |= DMODE_16BITS; + break; + case 24: + md_mode |= DMODE_24BITS; + break; + } + + + if (requested_channels != 1 && requested_channels != 2) md_mode |= DMODE_SURROUND; + if (config_panrev) md_mode |= DMODE_REVERSE; + if (config_resonance) md_mode |= DMODE_RESONANCE; + + MDRIVER *md = Mikmod_Init(requested_srate?requested_srate:config_srate, 0, 0, MD_STEREO, config_cpu, md_mode, &drv_buffer); + MPLAYER *mp; + + MMSTREAM *fp; + fp = _mm_fopen_rf(params.file); + if (!fp) + { + Mikmod_Exit(md); + //CleanupTemp(); + return 0; + } + UNIMOD *mf=Unimod_Load_FP(md, params.file,fp); + _mm_fclose(fp); + if (mf==NULL) + { + Mikmod_Exit(md); + // CleanupTemp(); + return 0; + } + + if (!InitPlayer(mf, &mp, ¶ms, FALSE)) + { + //CleanupTemp(); + Unimod_Free(mf); + Mikmod_Exit(md); + return 0; + } + + Player_Start(mp); + DecodeInfo *hwdata = (DecodeInfo *)md->device.local; + *bps = hwdata->bits; + *srate = hwdata->mixspeed; + *nch = hwdata->channels; + if (mf->songlen) + *size = MulDiv(mf->songlen, hwdata->mixspeed * hwdata->channels *hwdata->bits, 8*1000); + else + *size = -1; + return (intptr_t)mp; + } + + __declspec(dllexport) size_t winampGetExtendedRead_getData(intptr_t handle, char *dest, size_t len, int *killswitch) + { + MPLAYER *mp = (MPLAYER *)handle; + DecodeInfo *hwdata = (DecodeInfo *)mp->mf->md->device.local; + + if (!Player_Active(mp)) // check if we're done + return 0; + + hwdata->buffer = dest; + hwdata->buffersize = len; + hwdata->bytesWritten = 0; + Mikmod_Update(mp->mf->md); + return hwdata->bytesWritten; + } + + + __declspec(dllexport) void winampGetExtendedRead_close(intptr_t handle) + { + MPLAYER *mp = (MPLAYER *)handle; + MDRIVER *md = mp->mf->md; + UNIMOD *mf = (UNIMOD *)mp->mf; + Player_Free(mp); + Unimod_Free(mf); + Mikmod_Exit(md); + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mod/mikamp/src/Info.c b/Src/Plugins/Input/in_mod/mikamp/src/Info.c new file mode 100644 index 00000000..75fdb40c --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/src/Info.c @@ -0,0 +1,602 @@ +#include "api.h" +#include "main.h" +#include "resource.h" +#include <commctrl.h> +#include "../../winamp/wa_ipc.h" +#include <strsafe.h> + +static BOOL CALLBACK infoProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam); +static BOOL CALLBACK tabProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam); +static void OnSelChanged(HWND hwndDlg); + +INFOBOX *infobox_list = NULL; + +int config_info_x = 0, config_info_y = 0; +BOOL config_track = FALSE; + +// ===================================================================================== +void infobox_delete(HWND hwnd) +// ===================================================================================== +// This function is called with the handle of the infobox to destroy. It unloads the +// module if appropriate (care must be take not to unload a module which is in use!). +{ + INFOBOX *cruise, *old; + + old = cruise = infobox_list; + + while(cruise) + { + if(cruise->hwnd == hwnd) + { + if(cruise == infobox_list) + infobox_list = cruise->next; + else old->next = cruise->next; + + // Destroy the info box window, then unload the module, *if* + // the module is not actively playing! + + info_killseeker(hwnd); + DestroyWindow(hwnd); + + if (cruise->dlg.module!=mf && cruise->dlg.ownModule) + Unimod_Free(cruise->dlg.module); + + free(cruise->dlg.suse); + free(cruise); + return; + } + old = cruise; + cruise = cruise->next; + } +} + + +// ===================================================================================== +MPLAYER *get_player(UNIMOD *othermf) +// ===================================================================================== +// Checks the current module against the one given. if they match the MP is returned, +// else it returns NULL. +{ + if (mf == othermf) + return mp; + return NULL; +} + + +// ===================================================================================== +static void infoTabInit(HWND hwndDlg, UNIMOD *m, DLGHDR *pHdr) +// ===================================================================================== +{ + DWORD dwDlgBase = GetDialogBaseUnits(); + int cxMargin = LOWORD(dwDlgBase) / 4, + cyMargin = HIWORD(dwDlgBase) / 8; + TC_ITEM tie; + int tabCounter; + + // Add a tab for each of the three child dialog boxes. + // and lock the resources for the child frames that appear within. + + tie.mask = TCIF_TEXT | TCIF_IMAGE; + tie.iImage = -1; + tabCounter = 0; + + if(m->numsmp) + { + tie.pszText = WASABI_API_LNGSTRING(IDS_SAMPLES); + TabCtrl_InsertItem(pHdr->hwndTab, tabCounter, &tie); + pHdr->apRes[tabCounter] = WASABI_API_CREATEDIALOGPARAM(IDD_SAMPLES, hwndDlg, tabProc, IDD_SAMPLES); + SendMessage(mikmod.hMainWindow,WM_WA_IPC,(WPARAM)pHdr->apRes[tabCounter],IPC_USE_UXTHEME_FUNC); + SetWindowPos(pHdr->apRes[tabCounter], HWND_TOP, pHdr->left+1, pHdr->top+15, 0, 0, SWP_NOSIZE); + ShowWindow(pHdr->apRes[tabCounter++], SW_HIDE); + } + + if(m->numins) + { + tie.pszText = WASABI_API_LNGSTRING(IDS_INSTRUMENTS); + TabCtrl_InsertItem(pHdr->hwndTab, tabCounter, &tie); + pHdr->apRes[tabCounter] = WASABI_API_CREATEDIALOGPARAM(IDD_INSTRUMENTS, hwndDlg, tabProc, IDD_INSTRUMENTS); + SendMessage(mikmod.hMainWindow,WM_WA_IPC,(WPARAM)pHdr->apRes[tabCounter],IPC_USE_UXTHEME_FUNC); + SetWindowPos(pHdr->apRes[tabCounter], HWND_TOP, pHdr->left+1, pHdr->top+15, 0, 0, SWP_NOSIZE); + ShowWindow(pHdr->apRes[tabCounter++], SW_HIDE); + } + + if(m->comment && m->comment[0]) + { + tie.pszText = WASABI_API_LNGSTRING(IDS_COMMENT); + TabCtrl_InsertItem(pHdr->hwndTab, tabCounter, &tie); + pHdr->apRes[tabCounter] = WASABI_API_CREATEDIALOGPARAM(IDD_COMMENT, hwndDlg, tabProc, CEMENT_BOX); + SendMessage(mikmod.hMainWindow,WM_WA_IPC,(WPARAM)pHdr->apRes[tabCounter],IPC_USE_UXTHEME_FUNC); + SetWindowPos(pHdr->apRes[tabCounter], HWND_TOP, pHdr->left+1, pHdr->top+15, 0, 0, SWP_NOSIZE); + ShowWindow(pHdr->apRes[tabCounter++], SW_HIDE); + } + + // Simulate selection of the LAST item + TabCtrl_SetCurSel(pHdr->hwndTab, tabCounter-1); + OnSelChanged(hwndDlg); +} + + +// ===================================================================================== +static BOOL CALLBACK tabProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +// ===================================================================================== +// This is the callback procedure used by each of the three forms under the +// tab control on the Module info dialog box (sample, instrument, comment +// info forms). +{ + switch (uMsg) + { case WM_INITDIALOG: + { + HWND hwndLB; + DLGHDR *pHdr = (DLGHDR *)GetWindowLong(GetParent(hwndDlg), GWL_USERDATA); + UNIMOD *m = pHdr->module; + char sbuf[10280] = {0}; + + switch(lParam) + { case IDD_SAMPLES: + { + uint x; + + hwndLB = GetDlgItem(hwndDlg, IDC_SAMPLIST); + for (x=0; x<m->numsmp; x++) + { + StringCbPrintfA(sbuf, sizeof(sbuf), "%02d: %s",x+1, m->samples[x].samplename ? m->samples[x].samplename : ""); + SendMessage(hwndLB, LB_ADDSTRING, 0, (LPARAM) sbuf); + } + SendMessage(hwndLB, LB_SETCURSEL, 0, 0); + tabProc(hwndDlg, WM_COMMAND, (WPARAM)((LBN_SELCHANGE << 16) + IDC_SAMPLIST), (LPARAM)hwndLB); + } + return TRUE; + + case IDD_INSTRUMENTS: + { + uint x; + + hwndLB = GetDlgItem(hwndDlg, IDC_INSTLIST); + for (x=0; x<m->numins; x++) + { + StringCbPrintfA(sbuf, sizeof(sbuf), "%02d: %s",x+1, m->instruments[x].insname ? m->instruments[x].insname : ""); + SendMessage(hwndLB, LB_ADDSTRING, 0, (LPARAM) sbuf); + } + SendMessage(hwndLB, LB_SETCURSEL, 0, 0); + tabProc(hwndDlg, WM_COMMAND, (WPARAM)((LBN_SELCHANGE << 16) + IDC_INSTLIST), (LPARAM)hwndLB); + } + return TRUE; + + case CEMENT_BOX: + if(m->comment && m->comment[0]) + { + uint x,i; + + hwndLB = GetDlgItem(hwndDlg, CEMENT_BOX); + // convert all CRs to CR/LF pairs. That's the way the edit box likes them! + + for(x=0, i=0; x<strlen(m->comment) && i < sizeof(sbuf)-1; x++) + { + sbuf[i++] = m->comment[x]; + if(m->comment[x]==0x0d && m->comment[x+1]!=0x0a) + sbuf[i++] = 0x0a; + } + sbuf[i] = 0; + + SetWindowText(hwndLB, sbuf); + } + return TRUE; + } + } + break; + + case WM_COMMAND: + if(HIWORD(wParam) == LBN_SELCHANGE) + { // Processes the events for the sample and instrument list boxes, namely updating + // the samp/inst info upon a WM_COMMAND issuing a listbox selection change. + + int moo = SendMessage((HWND)lParam, LB_GETCURSEL, 0, 0); + char sbuf[1024] = {0}, st1[128] = {0}, st2[64] = {0}, st3[64] = {0}; + char tmp1[32] = {0}, tmp2[32] = {0}, tmp3[32] = {0}; + DLGHDR *pHdr = (DLGHDR *)GetWindowLong(GetParent(hwndDlg), GWL_USERDATA); + UNIMOD *m = pHdr->module; + + switch (LOWORD(wParam)) + { case IDC_INSTLIST: + { INSTRUMENT *inst = &m->instruments[moo]; + uint x; + size_t cnt; + char *sbuf_p; + + // -------------------- + // Part 1: General instrument header info + // default volume, auto-vibrato, fadeout (in that order). + + { + StringCbPrintfA(sbuf, sizeof(sbuf), "%d%%\n%s\n%s", (inst->globvol * 400) / 256, + WASABI_API_LNGSTRING_BUF((inst->vibdepth ? IDS_YES : IDS_NO),tmp1,sizeof(tmp1)/sizeof(*tmp1)), + WASABI_API_LNGSTRING_BUF((inst->volfade ? IDS_YES : IDS_NO),tmp2,sizeof(tmp2)/sizeof(*tmp2))); + SetWindowText(GetDlgItem(hwndDlg, IDC_INSTHEAD), sbuf); + } + //(inst->nnatype == NNA_CONTINUE) ? "Continue" : (inst->nnatype == NNA_OFF) ? "Off" : (inst->nnatype == NNA_FADE) ? "Fade" : "Cut"); + + // -------------------- + // Part 2: The instrument envelope info (vol/pan/pitch) + + // Wow this is ugly, but it works: Make a set of strings that have the + // '(loop / sustain)' string. Tricky, cuz the '/' is only added if it + // is needed of course. + + if(inst->volflg & (EF_LOOP | EF_SUSTAIN)) + { + StringCbPrintfA(st1, sizeof(st1), "(%s%s%s)", + (inst->volflg & EF_LOOP) ? WASABI_API_LNGSTRING(IDS_LOOP) : "", + ((inst->volflg & EF_LOOP) && (inst->volflg & EF_SUSTAIN)) ? " / " : "", + (inst->volflg & EF_SUSTAIN) ? WASABI_API_LNGSTRING_BUF(IDS_SUSTAIN,tmp1,sizeof(tmp1)/sizeof(*tmp1)) : ""); + } else st1[0] = 0; + + if(inst->panflg & (EF_LOOP | EF_SUSTAIN)) + { + + StringCbPrintfA(st2, sizeof(st2), "(%s%s%s)", + (inst->panflg & EF_LOOP) ? WASABI_API_LNGSTRING(IDS_LOOP) : "", + ((inst->panflg & EF_LOOP) && (inst->panflg & EF_SUSTAIN)) ? " / " : "", + (inst->panflg & EF_SUSTAIN) ? WASABI_API_LNGSTRING_BUF(IDS_SUSTAIN,tmp1,sizeof(tmp1)/sizeof(*tmp1)) : ""); + } else st2[0] = 0; + + if(inst->pitflg & (EF_LOOP | EF_SUSTAIN)) + { + StringCchPrintfA(st3,sizeof(st3), "(%s%s%s)", + (inst->pitflg & EF_LOOP) ? WASABI_API_LNGSTRING(IDS_LOOP) : "", + ((inst->pitflg & EF_LOOP) && (inst->pitflg & EF_SUSTAIN)) ? " / " : "", + (inst->pitflg & EF_SUSTAIN) ? WASABI_API_LNGSTRING_BUF(IDS_SUSTAIN,tmp1,sizeof(tmp1)/sizeof(*tmp1)) : ""); + } else st3[0] = 0; + + { + StringCbPrintfA(sbuf, sizeof(sbuf), "%s %s\n%s %s\n%s %s", + WASABI_API_LNGSTRING_BUF(((inst->volflg & EF_ON) ? IDS_ON : IDS_OFF),tmp1,sizeof(tmp1)/sizeof(*tmp1)), + st1[0] ? st1 : "", + WASABI_API_LNGSTRING_BUF(((inst->panflg & EF_ON) ? IDS_ON : IDS_OFF),tmp2,sizeof(tmp2)/sizeof(*tmp2)), + st2[0] ? st2 : "", + WASABI_API_LNGSTRING_BUF(((inst->pitflg & EF_ON) ? IDS_ON : IDS_OFF),tmp3,sizeof(tmp3)/sizeof(*tmp3)), + st3[0] ? st3 : ""); + + } + SetWindowText(GetDlgItem(hwndDlg, IDC_INSTENV), sbuf); + + // -------------------- + // Part 3: List of samples used by this instrument! + // the trick here is that that we have to figure out what samples are used from the + // sample index table in inst->samplenumber. + + memset(pHdr->suse,0,m->numsmp*sizeof(BOOL)); + for(x=0; x<120; x++) + if(inst->samplenumber[x] != 65535) + pHdr->suse[inst->samplenumber[x]] = 1; + + sbuf[0] = 0; cnt = sizeof(sbuf)/sizeof(*sbuf); + sbuf_p = sbuf; + for (x=0; x<m->numsmp; x++) + { + if(pHdr->suse[x]) + { + StringCbPrintfExA(sbuf_p, cnt, &sbuf_p, &cnt, 0, "%02d: %s\r\n",x+1, m->samples[x].samplename); + } + } + if (cnt < sizeof(sbuf)/sizeof(*sbuf)) + { + sbuf[sizeof(sbuf)/sizeof(*sbuf) - cnt - 2] = 0; // cut off the final CR/LF set + } + SetWindowText(GetDlgItem(hwndDlg, TB_SAMPLELIST), sbuf); + + } + break; + + case IDC_SAMPLIST: + { UNISAMPLE *samp = &m->samples[moo]; + EXTSAMPLE *es = NULL; + + if(m->extsamples) es = &m->extsamples[moo]; + + // Display sampe header info... + // Length, Format, Quality, Looping, Auto-vibrato, Volume, Panning (in that order). + + { + char yn[64] = {0}, pp[64] = {0}; + StringCbPrintfA(sbuf, sizeof(sbuf), "%d %s\n%d %s\n%s %s\n%s\n%s\n%d\n%d", + samp->length * (samp->format&SF_16BITS ? 2 : 1), WASABI_API_LNGSTRING_BUF(IDS_BYTES, tmp1, sizeof(tmp1)/sizeof(*tmp1)), + samp->speed, WASABI_API_LNGSTRING_BUF((m->flags&UF_XMPERIODS ? IDS_FINETUNE : (samp->format&SF_SIGNED ? IDS_HZ_SIGNED : IDS_HZ_UNSIGNED)),tmp2,sizeof(tmp2)/sizeof(*tmp2)), + samp->format & SF_16BITS ? "16" : "8", WASABI_API_LNGSTRING_BUF(IDS_BITS, tmp3, sizeof(tmp3)/sizeof(*tmp3)), + WASABI_API_LNGSTRING_BUF((samp->flags&SL_LOOP ? ( samp->flags&SL_BIDI ? IDS_PING_PONG : (samp->flags&SL_REVERSE ? IDS_REVERSE : IDS_FORWARD )) : samp->flags&SL_SUSTAIN_LOOP ? ( samp->flags&SL_SUSTAIN_BIDI ? IDS_SUSTAIN_PING_PONG : IDS_SUSTAIN ) : IDS_NONE),pp,sizeof(pp)/sizeof(*pp)), + WASABI_API_LNGSTRING_BUF(((es && es->vibdepth) ? IDS_YES : IDS_NO),yn,sizeof(yn)/sizeof(*yn)), + samp->volume, samp->panning); + } + + SetWindowText(GetDlgItem(hwndDlg, IDC_SAMPINFO), sbuf); + } + break; + } + } + break; + } + return 0; +} + + +// ===================================================================================== +static void OnSelChanged(HWND hwndDlg) +// ===================================================================================== +{ + DLGHDR *pHdr = (DLGHDR *) GetWindowLong(hwndDlg, GWL_USERDATA); + int iSel = TabCtrl_GetCurSel(pHdr->hwndTab); + + if(pHdr->hwndDisplay) ShowWindow(pHdr->hwndDisplay,SW_HIDE); + ShowWindow(pHdr->apRes[iSel],SW_SHOW); + pHdr->hwndDisplay = pHdr->apRes[iSel]; + + // Note to self: Despite their inhernet use in interfaces, coding tab controls + // apparently REALLY sucks, and it should never ever be done again by myself + // or anyone else whom I respect as a sane individual and I would like to have + // remain that way. As for me, it is too late. Bruhahahaha!K!J!lkjgkljASBfkJBdglkn. + +} + +// ===================================================================================== +static void CALLBACK UpdateInfoRight(HWND hwnd, UINT uMsg, UINT ident, DWORD systime) +// ===================================================================================== +{ + char str[256] = {0}; + DLGHDR *pHdr = (DLGHDR *)GetWindowLong(hwnd, GWL_USERDATA); + MPLAYER *mp; + + // Player info update .. BPM, sngspeed, position, row, voices. + // Only update if our mf struct is the same as the one currently loaded into the player. + + if ((mp = get_player(pHdr->module)) == NULL) + { + // clean up + if (pHdr->inUse) + { + UNIMOD *m = pHdr->module; + pHdr->inUse = FALSE; + + StringCbPrintfA(str, sizeof(str), WASABI_API_LNGSTRING(IDS_X_X_X_OF_X_NOT_PLAYING), + m->inittempo, m->initspeed, m->numpos); + SetDlgItemText(hwnd, IDC_INFORIGHT, str); + + if (pHdr->seeker) + { + if (pHdr->seeker != mp) + Player_Free(pHdr->seeker); + pHdr->seeker = NULL; + } + } + + // "track song" mode + if (mf && IsDlgButtonChecked(hwnd, IDC_TRACK)==BST_CHECKED) + { + SendMessage(hwnd, WM_USER+10, 0, 0); + infoDlg(GetParent(hwnd), mf, GetActiveWindow()==hwnd, FALSE); + PostMessage(hwnd, WM_CLOSE, 0, 0); + } + } + else + { + MPLAYER *seeker; + long acv; + + if (!pHdr->inUse) + { + assert(pHdr->seeker == NULL); + pHdr->inUse = TRUE; + + // create our new player instance specifically for seeking + if (!(config_playflag & CPLAYFLG_SEEKBYORDERS)) + { + if ((pHdr->seeker = Player_Dup(mp)) == NULL) + return; + + // steal statelist from the original player + // (this will not require special handling, + // because of a smart allocation system) + + pHdr->seeker->statelist = mp->statelist; + pHdr->seeker->statecount = mp->statecount; + } + else pHdr->seeker = NULL; + } + + // Seek to our new song time, using a bastardized version of Player_SetPosTime code: + if (pHdr->seeker) + { + long curtime = mikmod.GetOutputTime() * 64; + seeker = pHdr->seeker; + + if (seeker->statelist) + { + int t = 0; + + while (t<seeker->statecount && + seeker->statelist[t].curtime && + curtime>=seeker->statelist[t].curtime) + t++; + + if (t) + Player_Restore(seeker, t - 1); + else Player_Cleaner(seeker); + } + else Player_Cleaner(seeker); + + while(!seeker->ended && seeker->state.curtime<curtime) + Player_PreProcessRow(seeker, NULL); + } + else seeker = mp; + + // Display all the goodie info we have collected: + // --------------------------------------------- + + acv = Mikmod_GetActiveVoices(mp->vs->md); + if (acv > pHdr->maxv) pHdr->maxv = acv; + + StringCbPrintfA(str, sizeof(str), WASABI_API_LNGSTRING(IDS_X_X_X_OF_X_X_OF_X_X_OF_X), + seeker->state.bpm, seeker->state.sngspd, seeker->state.sngpos, + seeker->mf->numpos, seeker->mf->positions[seeker->state.sngpos], + seeker->state.patpos, seeker->state.numrow, acv, pHdr->maxv); + SetWindowText(GetDlgItem(hwnd,IDC_INFORIGHT), str); + } +} + +// ===================================================================================== +void infoDlg(HWND hwnd, UNIMOD *m, BOOL activate, BOOL primiary) +// ===================================================================================== +{ + INFOBOX *box; + HWND dialog, hwndPrev; + char str[256] = {0}; + + if (!m) return; + + // + // create local dataplace + // + + box = (INFOBOX*)calloc(1, sizeof(INFOBOX)); + if (!box) return; + + box->dlg.left = 7; + box->dlg.top = 168; + box->dlg.module = m; + box->dlg.ownModule = primiary; + + box->next = infobox_list; + infobox_list = box; + + // + // create dialog + // + + hwndPrev = GetActiveWindow(); + box->hwnd = dialog = WASABI_API_CREATEDIALOG(IDD_ID3EDIT, hwnd, infoProc); + box->dlg.hwndTab = GetDlgItem(dialog, IDC_TAB); + + SetWindowLong(dialog, GWL_USERDATA, (LONG)&box->dlg); + SetDlgItemText(dialog, IDC_ID3_FN, m->filename); + + // IDC_INFOLEFT contains static module information: + // File Size, Length (in mins), channels, samples, instruments. + + StringCbPrintfA(str, sizeof(str), WASABI_API_LNGSTRING(IDS_X_BTYES_X_OF_X_MINUTES), + m->filesize, m->songlen/60000,(m->songlen%60000)/1000, m->numchn, m->numsmp, m->numins); + SetDlgItemText(dialog, IDC_INFOLEFT, str); + SetDlgItemText(dialog, IDC_TITLE, m->songname); + SetDlgItemText(dialog, IDC_TYPE, m->modtype); + + // IDC_INFORIGHT - contains player information + + StringCbPrintfA(str, sizeof(str), WASABI_API_LNGSTRING(IDS_X_X_X_OF_X_NOT_PLAYING), + m->inittempo, m->initspeed, m->numpos); + SetDlgItemText(dialog, IDC_INFORIGHT, str); + + // pHdr->suse is a samples-used block, allocated if this module uses + // instruments, and used to display the sampels that each inst uses + + if (m->numins) + box->dlg.suse = (BOOL*)calloc(m->numsmp, sizeof(BOOL)); + + CheckDlgButton(dialog, IDC_TRACK, config_track ? BST_CHECKED : BST_UNCHECKED); + infoTabInit(dialog, m, &box->dlg); + SetTimer(dialog, 1, 50, UpdateInfoRight); + + ShowWindow(dialog, SW_SHOW); + if (!activate) SetActiveWindow(hwndPrev); // do not steal focus +} + +// ===================================================================================== +void info_killseeker(HWND hwnd) +// ===================================================================================== +{ + DLGHDR *pHdr = (DLGHDR *)GetWindowLong(hwnd, GWL_USERDATA); + + if (pHdr->seeker) + { + assert(pHdr->inUse); + + if (pHdr->seeker != mp) + Player_Free(pHdr->seeker); + pHdr->seeker = NULL; + } + + pHdr->inUse = FALSE; +} + + +// ===================================================================================== +static BOOL CALLBACK infoProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +// ===================================================================================== +{ + UNIMOD *m = NULL; + + switch (uMsg) + { + case WM_INITDIALOG: + { + RECT rect, wrect; + + if (GetWindowRect(mikmod.hMainWindow, &wrect) && GetWindowRect(hwndDlg, &rect)) + { + wrect.left += config_info_x; + wrect.top += config_info_y; + + if (wrect.left>=0 && wrect.top>=0 && + wrect.left<GetSystemMetrics(SM_CXFULLSCREEN)-16 && + wrect.top<GetSystemMetrics(SM_CYFULLSCREEN)-16) + MoveWindow(hwndDlg, wrect.left, wrect.top, rect.right-rect.left, rect.bottom-rect.top, FALSE); + } + } + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + case IDCANCEL: + SendMessage(hwndDlg, WM_CLOSE, 0, 0); + break; + case IDC_TRACK: + config_track = IsDlgButtonChecked(hwndDlg, IDC_TRACK) == BST_CHECKED; + break; + } + break; + + case WM_USER + 10: + case WM_CLOSE: + // save offset + { + RECT rect, wrect; + + if (GetWindowRect(mikmod.hMainWindow, &wrect) && GetWindowRect(hwndDlg, &rect)) + { + config_info_x = rect.left - wrect.left; + config_info_y = rect.top - wrect.top; + } + } + config_track = IsDlgButtonChecked(hwndDlg, IDC_TRACK) == BST_CHECKED; + + // clean up + if (uMsg != WM_CLOSE) + break; + + KillTimer(hwndDlg, 1); + infobox_delete(hwndDlg); + config_write(); + break; + + case WM_NOTIFY: + { + NMHDR *notice = (NMHDR*)lParam; + switch(notice->code) + { + case TCN_SELCHANGE: + OnSelChanged(hwndDlg); + break; + } + } + return TRUE; + } + return 0; +} diff --git a/Src/Plugins/Input/in_mod/mikamp/src/Main.c b/Src/Plugins/Input/in_mod/mikamp/src/Main.c new file mode 100644 index 00000000..b10f4bb0 --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/src/Main.c @@ -0,0 +1,1067 @@ +#include "api.h" +extern "C" { +#include "main.h" +} +#include "log.h" +#include "../../winamp/wa_ipc.h" +#include "../nu/AutoWide.h" +#include "../nu/AutoCharFn.h" +#include <shlwapi.h> +#include <commdlg.h> + +extern "C" MMSTREAM *_mm_fopen_rf(const CHAR *fname); //rf_wrapper.c + +// +// data types and stuff +// + +#define SU_POSITION 1 +#define SU_TIME 2 + +#define PPF_CONT_LOOP 1 +#define PPF_LOOPALL 2 +#define PPF_ADD_TITLE 4 + +typedef struct +{ + const char *cmd; + const char *file; + const char *title; + int titleLength; + int start; + int startUnit; + int loops; + int flags; +} PlayParams; + + +// Public Globals! +// --------------- +extern "C" +{ + UNIMOD *mf; + MPLAYER *mp; + int paused; + int decode_pos; // in 1/64th of millisecond + extern char cfg_format[]; +} + + +void infobox_setmodule(HWND hwnd); + + +// Static Globals! +// --------------- + +#define SILENCE_THRESHOLD 10800 + +extern "C" int GetSampleSizeFlag(); +static char ERROR_TITLE[64]; + +static int is_tempfile = 0; +static char cmdName[2048], saveName[MAX_PATH]; +static char songTitle[400]; // as in Winamp +static PlayParams currParams; + +static HANDLE thread_handle = INVALID_HANDLE_VALUE; +static volatile int killDecodeThread; +static volatile int seek_needed; + +// wasabi based services for localisation support +api_application *WASABI_API_APP = 0; +api_language *WASABI_API_LNG = 0; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; + +extern "C" DWORD WINAPI decodeThread(void *b); +void __cdecl setoutputtime(int time_in_ms); + +// ===================================================================================== +// error handling shiz +// ===================================================================================== + +static int lastError = 0; + +__inline void mm_clearerror() { lastError = 0; } + +static void mmerr(int crap, const CHAR *crud) +{ + char tmp[128] = {0}; + if (lastError==crap || crap==MMERR_OPENING_FILE) + return; + else + { + if(!lstrcmpi(crud,"Corrupt file or unsupported module type.")) + { + WASABI_API_LNGSTRING_BUF(IDS_CORRUPT_UNSUPPORTED_TYPE,tmp,128); + } + else + tmp[0] = 0; + } + + MessageBox(mikmod.hMainWindow, (tmp[0]?tmp:crud), ERROR_TITLE, MB_ICONERROR); + lastError = crap; +} + + +// ===================================================================================== +static int __cdecl init(void) +// ===================================================================================== +{ + if (!IsWindow(mikmod.hMainWindow)) + return IN_INIT_FAILURE; + + waServiceFactory *sf = mikmod.service->service_getServiceByGuid(languageApiGUID); + if (sf) WASABI_API_LNG = reinterpret_cast<api_language*>(sf->getInterface()); + + sf = mikmod.service->service_getServiceByGuid(applicationApiServiceGuid); + if (sf) WASABI_API_APP = reinterpret_cast<api_application*>(sf->getInterface()); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(mikmod.hDllInstance,InModLangGUID); + + static wchar_t szDescription[256]; + swprintf(szDescription,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_MODULE_DECODER),PLUGIN_VER); + mikmod.description = (char*)szDescription; + + WASABI_API_LNGSTRING_BUF(IDS_MOD_PLUGIN_ERROR,ERROR_TITLE,64); + + _mmerr_sethandler(&mmerr); + + config_read(); + + Mikmod_RegisterAllLoaders(); + Mikmod_RegisterDriver(drv_amp); + //Mikmod_RegisterDriver(drv_buffer); + return IN_INIT_SUCCESS; +} + +// ===================================================================================== +static void __cdecl quit() +// ===================================================================================== +{ +//LOG log_exit(); + SL_Cleanup(); +} + +static MDRIVER *md; + +// ===================================================================================== +// file open shiz +// ===================================================================================== + +#define IPC_GETHTTPGETTER 240 + +__inline char *GetFileName(const char *fullname) +{ + const char *c = fullname + strlen(fullname) - 1; + + while (c > fullname) + { + if (*c=='\\' || *c=='/') + { + c++; + break; + } + c--; + } + + return (char*)c; +} + +char* BuildFilterString(void) +{ + static char filterStr[128] = {0}; + if(!filterStr[0]) + { + char* temp = filterStr; + WASABI_API_LNGSTRING_BUF(IDS_ALL_FILES,filterStr,128); + temp += lstrlen(filterStr)+1; + lstrcpy(temp, "*.*"); + *(temp = temp + lstrlen(temp) + 1) = 0; + } + return filterStr; +} + +BOOL GetPlayParams(const char *fileName, BOOL open, PlayParams *params) +{ + mm_clearerror(); + + // fill params + params->cmd = fileName; + params->start = 0; + params->loops = config_loopcount; + params->titleLength = 0; + params->flags = 0; + + if (config_playflag & CPLAYFLG_CONT_LOOP) + params->flags |= PPF_CONT_LOOP; + + if (config_playflag & CPLAYFLG_LOOPALL) + params->flags |= PPF_LOOPALL; + + if (params->loops == -1) + params->flags &= ~PPF_CONT_LOOP; + + // check for mod:// prefix + if (!strncmp(fileName, "mod://", 6)) + { + const char *c = fileName += 6; + + while (c && *c && *c!=':') + { + // jump to + if (!strncmp(c, "jmp=", 4)) + { + // jump units + switch (*(c + 4)) + { + // position + case 'p': + params->startUnit = SU_POSITION; + params->flags &= ~PPF_CONT_LOOP; + break; + // time + case 't': + params->startUnit = SU_TIME; + break; + // invalid + default: + return FALSE; + } + params->start = atoi(c + 5); + } + // loops + else if (!strncmp(c, "lop=", 4)) + { + if (*(c+4) == 'u') + { + params->flags |= PPF_LOOPALL; + c++; + } + params->loops = atoi(c + 4); + params->loops = _mm_boundscheck(params->loops, -1, 64); + if (params->loops == -1) + params->flags &= ~PPF_CONT_LOOP; + } + // continue after loop + else if (!strncmp(c, "con=", 4)) + { + if (atoi(c + 4)) + params->flags |= PPF_CONT_LOOP; + else params->flags &= ~PPF_CONT_LOOP; + } + // title + else if (!strncmp(c, "tit=", 4)) + { + // find string + const char *p = c + 4; + + if (*p == '+') + { + params->flags |= PPF_ADD_TITLE; + c++; + p++; + } + + if (*p++ != '"') return FALSE; + + while (p && *p && *p!='"') + p++; + + if (*p != '"') return FALSE; + // set + params->title = c + 5; + params->titleLength = p - c - 5; + c = p - 3; + } + // invalid + else return FALSE; + + // skip + c += 4; + while (c && *c && *c!=',' && *c!=':') + c++; + if (*c == ',') c++; + } + + if (!*c) return FALSE; + fileName = c + 1; + } + + params->file = fileName; + + // check for URLs + if (open) + { + saveName[0] = 0; + is_tempfile = 0; + + if (!_strnicmp(fileName, "http://", 7) || !_strnicmp(fileName, "https://", 8) || + !_strnicmp(fileName, "ftp://", 6)) // FTP is now currently supported, but still... + { + typedef int (__cdecl *HttpRetrieveFile)(HWND hwnd, const char *url, const char *file, const char *dlgtitle); + + HttpRetrieveFile fileGetter; + int t = SendMessage(mikmod.hMainWindow,WM_USER,0,IPC_GETHTTPGETTER); + // try to get httpGetter + if (!t || t==1) + { + MessageBox(mikmod.hMainWindow, + WASABI_API_LNGSTRING(IDS_URLS_ONLY_SUPPORTED_IN_2_10_PLUS), + ERROR_TITLE, MB_ICONERROR); + return FALSE; + } + + fileGetter = (HttpRetrieveFile)t; + // save stream if required + if (config_savestr) + { + OPENFILENAME l = {0}; + lstrcpyn(saveName, GetFileName(fileName), MAX_PATH); + l.lStructSize = sizeof(l); + l.hwndOwner = mikmod.hMainWindow; + l.hInstance = NULL; + l.lpstrFilter = BuildFilterString(); + l.lpstrCustomFilter = NULL; + l.nMaxCustFilter = 0; + l.nFilterIndex = 0; + l.lpstrFile = saveName; + l.nMaxFile = sizeof(saveName); + l.lpstrFileTitle = 0;; + l.nMaxFileTitle = 0; + l.lpstrInitialDir = NULL; + l.lpstrTitle = WASABI_API_LNGSTRING(IDS_SAVE_MODULE); + l.lpstrDefExt = "mod"; + l.Flags = OFN_HIDEREADONLY|OFN_EXPLORER|OFN_OVERWRITEPROMPT; + + if (!GetSaveFileName(&l)) + saveName[0] = 0; + } + // generate temp name, if not saving + if (!saveName[0]) + { + char p[MAX_PATH] = {0}; + + GetTempPath(sizeof(p), p); + GetTempFileName(p, "mod", 0, saveName); + is_tempfile = 1; + } + // get file + if (fileGetter(mikmod.hMainWindow, fileName, saveName, WASABI_API_LNGSTRING(IDS_RETRIEVING_MODULE))) + { + is_tempfile = 0; + saveName[0] = 0; + return FALSE; + } + params->file = saveName; + } + } + else + { + if (saveName[0] && !_stricmp(fileName, cmdName)) + params->file = saveName; + } + + return TRUE; +} + +static void CleanupTemp() +{ + if (is_tempfile && saveName[0]) + { + DeleteFile(saveName); + is_tempfile = 0; + } + + saveName[0] = 0; +} + +BOOL InitPlayer(UNIMOD *mf, MPLAYER **ps, const PlayParams *params, BOOL quick) +{ + int flags; + + // strip silence + if (config_playflag & CPLAYFLG_STRIPSILENCE) + Unimod_StripSilence(mf, SILENCE_THRESHOLD); + + // set flags + flags = PF_TIMESEEK; + if (params->flags & PPF_CONT_LOOP) flags |= PF_CONT_LOOP; + + // init player + if (quick) + *ps = Player_Create(mf, flags); + else *ps = Player_InitSong(mf, NULL, flags, config_voices); + + if (!*ps) return FALSE; + + // position seek + if (params->start && params->startUnit==SU_POSITION) + Player_SetStartPosition(*ps, params->start); + + // looping + Player_SetLoopStatus(*ps, params->flags & PPF_LOOPALL, params->loops); + + if (quick || config_playflag&CPLAYFLG_SEEKBYORDERS) + Player_PredictSongLength(*ps); + else + { + // time calculation & seeking-lookups creation + Player_BuildQuickLookups(*ps); + + // fade (needs results of Player_BuildQuickLookups) + if (config_playflag & CPLAYFLG_FADEOUT) + Player_VolumeFadeEx(*ps, MP_VOLUME_CUR, 0, config_fadeout, MP_SEEK_END, config_fadeout); + } + + // remember song length + mf->songlen = (*ps)->songlen; + + return TRUE; +} + +static UNIMOD *GetModuleInfo(const PlayParams *params) +{ + UNIMOD *m = mf; + + // check against the current one + if (!m || _stricmp(cmdName, params->cmd)) // check the whole string, not just file name + { + MPLAYER *ps; + MMSTREAM * fp; + + // load module + mm_clearerror(); + fp = _mm_fopen_rf(params->file); + if (!fp) return NULL; + m = Unimod_LoadInfo_FP(params->file,fp); + _mm_fclose(fp); + if (!m) return NULL; + + // get info and clean up + if (!InitPlayer(m, &ps, params, TRUE)) + { + Unimod_Free(m); + return NULL; + } + Player_Free(ps); + } + + return m; +} + +static int __cdecl isourfile(const char *fn) +{ + return !_strnicmp(fn, "mod://", 6); +} + +// ===================================================================================== +// helpers +// ===================================================================================== + +static UNIMOD *FindInfoBox(const char *fileName, HWND *hwnd) +{ + INFOBOX *cruise; + + for (cruise=infobox_list; cruise; cruise=cruise->next) + if (!_stricmp(cruise->dlg.module->filename, fileName)) + { + if (hwnd) *hwnd = cruise->hwnd; + return cruise->dlg.module; + } + + return NULL; +} + +static BOOL FindInfoBoxPtr(const UNIMOD *mf) +{ + INFOBOX *cruise; + + for (cruise=infobox_list; cruise; cruise=cruise->next) + if (cruise->dlg.module == mf) + return TRUE; + + return FALSE; +} + +// ===================================================================================== +static int __cdecl play(const char *fileName) +// ===================================================================================== +{ + PlayParams params; + uint md_mode = 0; + + // parse parameters + if (!GetPlayParams(fileName, TRUE, ¶ms)) + return 1; + + // save strings locally + lstrcpyn(cmdName, params.cmd, 2048); + if (params.titleLength) + lstrcpyn(songTitle, params.title, min(params.titleLength+1, sizeof(songTitle))); + else songTitle[0] = 0; + + // save current values + currParams = params; + currParams.cmd = params.cmd; + currParams.title = songTitle; + + // Initialize MDRVER + // ----------------- + + if (config_interp & 1) md_mode |= DMODE_INTERP; + if (config_interp & 2) md_mode |= DMODE_NOCLICK; + if (config_interp & 4) md_mode |= DMODE_FIR; + md_mode |= GetSampleSizeFlag(); + if (AllowSurround()) md_mode |= DMODE_SURROUND; + if (config_panrev) md_mode |= DMODE_REVERSE; + if (config_resonance) md_mode |= DMODE_RESONANCE; + + md = Mikmod_Init(config_srate, 1000, NULL, GetNumChannels()==1 ? MD_MONO : MD_STEREO, config_cpu, md_mode, &drv_amp); + if (!md) + { + CleanupTemp(); + return 1; + } + + md->pansep = config_pansep; + + // Register non-interpolation mixers + // --------------------------------- + // if the user has disabled interpolation... + + if(!(config_interp & 1)) + { + VC_RegisterMixer(md->device.vc, &RF_M8_MONO); + VC_RegisterMixer(md->device.vc, &RF_M16_MONO); + VC_RegisterMixer(md->device.vc, &RF_M8_STEREO); + VC_RegisterMixer(md->device.vc, &RF_M16_STEREO); + + VC_RegisterMixer(md->device.vc, &M8_MONO); + VC_RegisterMixer(md->device.vc, &M16_MONO); + VC_RegisterMixer(md->device.vc, &M8_STEREO); + VC_RegisterMixer(md->device.vc, &M16_STEREO); + } + else if (config_interp&4) + { +/* + VC_RegisterMixerHack(md->device.vc, &M16_MONO_CUBIC); + VC_RegisterMixerHack(md->device.vc, &M16_STEREO_CUBIC); + VC_RegisterMixerHack(md->device.vc, &M8_MONO_CUBIC); + VC_RegisterMixerHack(md->device.vc, &M8_STEREO_CUBIC); +*/ + VC_RegisterMixerHack(md->device.vc, &M16_MONO_FIR); + VC_RegisterMixerHack(md->device.vc, &M16_STEREO_FIR); + VC_RegisterMixerHack(md->device.vc, &M8_MONO_FIR); + VC_RegisterMixerHack(md->device.vc, &M8_STEREO_FIR); + } + + // LOADING THE SONG + // ---------------- + // Check through the list of active info boxes for a matching filename. If found, + // then we use the already-loaded module information instead! + + { + HWND hwnd; + + if ((mf=FindInfoBox(params.file, &hwnd)) != NULL) + { + MMSTREAM *smpfp; + + // prepare for reloading + info_killseeker(hwnd); + + // reload samples + smpfp = _mm_fopen_rf(params.file); + Unimod_LoadSamples(mf, md, smpfp); + _mm_fclose(smpfp); + } + // not already loaded + else + { + MMSTREAM *fp; + fp = _mm_fopen_rf(params.file); + if (!fp) + { + Mikmod_Exit(md); + CleanupTemp(); + return -1; + } +//MMEXPORT UNIMOD *Unimod_LoadFP(MDRIVER *md, MMSTREAM *modfp, MMSTREAM *smpfp, int mode); +//MMEXPORT UNIMOD *Unimod_Load(MDRIVER *md, const CHAR *filename); + + mf=Unimod_Load_FP(md, params.file,fp); + _mm_fclose(fp); + if (mf==NULL) + { + Mikmod_Exit(md); + CleanupTemp(); + return -1; + } + } + } + + // file name is stored in module now + if (!saveName[0]) + params.file = mf->filename; + + // init player + if (!InitPlayer(mf, &mp, ¶ms, FALSE)) + { + CleanupTemp(); + return -1; + } + + Player_Start(mp); + + // set start time + seek_needed = -1; + decode_pos = 0; + if (params.start && params.startUnit==SU_TIME) + setoutputtime(params.start*1000); + + // init output & info + mikmod.outMod->SetVolume(-666); + mikmod.SetInfo(MulDiv(mf->filesize, 8, mf->songlen), config_srate/1000, GetNumChannels(), 1); + + // init decoding thread + { + DWORD threadid; + + killDecodeThread = 0; + paused = 0; + thread_handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)decodeThread, NULL, 0, &threadid); + set_priority(); + } + + return 0; +} + +// ===================================================================================== +static void __cdecl stop(void) +// ===================================================================================== +{ + if (thread_handle != INVALID_HANDLE_VALUE) + { + killDecodeThread = 1; + if (WaitForSingleObject(thread_handle, 2000) == WAIT_TIMEOUT) + { + MessageBox(mikmod.hMainWindow, + WASABI_API_LNGSTRING(IDS_ERROR_KILLING_DECODING_THREAD), + ERROR_TITLE, MB_ICONWARNING); + TerminateThread(thread_handle, 0); + } + + CloseHandle(thread_handle); + thread_handle = INVALID_HANDLE_VALUE; + CleanupTemp(); + } + + Player_Free(mp); + mp = NULL; + + // We need to see if mf is in use. If so, then we can't unload it. + // Bute we *do* have to unload its samples, because those are not needed. + + if (FindInfoBoxPtr(mf)) + Unimod_UnloadSamples(mf); + else Unimod_Free(mf); + + mf = NULL; + + Mikmod_Exit(md); md = NULL; + mikmod.SAVSADeInit(); +} + +// ===================================================================================== +// pausing stuff +// ===================================================================================== + +static void __cdecl pause(void) { paused=1; mikmod.outMod->Pause(1); } +static void __cdecl unpause(void) { paused=0; mikmod.outMod->Pause(0); } +static int __cdecl ispaused(void) { return paused; } + +// ===================================================================================== +// seeking/timing related stuff +// ===================================================================================== + +static int __cdecl getlength(void) +{ + if (mp) + { + if (!(config_playflag & CPLAYFLG_SEEKBYORDERS)) + return mp->songlen; + else return mf->numpos * 1000; + } + else return 0; +} + +static int __cdecl getoutputtime(void) +{ + if (!(config_playflag & CPLAYFLG_SEEKBYORDERS)) + return decode_pos/64 + (mikmod.outMod->GetOutputTime() - mikmod.outMod->GetWrittenTime()); + else return mp ? mp->state.sngpos * 1000 : 0; +} + +static void __cdecl setoutputtime(int time_in_ms) +{ + seek_needed = time_in_ms; +} + +// ===================================================================================== +static int __cdecl infobox(const char *fileName, HWND hwnd) +// ===================================================================================== +{ + PlayParams params; + + // parse params + if (!GetPlayParams(fileName, FALSE, ¶ms)) + return 1; + + // First we check our array of loaded dialog boxes. If there are any filename matches, + // then we just bring that window to the foreground! + + if (FindInfoBox(params.file, &hwnd) != NULL) + { + SetForegroundWindow(hwnd); + return 0; + } + + infoDlg(hwnd, GetModuleInfo(¶ms), TRUE, TRUE); + + return 0; +} + +/*extern "C" __declspec(dllexport) int winampGetExtendedFileInfo(const char *fn, const char *data, char *dest, int destlen) +{ + UNIMOD *m=0; + PlayParams params; + const char *ret=0; + + + if (!_stricmp(data,"TYPE")) + { + dest[0] = '0'; + dest[1] = 0x00; + return 1; + } + if (!_stricmp(data,"FAMILY")) + { + LPCTSTR e; + e = PathFindExtension(fn); + if (L'.' != *e) return 0; + e++; + return GetTypeInfo(e, dest, destlen); + } + + if (!GetPlayParams(fn, FALSE, ¶ms)) + return 0; + + m=GetModuleInfo(¶ms); + if (!m) + return 0; + + if (!_stricmp(data,"TITLE")) + { + if (!params.titleLength || params.flags&PPF_ADD_TITLE) + ret = m->songname; + else + ret=params.title; + } + else if (!_stricmp(data,"PART")) + { + if (params.titleLength && params.flags&PPF_ADD_TITLE) + ret=params.title; + } + else if (!_stricmp(data,"ARTIST") ) + ret=m->composer; + else if (!_stricmp(data,"COMPOSER")) + ret=m->composer; + else if (!_stricmp(data,"COMMENT")) + ret=m->comment; + else if (!_stricmp(data,"FORMAT") || !_stricmp(data,"MODTYPE")) + ret=m->modtype; + else if (!_stricmp(data,"LENGTH")) + { + _itoa(m->songlen, dest, 10); + if (m!=mf) // make sure it's not the currently playing file + Unimod_Free(m); // in theory this is a race condition + return 1; + } + else + { + if (m!=mf) // make sure it's not the currently playing file + Unimod_Free(m); // in theory this is a race condition + return 0; + } + + if (ret) + lstrcpyn(dest, ret, destlen); + else + dest[0]=0; + + if (m!=mf) // make sure it's not the currently playing file + Unimod_Free(m); // in theory this is a race condition + return 1; +}*/ + +extern "C" __declspec(dllexport) int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, int destlen) +{ + UNIMOD *m=0; + PlayParams params; + const char *ret=0; + + if (!_stricmp(data,"TYPE")) + { + dest[0] = L'0'; + dest[1] = 0x00; + return 1; + } + if (!_stricmp(data,"FAMILY")) + { + LPCWSTR e; + e = PathFindExtensionW(fn); + if (L'.' != *e) return 0; + e++; + return GetTypeInfo(e, dest, destlen); + } + + if (!GetPlayParams(AutoCharFn(fn), FALSE, ¶ms)) + return 0; + + m=GetModuleInfo(¶ms); + if (!m) + return 0; + + if (!_stricmp(data,"TITLE")) + { + if (!params.titleLength || params.flags&PPF_ADD_TITLE) + ret = m->songname; + else + ret=params.title; + } + else if (!_stricmp(data,"PART")) + { + if (params.titleLength && params.flags&PPF_ADD_TITLE) + ret=params.title; + } + else if (!_stricmp(data,"ARTIST") ) + ret=m->composer; + else if (!_stricmp(data,"COMPOSER")) + ret=m->composer; + else if (!_stricmp(data,"COMMENT")) + ret=m->comment; + else if (!_stricmp(data,"FORMAT") || !_stricmp(data,"MODTYPE")) + ret=m->modtype; + else if (!_stricmp(data,"LENGTH")) + { + _itow(m->songlen, dest, 10); + if (m!=mf) // make sure it's not the currently playing file + Unimod_Free(m); // in theory this is a race condition + return 1; + } + else + { + if (m!=mf) // make sure it's not the currently playing file + Unimod_Free(m); // in theory this is a race condition + return 0; + } + + if (ret) + lstrcpynW(dest, AutoWide(ret), destlen); + else + dest[0]=0; + + if (m!=mf) // make sure it's not the currently playing file + Unimod_Free(m); // in theory this is a race condition + return 1; +} + +// ===================================================================================== +static void __cdecl getfileinfo(const char *fileName, char *title, int *length_in_ms) +// ===================================================================================== +{ + PlayParams params; + UNIMOD *m; + BOOL unload = FALSE; + + // empty string stands for the current file + if (fileName!=NULL && *fileName) + { + if (!GetPlayParams(fileName, FALSE, ¶ms)) + { + lstrcpyn(title, fileName, GETFILEINFO_TITLE_LENGTH); + if (length_in_ms) + *length_in_ms = -1; + return; + } + } + else + params = currParams; + + // module loaded + if ((m=FindInfoBox(params.file, NULL))!=NULL || (unload=1, m=GetModuleInfo(¶ms))!=NULL) + { + if (title) + { + if (!params.titleLength || params.flags&PPF_ADD_TITLE) + lstrcpyn(title, m->songname, GETFILEINFO_TITLE_LENGTH); + else + lstrcpyn(title, params.title, GETFILEINFO_TITLE_LENGTH); + } + // set playing time + if (length_in_ms) + *length_in_ms = m->songlen; + + // clean up + if (unload && m!=mf) + Unimod_Free(m); + } + // invalid module or smth else + else + { + lstrcpyn(title, GetFileName(params.file), GETFILEINFO_TITLE_LENGTH); + if (length_in_ms) + *length_in_ms = -1; + } +} + +// ===================================================================================== +// misc stuff +// ===================================================================================== + +static void __cdecl setvolume(int volume) { mikmod.outMod->SetVolume(volume); } +static void __cdecl setpan(int pan) { mikmod.outMod->SetPan(pan); } +static void __cdecl eq_set(int on, char data[10], int preamp) {} + +static CHAR capnstupid[4096]; + +// ===================================================================================== +In_Module mikmod = +// ===================================================================================== +{ + IN_VER_RET, + "nullsoft(in_mod.dll)", // need to set this to some form of valid buffer otherwise in_bass crashes (why it's looking at this i don't know!!) + 0, // hMainWindow + 0, // hDllInstance + capnstupid, + 1, // is_seekable + 1, // uses_output_plug + config, + 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, // vis stuff + + 0,0, // dsp shit + + eq_set, + + NULL, // setinfo + NULL // outmod +}; + +// ===================================================================================== +extern "C" __declspec(dllexport) In_Module *__cdecl winampGetInModule2() +// input module getter. the only thing exported from here. +// ===================================================================================== +{ + return &mikmod; +} + + +// ===================================================================================== +static DWORD WINAPI decodeThread(void *unused) +// ===================================================================================== +{ + int has_flushed = 0; + + while (!killDecodeThread) + { + if (seek_needed >= 0) + { + int ms = seek_needed; + seek_needed = -1; + + if (!(config_playflag & CPLAYFLG_SEEKBYORDERS)) + { + Player_SetPosTime(mp, ms); + decode_pos = ms * 64; + } + else Player_SetPosition(mp, ms/1000, TRUE); + + mikmod.outMod->Flush(ms); + if (paused) mikmod.outMod->Pause(1); + } + + if (!Player_Active(mp)) + { + // check for infinite looping + // infinite looping is done manually (only here). we check if + // it was requested and if the loop is required (song ended + // with loop or unconditional looping is on) + if (mp->loopcount!=-1 || !(mp->flags&PF_LOOP || mp->state.looping<0)) + { + if (!has_flushed) + { + has_flushed = 1; + mikmod.outMod->Write(NULL, 0); // write all samples into buffer queue + } + if (!mikmod.outMod->IsPlaying()) + { + PostMessage(mikmod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return 0; + } + else mikmod.outMod->CanWrite(); // make sure plug-in can do any extra processing needed + Sleep(20); + } + else + { + Player_Restart(mp, TRUE); + decode_pos = mp->state.curtime; + } + } + else + { + Mikmod_Update(md); + Sleep(8); + } + } + + return 0; +} + + +// ===================================================================================== +void set_priority(void) // also used in config.c +// ===================================================================================== +{ + if (thread_handle != INVALID_HANDLE_VALUE) + SetThreadPriority(thread_handle, GetThreadPriorityConfig()); +} + + +BOOL WINAPI DllMain(HANDLE h, DWORD r, void *z) +{ + if (r == DLL_PROCESS_ATTACH) + { + DisableThreadLibraryCalls((HMODULE)h); + } + return 1; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mod/mikamp/src/api.h b/Src/Plugins/Input/in_mod/mikamp/src/api.h new file mode 100644 index 00000000..b514a409 --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/src/api.h @@ -0,0 +1,11 @@ +#ifndef NULLSOFT_API_H +#define NULLSOFT_API_H + +#include <api/service/waServiceFactory.h> + +#include "../Agave/Language/api_language.h" + +#include <api/application/api_application.h> +#define WASABI_API_APP applicationApi + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mod/mikamp/src/drv_amp.c b/Src/Plugins/Input/in_mod/mikamp/src/drv_amp.c new file mode 100644 index 00000000..b84c818b --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/src/drv_amp.c @@ -0,0 +1,254 @@ +#include <windows.h> +#include <malloc.h> +#include "mikmod.h" +#include "virtch.h" +#include "main.h" +#include <io.h> +#include <sys/stat.h> +#include <fcntl.h> + +#define BUFSIZE 28 + +typedef struct AMP_LOCALINFO +{ + uint mode; + uint mixspeed; + uint channels; + + SBYTE RAW_DMABUF[(BUFSIZE*1024*2) + 64]; // added 64 for mmx mixer (it's not exact :) + + int block_len, bits,ismono, bytes_per_sec; + +} AMP_LOCALINFO; + +extern int decode_pos; // from main.c + +// ===================================================================================== + static BOOL RAW_IsThere(void) +// ===================================================================================== +{ + return 1; +} + + +// ===================================================================================== + static BOOL RAW_Init(MDRIVER *md, uint latency, void *optstr) +// ===================================================================================== +{ + AMP_LOCALINFO *hwdata; + + hwdata = (AMP_LOCALINFO *)MikMod_calloc(md->allochandle, 1, sizeof(AMP_LOCALINFO)); + + md->device.vc = VC_Init(); + if(!md->device.vc) + { mikmod.outMod->Close(); + return 1; + } + + hwdata->mode = DMODE_16BITS | DMODE_INTERP | DMODE_NOCLICK; + hwdata->mixspeed = 48000; + hwdata->channels = 2; + + md->device.local = hwdata; + + return 0; +} + + +// ===================================================================================== + static void RAW_Exit(MDRIVER *md) +// ===================================================================================== +{ + VC_Exit(md->device.vc); + mikmod.outMod->Close(); +} + + +// ===================================================================================== + static void RAW_Update(MDRIVER *md) +// ===================================================================================== +{ + AMP_LOCALINFO *hwdata = md->device.local; + int l; + char * vis; + int visbits; + + if ((l=mikmod.outMod->CanWrite()) > hwdata->block_len*16) l = hwdata->block_len*16; + if (mikmod.dsp_isactive()) l>>=1; + + if (l > hwdata->block_len) + { int o=0; + + l -= l % hwdata->block_len; + VC_WriteBytes(md, hwdata->RAW_DMABUF, l); + + while (o < l) + { + int a = min(hwdata->block_len,l-o); + + if (mikmod.dsp_isactive()) + { int t; + int k = (hwdata->bits>>3)*(hwdata->ismono?1:2); + + t = mikmod.dsp_dosamples((short *)(hwdata->RAW_DMABUF+o),a / k,hwdata->bits,(hwdata->ismono?1:2),hwdata->mixspeed) * k; + mikmod.outMod->Write(hwdata->RAW_DMABUF+o,t); + } else + mikmod.outMod->Write(hwdata->RAW_DMABUF+o,a); + + vis=hwdata->RAW_DMABUF+o; + visbits=hwdata->bits; + + if (visbits > 16) + { + uint n = 576 * 2>>hwdata->ismono; + const uint d = visbits >> 3; + WORD *const visbuf = (WORD*)alloca(n * sizeof(WORD)); + char *ptr = vis + d - 2; + WORD *vp = visbuf; + + for (;n;n--) + { + *vp++ = *(WORD*)ptr; + ptr += d; + } + vis=(char*)visbuf; + visbits=16; + } + + mikmod.SAAddPCMData(vis,hwdata->ismono ? 1 : 2, visbits, decode_pos/64); + mikmod.VSAAddPCMData(vis,hwdata->ismono ? 1 : 2, visbits, decode_pos/64); + + decode_pos += (a*1000*64) / hwdata->bytes_per_sec; + o+=a; + } + } else Sleep(6); +} + + +// ===================================================================================== + static BOOL RAW_SetMode(MDRIVER *md, uint mixspeed, uint mode, uint channels, uint cpumode) +// ===================================================================================== +{ + AMP_LOCALINFO *hwdata = md->device.local; + + // Check capabilities... + // [...] + + // Set the new mode of play + + if (mixspeed) hwdata->mixspeed = mixspeed; + + if(!(mode & DMODE_DEFAULT)) hwdata->mode = mode; + + switch(channels) + { case MD_MONO: + hwdata->channels = 1; + break; + + default: + hwdata->channels = 2; + channels = MD_STEREO; + break; + } + + VC_SetMode(md->device.vc, hwdata->mixspeed, hwdata->mode, channels, cpumode); + + { + int bits = (hwdata->mode & DMODE_16BITS) ? 16: ((hwdata->mode & DMODE_24BITS) ? 24 : 8); + int z; + int a = 576*2*(bits>>3); + + hwdata->bits = bits; + hwdata->ismono = (hwdata->channels == 1) ? 1 : 0; + + if (hwdata->ismono) a/=2; + + hwdata->block_len = a; + + hwdata->bytes_per_sec = hwdata->mixspeed * (hwdata->bits>>3) * (hwdata->ismono ? 1 : 2); + + z = mikmod.outMod->Open(hwdata->mixspeed,hwdata->channels,bits,-1,-1); + if (z < 0) return 1; + + mikmod.SAVSAInit(z,hwdata->mixspeed); + mikmod.VSASetInfo(hwdata->mixspeed,hwdata->channels); + + mikmod.outMod->SetVolume(-666); + } + + return 0; +} + + +// ===================================================================================== + static BOOL AMP_SetSoftVoices(MDRIVER *md, uint voices) +// ===================================================================================== +{ + return VC_SetSoftVoices(md->device.vc, voices); +} + + +// ===================================================================================== + static void AMP_GetMode(MDRIVER *md, uint *mixspeed, uint *mode, uint *channels, uint *cpumode) +// ===================================================================================== +{ + VC_GetMode(md->device.vc, mixspeed, mode, channels, cpumode); +} + + +// ===================================================================================== + MD_DEVICE drv_amp = +// ===================================================================================== +{ + "win32au", + BLAH("Nullsoft win32 output driver v0.700"), + 0, VC_MAXVOICES, + + NULL, + NULL, + NULL, + + // Sample Loading + VC_SampleAlloc, + VC_SampleGetPtr, + VC_SampleLoad, + VC_SampleUnload, + VC_SampleSpace, + VC_SampleLength, + + // Detection and Initialization + RAW_IsThere, + RAW_Init, + RAW_Exit, + RAW_Update, + VC_Preempt, + + NULL, + AMP_SetSoftVoices, + + RAW_SetMode, + AMP_GetMode, + + VC_SetVolume, + VC_GetVolume, + + // Voice control and Voie information + VC_GetActiveVoices, + + VC_VoiceSetVolume, + VC_VoiceGetVolume, + VC_VoiceSetFrequency, + VC_VoiceGetFrequency, + VC_VoiceSetPosition, + VC_VoiceGetPosition, + VC_VoiceSetSurround, + VC_VoiceSetResonance, + + VC_VoicePlay, + VC_VoiceResume, + VC_VoiceStop, + VC_VoiceStopped, + VC_VoiceReleaseSustain, + VC_VoiceRealVolume, + +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mod/mikamp/src/drv_buffer.cpp b/Src/Plugins/Input/in_mod/mikamp/src/drv_buffer.cpp new file mode 100644 index 00000000..ed75d798 --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/src/drv_buffer.cpp @@ -0,0 +1,176 @@ +#include <windows.h> +#include <malloc.h> +#include "mikmod.h" +#include "virtch.h" +#include "main.h" +#include <io.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "drv_buffer.h" + +#define BUFSIZE 28 + + +// ===================================================================================== +static BOOL Decode_IsThere(void) +// ===================================================================================== +{ + return 1; +} + + +// ===================================================================================== +static BOOL Decode_Init(MDRIVER *md, uint latency, void *optstr) +// ===================================================================================== +{ + DecodeInfo *hwdata; + + hwdata = (DecodeInfo *)MikMod_calloc(md->allochandle, 1, sizeof(DecodeInfo)); + + md->device.vc = VC_Init(); + if(!md->device.vc) + { + hwdata->error = 1; + return 1; + } + + hwdata->mode = DMODE_16BITS | DMODE_INTERP | DMODE_NOCLICK; + hwdata->mixspeed = 48000; + hwdata->channels = 2; + + md->device.local = hwdata; + + return 0; +} + + +// ===================================================================================== +static void Decode_Exit(MDRIVER *md) +// ===================================================================================== +{ + DecodeInfo *hwdata = (DecodeInfo *)md->device.local; + VC_Exit(md->device.vc); +} + + +// ===================================================================================== +static void Decode_Update(MDRIVER *md) +// ===================================================================================== +{ + DecodeInfo *hwdata = (DecodeInfo *)md->device.local; + + hwdata->bytesWritten = VC_WriteBytes(md, (SBYTE *)hwdata->buffer, hwdata->buffersize); +} + + +// ===================================================================================== +static BOOL Decode_SetMode(MDRIVER *md, uint mixspeed, uint mode, uint channels, uint cpumode) +// ===================================================================================== +{ + DecodeInfo *hwdata = (DecodeInfo *)md->device.local; + + // Check capabilities... + // [...] + + // Set the new mode of play + + if (mixspeed) hwdata->mixspeed = mixspeed; + + if(!(mode & DMODE_DEFAULT)) hwdata->mode = mode; + + switch(channels) + { + case MD_MONO: + hwdata->channels = 1; + break; + + default: + hwdata->channels = 2; + channels = MD_STEREO; + break; + } + + VC_SetMode(md->device.vc, hwdata->mixspeed, hwdata->mode, channels, cpumode); + + int bits = (hwdata->mode & DMODE_16BITS) ? 16: ((hwdata->mode & DMODE_24BITS) ? 24 : 8); + + hwdata->bits = bits; + hwdata->frame_size = MulDiv(hwdata->channels, bits, 8); + + + return 0; +} + + +// ===================================================================================== +static BOOL Decode_SetSoftVoices(MDRIVER *md, uint voices) +// ===================================================================================== +{ + return VC_SetSoftVoices(md->device.vc, voices); +} + + +// ===================================================================================== +static void Decode_GetMode(MDRIVER *md, uint *mixspeed, uint *mode, uint *channels, uint *cpumode) +// ===================================================================================== +{ + VC_GetMode(md->device.vc, mixspeed, mode, channels, cpumode); +} + + +// ===================================================================================== +extern "C" MD_DEVICE drv_buffer = +// ===================================================================================== +{ + "ExtendedRead", + BLAH("Nullsoft Extended Read decode driver v0.1"), + 0, VC_MAXVOICES, + + NULL, + NULL, + NULL, + + // Sample Loading + VC_SampleAlloc, + VC_SampleGetPtr, + VC_SampleLoad, + VC_SampleUnload, + VC_SampleSpace, + VC_SampleLength, + + // Detection and Initialization + Decode_IsThere, + Decode_Init, + Decode_Exit, + Decode_Update, + VC_Preempt, + + NULL, + Decode_SetSoftVoices, + + Decode_SetMode, + Decode_GetMode, + + VC_SetVolume, + VC_GetVolume, + + // Voice control and Voie information + VC_GetActiveVoices, + + VC_VoiceSetVolume, + VC_VoiceGetVolume, + VC_VoiceSetFrequency, + VC_VoiceGetFrequency, + VC_VoiceSetPosition, + VC_VoiceGetPosition, + VC_VoiceSetSurround, + VC_VoiceSetResonance, + + VC_VoicePlay, + VC_VoiceResume, + VC_VoiceStop, + VC_VoiceStopped, + VC_VoiceReleaseSustain, + VC_VoiceRealVolume, + +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mod/mikamp/src/drv_buffer.h b/Src/Plugins/Input/in_mod/mikamp/src/drv_buffer.h new file mode 100644 index 00000000..e96006c9 --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/src/drv_buffer.h @@ -0,0 +1,18 @@ +#pragma once +#include "mikmod.h" +struct DecodeInfo +{ + uint mode; + uint mixspeed; + uint channels; + + void *buffer; + size_t buffersize; + + int bits; + int frame_size; // cached channels*bits/8 + int error; + size_t bytesWritten; +}; + +//extern MD_DEVICE drv_buffer; diff --git a/Src/Plugins/Input/in_mod/mikamp/src/rf_wrapper.c b/Src/Plugins/Input/in_mod/mikamp/src/rf_wrapper.c new file mode 100644 index 00000000..3431affd --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/src/rf_wrapper.c @@ -0,0 +1,241 @@ +#include <windows.h> +#include <mmio.h> +#include <shlwapi.h> +#include "../../winamp/wa_ipc.h" +#include "../../winamp/in2.h" +extern In_Module mikmod; +//big mess here + +typedef struct // quick _thiscall hack +{ + char *(_fastcall *GetDescription)(void*,int); + int (_fastcall *Open)(void*,int,char *url, int *killswitch); + int (_fastcall *Read)(void*,int,void *buffer, int length, int *killswitch); + int (_fastcall *GetLength)(void*); + int (_fastcall *CanSeek)(void*); + int (_fastcall *Seek)(void*,int,int position, int *killswitch); + char *(_fastcall *GetHeader)(void*,int,char *name); + void (_fastcall *Release)(void*,int,int); // rough ~WReader() hack +} RF_vtbl; + +#define RF_Open(x,a,b) (*x)->Open(x,0,a,b) +#define RF_Read(x,a,b,c) (*x)->Read(x,0,a,b,c) +#define RF_GetLength(x) (*x)->GetLength(x) +#define RF_Seek(x,a,b) (*x)->Seek(x,0,a,b) +#define RF_Release(x) (*x)->Release(x,0,0) + +#define READ_VER 0x100 + +typedef struct +{ + int version; + char *description; + + RF_vtbl ** (_cdecl *create)(); + + int (_cdecl *ismine)(char *url); + +} reader_source; + +typedef int (_cdecl *RF_entry)(HINSTANCE hIns,reader_source** s); + +static int initialized,got_dll; +static HINSTANCE hRF; + +typedef struct +{ + RF_vtbl ** r; + UINT size,pos; +} RFstruct; + +static RF_vtbl** (_cdecl *rf_create)(); + +static int rf_init() +{ + wchar_t fn[MAX_PATH] = {0}; + RF_entry rf_entry; + reader_source * source; + + if (initialized) return got_dll; + initialized=1; + + PathCombineW(fn, (wchar_t*)SendMessage(mikmod.hMainWindow, WM_WA_IPC, 0, IPC_GETSHAREDDLLDIRECTORYW), L"read_file.dll"); + + hRF=LoadLibraryW(fn); + if (!hRF) return 0; + + rf_entry = (RF_entry)GetProcAddress(hRF,"readerSource"); + if (!rf_entry) + { + FreeLibrary(hRF); + return 0; + } + + rf_entry(hRF,&source); + + if (source->version!=READ_VER) + { + FreeLibrary(hRF); + return 0; + } + + rf_create=source->create; + + got_dll=1; + return 1; +} + +static void rf_quit() +{ + if (got_dll) + { + FreeLibrary(hRF); + got_dll=0; + } + initialized=0; +} + +static void * _cdecl rfopen(const char * fn) +{ + int ks; + RF_vtbl ** r; + RFstruct * rs; + + + if (!got_dll) return 0; + + r=rf_create(); + if (!r) return 0; + + ks=0; + + if (RF_Open(r,(char*)fn,&ks)) + { + RF_Release(r); + return 0; + } + + rs=malloc(sizeof(RFstruct)); + if (!rs) + { + RF_Release(r); + return 0; + } + rs->r=r; + rs->pos=0; + rs->size=RF_GetLength(r); + + return rs; +} + +static size_t _cdecl rfread( void *buffer, size_t size, size_t count, void *stream ) +{ + RFstruct * rs; + int ks,rv; + UINT siz; + rs=stream; + ks=0; + siz=size*count; + if (siz>rs->size-rs->pos) siz=rs->size-rs->pos;//just to be sure + rv=RF_Read(rs->r,buffer,siz,&ks); + if (rv>0) rs->pos+=rv; + return rv; + +} + +static size_t _cdecl rfwrite( const void *buffer, size_t size, size_t count, void *stream ) {return -1;} + +static int _cdecl rfgetc( void *stream ) +{ + RFstruct * rs; + int rv,ks; + rv=0; + ks=0; + rs=stream; + + if (RF_Read(rs->r,&rv,1,&ks)>0) rs->pos++; + else rv=EOF; + return rv; +} + +static int _cdecl rfputc( int c, void *stream ) +{ + // not implemented + return -1; +} + +static int _cdecl rfseek( void *stream, long offset, int origin ) +{ + RFstruct * rs; + int ks; + UINT new_pos; + + ks=0; + rs=stream; + + switch(origin) + { + case SEEK_CUR: + new_pos=rs->pos+offset; + break; + case SEEK_END: + new_pos=rs->size+offset; + break; + case SEEK_SET: + new_pos=offset; + break; + default: + return -1; + } + if (new_pos>rs->size) new_pos=rs->size; + if (RF_Seek(rs->r,new_pos,&ks)) + { + return -1; + } + rs->pos=new_pos; + return 0; +} + +static long _cdecl rftell(void * stream) +{ + RFstruct * rs=stream; + return rs->pos; +} + +static int _cdecl rfeof(void * stream) +{ + RFstruct * rs=stream; + return rs->pos==rs->size; +} + +static int _cdecl rfclose(void * stream) +{ + RFstruct * rs=stream; + RF_Release(rs->r); + free(rs); + return 0; +} + +static const MMSTREAM_CALLBACK callback_rf = +{ + rfread, + rfwrite, + rfgetc, + rfputc, + rfseek, + rftell, + rfeof, + rfclose +}; + + +MMSTREAM *_mm_fopen_rf(const CHAR *fname) +{ + void * handle; + if (!rf_init()) return 0; + handle = rfopen(fname); + if (!handle) return _mm_fopen(fname,"rb"); + return _mmstream_createfp_callback(handle,0,&callback_rf); +} + + diff --git a/Src/Plugins/Input/in_mod/mikamp/version.rc2 b/Src/Plugins/Input/in_mod/mikamp/version.rc2 new file mode 100644 index 00000000..62a67dcc --- /dev/null +++ b/Src/Plugins/Input/in_mod/mikamp/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 2,94,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", "2,94,0,0" + VALUE "InternalName", "Nullsoft Module Decoder" + VALUE "LegalCopyright", "Copyright © 1998-2014 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_mod.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_mp3/AACFrame.cpp b/Src/Plugins/Input/in_mp3/AACFrame.cpp new file mode 100644 index 00000000..b2e8384d --- /dev/null +++ b/Src/Plugins/Input/in_mp3/AACFrame.cpp @@ -0,0 +1,94 @@ +#include "AACFrame.h" +#include "api__in_mp3.h" +#include "resource.h" +#include "in2.h" +extern In_Module mod; + +void AACFrame::ReadBuffer( unsigned __int8 *buffer ) +{ + syncword = ( buffer[ 0 ] << 4 ) | ( buffer[ 1 ] >> 4 ); + id = ( buffer[ 1 ] >> 3 ) & 1; + layer = ( buffer[ 1 ] >> 1 ) & 3; + protection = ( buffer[ 1 ] ) & 1; + profile = ( buffer[ 2 ] >> 6 ) & 3; + sampleRateIndex = ( buffer[ 2 ] >> 2 ) & 0xF; + privateBit = ( buffer[ 2 ] >> 1 ) & 1; + channelConfiguration = ( ( buffer[ 2 ] & 1 ) << 2 ) | ( ( buffer[ 3 ] >> 6 ) & 3 ); + original = ( buffer[ 3 ] >> 5 ) & 1; + home = ( buffer[ 3 ] >> 4 ) & 1; + + //copyright_identification_bit = (buffer[3] >> 3) & 1; + //copyright_identification_start = (buffer[3] >> 2) & 1; + frameLength = ( ( buffer[ 3 ] & 3 ) << 11 ) | ( buffer[ 4 ] << 3 ) | ( ( buffer[ 5 ] >> 5 ) & 7 ); + bufferFullness = ( ( buffer[ 5 ] & 0xF8 ) << 5 ) | ( ( buffer[ 6 ] >> 2 ) & 0x3F ); + numDataBlocks = buffer[ 6 ] & 3; +} + +bool AACFrame::OK() +{ + if (syncword == SYNC + && layer == 0 + && sampleRateIndex < 13 + //&& profile != LTP // TODO: can coding technologies decoder do LTP? + ) + return true; + else + return false; +} + + +static const unsigned int aac_sratetab[] = + { + 96000, + 88200, + 64000, + 48000, + 44100, + 32000, + 24000, + 22050, + 16000, + 12000, + 11025, + 8000, + 7350, + }; + +int AACFrame::GetSampleRate() +{ + return aac_sratetab[sampleRateIndex]; +} + +static const wchar_t *aac_profiletab[] = {L"Main", L"LC", L"SSR", L"LTP"}; + +const wchar_t *AACFrame::GetProfileName() +{ + return aac_profiletab[profile]; +} + +//static const char *aac_channels[] = {"Custom", "Mono", "Stereo", "3 channel", "4 channel", "surround", "5.1", "7.1"}; +static wchar_t aac_channels_str[64]; +static int aac_channels_id[] = {IDS_CUSTOM, IDS_MONO, IDS_STEREO, IDS_3_CHANNEL, IDS_4_CHANNEL, IDS_SURROUND, IDS_5_1, IDS_7_1}; +const wchar_t *AACFrame::GetChannelConfigurationName() +{ + return WASABI_API_LNGSTRINGW_BUF(aac_channels_id[channelConfiguration],aac_channels_str,64); +} + +int AACFrame::GetNumChannels() +{ + switch(channelConfiguration) + { + case 7: + return 8; + default: + return channelConfiguration; + } +} + +int AACFrame::GetMPEGVersion() +{ + if (id == 0) + return 2; + else + return 4; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/AACFrame.h b/Src/Plugins/Input/in_mp3/AACFrame.h new file mode 100644 index 00000000..c53bd18c --- /dev/null +++ b/Src/Plugins/Input/in_mp3/AACFrame.h @@ -0,0 +1,43 @@ +#ifndef NULLSOFT_AACFRAME_H +#define NULLSOFT_AACFRAME_H + +class AACFrame +{ +public: + void ReadBuffer(unsigned __int8 *buffer); + bool OK(); + + enum + { + NOT_PROTECTED=1, + PROTECTED=0, + SYNC = 0xFFF, + MAIN = 0x00, + LC = 0x01, + SSR = 0x10, + LTP = 0x11, + }; + int GetNumChannels(); + int GetSampleRate(); + const wchar_t *GetProfileName(); + const wchar_t *GetChannelConfigurationName(); + int GetMPEGVersion(); // returns 2 or 4 +public: + int syncword; + int layer; + int id; + int protection; + int profile; + int sampleRateIndex; + int privateBit; + int channelConfiguration; + int original; + int home; + int frameLength; + int bufferFullness; + int numDataBlocks; + + +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/AlbumArt.cpp b/Src/Plugins/Input/in_mp3/AlbumArt.cpp new file mode 100644 index 00000000..b0bd78a2 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/AlbumArt.cpp @@ -0,0 +1,283 @@ +#include "main.h" +#include "Metadata.h" +#include "api__in_mp3.h" +#include "../nu/AutoWide.h" +#include "AlbumArt.h" +#include "Stopper.h" +#include <shlwapi.h> +#include <strsafe.h> + +bool IsMyExtension(const wchar_t *filename) +{ + // check if it's the current stream and is playing and is SHOUTcast2 + if (PathIsURLW(filename)) + { + if (g_playing_file) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file && + (g_playing_file->uvox_artwork.uvox_stream_artwork || g_playing_file->uvox_artwork.uvox_playing_artwork) && + !lstrcmpW(lastfn, filename)) // check again now that we've acquired the lock + { + LeaveCriticalSection(&streamInfoLock); + return true; + } + LeaveCriticalSection(&streamInfoLock); + } + } + // otherwise handle as normal embedded + else + { + const wchar_t *extension = PathFindExtension(filename); + if (extension && *extension) + { + AutoWide wideList(config_extlist); // TODO: build a copy of this at config load time so we don't have to run this every time + extension++; + wchar_t *b = wideList; + + wchar_t *c = 0; + do + { + wchar_t d[20] = {0}; + StringCchCopyW(d, 15, b); + if ((c = wcschr(b, L';'))) + { + if ((c-b)<15) + d[c - b] = 0; + } + + if (!lstrcmpiW(extension, d)) + return true; + + b = c + 1; + } + while (c); + } + } + return false; +} + +bool ID3v2_AlbumArtProvider::IsMine(const wchar_t *filename) +{ + return IsMyExtension(filename); +} + +int ID3v2_AlbumArtProvider::ProviderType() +{ + return ALBUMARTPROVIDER_TYPE_EMBEDDED; +} + +int ID3v2_AlbumArtProvider::GetAlbumArtData(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType) +{ + if (g_playing_file) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file && !lstrcmpW(lastfn, filename)) // check again now that we've acquired the lock + { + wchar_t* mimeType[] = { + L"image/jpeg", + L"image/png", + L"image/bmp", + L"image/gif" + }; + if (!g_playing_file->uvox_artwork.uvox_stream_artwork) + { + int ret = g_playing_file->info.GetAlbumArt(type, bits, len, mimeType); + LeaveCriticalSection(&streamInfoLock); + return ret; + } + else + { + // will handle "playing" and "cover" - cover is the stream branding + // with "playing" used to provide song specific stream artwork + if (!_wcsicmp(type, L"playing")) + { + if (g_playing_file->uvox_artwork.uvox_playing_artwork_len > 0) + { + *len = g_playing_file->uvox_artwork.uvox_playing_artwork_len; + int type = g_playing_file->uvox_artwork.uvox_playing_artwork_type; + if(type >= 0 && type <= 3) + { + *mimeType = (wchar_t *)WASABI_API_MEMMGR->sysMalloc(12 * sizeof(wchar_t)); + wcsncpy(*mimeType, mimeType[type], 12); + } + else + { + *mimeType = 0; + } + + *bits = WASABI_API_MEMMGR->sysMalloc(*len); + memcpy(*bits, g_playing_file->uvox_artwork.uvox_playing_artwork, *len); + + LeaveCriticalSection(&streamInfoLock); + return ALBUMARTPROVIDER_SUCCESS; + } + else + { + LeaveCriticalSection(&streamInfoLock); + return ALBUMARTPROVIDER_FAILURE; + } + } + else + { + if (g_playing_file->uvox_artwork.uvox_stream_artwork_len > 0) + { + *len = g_playing_file->uvox_artwork.uvox_stream_artwork_len; + + int type = g_playing_file->uvox_artwork.uvox_stream_artwork_type; + if(type >= 0 && type <= 3) + { + *mimeType = (wchar_t *)WASABI_API_MEMMGR->sysMalloc(12 * sizeof(wchar_t)); + wcsncpy(*mimeType, mimeType[type], 12); + } + else + { + *mimeType = 0; + } + + *bits = WASABI_API_MEMMGR->sysMalloc(*len); + memcpy(*bits, g_playing_file->uvox_artwork.uvox_stream_artwork, *len); + + LeaveCriticalSection(&streamInfoLock); + return ALBUMARTPROVIDER_SUCCESS; + } + else + { + LeaveCriticalSection(&streamInfoLock); + return ALBUMARTPROVIDER_FAILURE; + } + } + } + } + LeaveCriticalSection(&streamInfoLock); + } + + Metadata metadata; + if (metadata.Open(filename) == METADATA_SUCCESS) + { + return metadata.id3v2.GetAlbumArt(type, bits, len, mimeType); + } + + return ALBUMARTPROVIDER_FAILURE; +} + +extern Metadata *m_ext_get_mp3info; + +int ID3v2_AlbumArtProvider::SetAlbumArtData(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType) +{ + Metadata metadata; + if (metadata.Open(filename) == METADATA_SUCCESS) + { + int ret = metadata.id3v2.SetAlbumArt(type, bits, len, mimeType); + if (ret == METADATA_SUCCESS) + { + // flush our read cache too :) + if (m_ext_get_mp3info) m_ext_get_mp3info->Release(); + m_ext_get_mp3info = NULL; + + Stopper stopper; + if (metadata.IsMe(lastfn)) + stopper.Stop(); + metadata.Save(); + stopper.Play(); + } + return ret; + } + return ALBUMARTPROVIDER_FAILURE; +} + +int ID3v2_AlbumArtProvider::DeleteAlbumArt(const wchar_t *filename, const wchar_t *type) +{ + Metadata metadata; + if (metadata.Open(filename) == METADATA_SUCCESS) + { + int ret = metadata.id3v2.DeleteAlbumArt(type); + if (ret == METADATA_SUCCESS) + { + // flush our read cache too :) + if (m_ext_get_mp3info) m_ext_get_mp3info->Release(); + m_ext_get_mp3info = NULL; + + Stopper stopper; + if (metadata.IsMe(lastfn)) + stopper.Stop(); + metadata.Save(); + stopper.Play(); + } + return ret; + } + return ALBUMARTPROVIDER_FAILURE; +} + +#define CBCLASS ID3v2_AlbumArtProvider +START_DISPATCH; +CB(SVC_ALBUMARTPROVIDER_PROVIDERTYPE, ProviderType); +CB(SVC_ALBUMARTPROVIDER_GETALBUMARTDATA, GetAlbumArtData); +CB(SVC_ALBUMARTPROVIDER_ISMINE, IsMine); +CB(SVC_ALBUMARTPROVIDER_SETALBUMARTDATA, SetAlbumArtData); +CB(SVC_ALBUMARTPROVIDER_DELETEALBUMART, DeleteAlbumArt); +END_DISPATCH; +#undef CBCLASS + +static ID3v2_AlbumArtProvider albumArtProvider; + +// {C8222317-8F0D-4e79-9222-447381C46E07} +static const GUID id3v2_albumartproviderGUID = + { 0xc8222317, 0x8f0d, 0x4e79, { 0x92, 0x22, 0x44, 0x73, 0x81, 0xc4, 0x6e, 0x7 } }; + +FOURCC AlbumArtFactory::GetServiceType() +{ + return svc_albumArtProvider::SERVICETYPE; +} + +const char *AlbumArtFactory::GetServiceName() +{ + return "ID3v2 Album Art Provider"; +} + +GUID AlbumArtFactory::GetGUID() +{ + return id3v2_albumartproviderGUID; +} + +void *AlbumArtFactory::GetInterface(int global_lock) +{ + return &albumArtProvider; +} + +int AlbumArtFactory::SupportNonLockingInterface() +{ + return 1; +} + +int AlbumArtFactory::ReleaseInterface(void *ifc) +{ + //plugin.service->service_unlock(ifc); + return 1; +} + +const char *AlbumArtFactory::GetTestString() +{ + return 0; +} + +int AlbumArtFactory::ServiceNotify(int msg, int param1, int param2) +{ + return 1; +} + +#ifdef CBCLASS +#undef CBCLASS +#endif + +#define CBCLASS AlbumArtFactory +START_DISPATCH; +CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType) +CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName) +CB(WASERVICEFACTORY_GETGUID, GetGUID) +CB(WASERVICEFACTORY_GETINTERFACE, GetInterface) +CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface) +CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface) +CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString) +CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify) +END_DISPATCH;
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/AlbumArt.h b/Src/Plugins/Input/in_mp3/AlbumArt.h new file mode 100644 index 00000000..a01c826d --- /dev/null +++ b/Src/Plugins/Input/in_mp3/AlbumArt.h @@ -0,0 +1,39 @@ +#ifndef NULLSOFT_IN_MP3_ALBUMART_H +#define NULLSOFT_IN_MP3_ALBUMART_H + +#include "../Agave/AlbumArt/svc_albumArtProvider.h" + +class ID3v2_AlbumArtProvider : public svc_albumArtProvider +{ +public: + bool IsMine(const wchar_t *filename); + int ProviderType(); + // implementation note: use WASABI_API_MEMMGR to alloc bits and mimetype, so that the recipient can free through that + int GetAlbumArtData(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType); + int SetAlbumArtData(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType); + int DeleteAlbumArt(const wchar_t *filename, const wchar_t *type); +protected: + RECVS_DISPATCH; +}; + +#include <api/service/waservicefactory.h> +#include <api/service/services.h> + +class AlbumArtFactory : public waServiceFactory +{ +public: + FOURCC GetServiceType(); + const char *GetServiceName(); + GUID GetGUID(); + void *GetInterface(int global_lock); + int SupportNonLockingInterface(); + int ReleaseInterface(void *ifc); + const char *GetTestString(); + int ServiceNotify(int msg, int param1, int param2); + +protected: + RECVS_DISPATCH; +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/CVbriHeader.cpp b/Src/Plugins/Input/in_mp3/CVbriHeader.cpp new file mode 100644 index 00000000..d8eebd84 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/CVbriHeader.cpp @@ -0,0 +1,327 @@ +//---------------------------------------------------------------------------\ +// +// (C) copyright Fraunhofer - IIS (2000) +// All Rights Reserved +// +// filename: CVbriHeader.cpp +// MPEG Layer-3 Audio Decoder +// author : Martin Weishart martin.weishart@iis.fhg.de +// date : 2000-02-11 +// contents/description: provides functions to read a VBRI header +// of a MPEG Layer 3 bitstream encoded +// with variable bitrate using Fraunhofer +// variable bitrate format +// +//--------------------------------------------------------------------------/ + +#include <windows.h> + +#include "CVbriHeader.h" +#include "LAMEInfo.h" +#include <malloc.h> + +//---------------------------------------------------------------------------\ +// +// Constructor: set position in buffer to parse and create a +// VbriHeaderTable +// +//---------------------------------------------------------------------------/ + +CVbriHeader::CVbriHeader(){ + position = 0; + VbriTable=0; + VbriStreamFrames=0; + encoderDelay=0; + h_id=0; + SampleRate=0; + VbriTableSize=0; + VbriEntryFrames=0; + VbriStreamBytes=0; +} + + + +//---------------------------------------------------------------------------\ +// +// Destructor: delete a VbriHeaderTable and a VbriHeader +// +//---------------------------------------------------------------------------/ + +CVbriHeader::~CVbriHeader(){ + free(VbriTable); +} + + + +//---------------------------------------------------------------------------\ +// +// Method: checkheader +// Reads the header to a struct that has to be stored and is +// used in other functions to determine file offsets +// Input: buffer containing the first frame +// Output: fills struct VbriHeader +// Return: 0 on success; 1 on error +// +//---------------------------------------------------------------------------/ + +int CVbriHeader::readVbriHeader(unsigned char *Hbuffer) +{ + position=0; + // MPEG header + MPEGFrame frame; + frame.ReadBuffer(Hbuffer); + if (!frame.IsSync()) + return 0; + + SampleRate = frame.GetSampleRate(); + h_id = frame.mpegVersion & 1; + + position += DWORD ; + + // data indicating silence + position += (8*DWORD) ; + + // if a VBRI Header exists read it + + if ( *(Hbuffer+position ) == 'V' && + *(Hbuffer+position+1) == 'B' && + *(Hbuffer+position+2) == 'R' && + *(Hbuffer+position+3) == 'I'){ + + position += DWORD; + + //position += WORD; + /*unsigned int vbriVersion = */readFromBuffer(Hbuffer, WORD); // version + + encoderDelay = readFromBuffer(Hbuffer, WORD); // delay + + position += WORD; + //readFromBuffer(Hbuffer, WORD); // quality + + VbriStreamBytes = readFromBuffer(Hbuffer, DWORD); + VbriStreamFrames = readFromBuffer(Hbuffer, DWORD); + VbriTableSize = readFromBuffer(Hbuffer, WORD); + unsigned int VbriTableScale = readFromBuffer(Hbuffer, WORD); + unsigned int VbriEntryBytes = readFromBuffer(Hbuffer, WORD); + VbriEntryFrames = readFromBuffer(Hbuffer, WORD); + + if (VbriTableSize > 32768) return 1; + + VbriTable = (int *)calloc((VbriTableSize + 1), sizeof(int)); + + for (unsigned int i = 0 ; i <= VbriTableSize ; i++){ + VbriTable[i] = readFromBuffer(Hbuffer, VbriEntryBytes*BYTE) + * VbriTableScale ; + } + } + else + { + return 0; + } + return frame.FrameSize(); +} + + + +//---------------------------------------------------------------------------\ +// +// Method: seekPointByTime +// Returns a point in the file to decode in bytes that is nearest +// to a given time in seconds +// Input: time in seconds +// Output: None +// Returns: point belonging to the given time value in bytes +// +//---------------------------------------------------------------------------/ + +int CVbriHeader::seekPointByTime(float EntryTimeInMilliSeconds){ + + unsigned int SamplesPerFrame, i=0, SeekPoint = 0 , fraction = 0; + + float TotalDuration ; + float DurationPerVbriFrames ; + float AccumulatedTime = 0.0f ; + + (SampleRate >= 32000) ? (SamplesPerFrame = 1152) : (SamplesPerFrame = 576) ; + + TotalDuration = ((float)VbriStreamFrames * (float)SamplesPerFrame) + / (float)SampleRate * 1000.0f ; + DurationPerVbriFrames = (float)TotalDuration / (float)(VbriTableSize+1) ; + + if ( EntryTimeInMilliSeconds > TotalDuration ) EntryTimeInMilliSeconds = TotalDuration; + + while ( AccumulatedTime <= EntryTimeInMilliSeconds ){ + + SeekPoint += VbriTable[i] ; + AccumulatedTime += DurationPerVbriFrames; + i++; + + } + + // Searched too far; correct result + fraction = ( (int)(((( AccumulatedTime - EntryTimeInMilliSeconds ) / DurationPerVbriFrames ) + + (1.0f/(2.0f*(float)VbriEntryFrames))) * (float)VbriEntryFrames)); + + + SeekPoint -= (int)((float)VbriTable[i-1] * (float)(fraction) + / (float)VbriEntryFrames) ; + + return SeekPoint ; + +} + +int CVbriHeader::getNumMS() + { + if (!VbriStreamFrames || !SampleRate) return 0; + + int nf=VbriStreamFrames; + int sr=SampleRate; + if (sr >= 32000) sr/=2; + //576 + return MulDiv(nf,576*1000,sr); + } + +#if 0 +//---------------------------------------------------------------------------\ +// +// Method: seekTimeByPoint +// Returns a time in the file to decode in seconds that is +// nearest to a given point in bytes +// Input: time in seconds +// Output: None +// Returns: point belonging to the given time value in bytes +// +//---------------------------------------------------------------------------/ + +float CVbriHeader::seekTimeByPoint(unsigned int EntryPointInBytes){ + + unsigned int SamplesPerFrame, i=0, AccumulatedBytes = 0, fraction = 0; + + float SeekTime = 0.0f; + float TotalDuration ; + float DurationPerVbriFrames ; + + (SampleRate >= 32000) ? (SamplesPerFrame = 1152) : (SamplesPerFrame = 576) ; + + TotalDuration = ((float)VbriStreamFrames * (float)SamplesPerFrame) + / (float)SampleRate; + DurationPerVbriFrames = (float)TotalDuration / (float)(VbriTableSize+1) ; + + while (AccumulatedBytes <= EntryPointInBytes){ + + AccumulatedBytes += VbriTable[i] ; + SeekTime += DurationPerVbriFrames; + i++; + + } + + // Searched too far; correct result + fraction = (int)(((( AccumulatedBytes - EntryPointInBytes ) / (float)VbriTable[i-1]) + + (1/(2*(float)VbriEntryFrames))) * (float)VbriEntryFrames); + + SeekTime -= (DurationPerVbriFrames * (float) ((float)(fraction) / (float)VbriEntryFrames)) ; + + return SeekTime ; + +} + + + +//---------------------------------------------------------------------------\ +// +// Method: seekPointByPercent +// Returns a point in the file to decode in bytes that is +// nearest to a given percentage of the time of the stream +// Input: percent of time +// Output: None +// Returns: point belonging to the given time percentage value in bytes +// +//---------------------------------------------------------------------------/ + +int CVbriHeader::seekPointByPercent(float percent){ + + int SamplesPerFrame; + + float TotalDuration ; + + if (percent >= 100.0f) percent = 100.0f; + if (percent <= 0.0f) percent = 0.0f; + + (SampleRate >= 32000) ? (SamplesPerFrame = 1152) : (SamplesPerFrame = 576) ; + + TotalDuration = ((float)VbriStreamFrames * (float)SamplesPerFrame) + / (float)SampleRate; + + return seekPointByTime( (percent/100.0f) * TotalDuration * 1000.0f ); + +} + +#endif + + +//---------------------------------------------------------------------------\ +// +// Method: GetSampleRate +// Returns the sampling rate of the file to decode +// Input: Buffer containing the part of the first frame after the +// syncword +// Output: None +// Return: sampling rate of the file to decode +// +//---------------------------------------------------------------------------/ + +/*int CVbriHeader::getSampleRate(unsigned char * buffer){ + + unsigned char id, idx, mpeg ; + + id = (0xC0 & (buffer[1] << 3)) >> 4; + idx = (0xC0 & (buffer[2] << 4)) >> 6; + + mpeg = id | idx; + + switch ((int)mpeg){ + + case 0 : return 11025; + case 1 : return 12000; + case 2 : return 8000; + case 8 : return 22050; + case 9 : return 24000; + case 10: return 16000; + case 12: return 44100; + case 13: return 48000; + case 14: return 32000; + default: return 0; + + } +}*/ + + + +//---------------------------------------------------------------------------\ +// +// Method: readFromBuffer +// reads from a buffer a segment to an int value +// Input: Buffer containig the first frame +// Output: none +// Return: number containing int value of buffer segmenet +// length +// +//---------------------------------------------------------------------------/ + +int CVbriHeader::readFromBuffer ( unsigned char * HBuffer, int length ){ + + if (HBuffer) + { + int number = 0; + for(int i = 0; i < length ; i++ ) + { + int b = length-1-i ; + number = number | (unsigned int)( HBuffer[position+i] & 0xff ) << ( 8*b ); + } + position += length ; + return number; + } + else{ + return 0; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/CVbriHeader.h b/Src/Plugins/Input/in_mp3/CVbriHeader.h new file mode 100644 index 00000000..df9aaa70 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/CVbriHeader.h @@ -0,0 +1,49 @@ +#ifndef _VBRIHEADER_H_ +#define _VBRIHEADER_H_ + +class CVbriHeader{ + +public: + + CVbriHeader(); + ~CVbriHeader(); + + int readVbriHeader(unsigned char *Hbuffer); + + int seekPointByTime(float EntryTimeInSeconds); +#if 0 + float seekTimeByPoint(unsigned int EntryPointInBytes); + int seekPointByPercent(float percent); +#endif + + int getNumFrames() { return VbriStreamFrames; } + int getNumMS(); + int getEncoderDelay() { return encoderDelay; } + int getBytes() { return VbriStreamBytes; } +int h_id; +private: + + int getSampleRate(unsigned char * buffer); + int readFromBuffer ( unsigned char * HBuffer, int length ); + + int SampleRate; + unsigned int VbriStreamBytes; + unsigned int VbriStreamFrames; + unsigned int VbriTableSize; + unsigned int VbriEntryFrames; + int * VbriTable; + int encoderDelay; + + int position ; + + enum offset{ + + BYTE = 1, + WORD = 2, + DWORD = 4 + + }; + +}; + +#endif//_VBRIHEADER_H_
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/CreateFile.cpp b/Src/Plugins/Input/in_mp3/CreateFile.cpp new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/Src/Plugins/Input/in_mp3/CreateFile.cpp diff --git a/Src/Plugins/Input/in_mp3/CreateFile.h b/Src/Plugins/Input/in_mp3/CreateFile.h new file mode 100644 index 00000000..25be7078 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/CreateFile.h @@ -0,0 +1,6 @@ +#ifndef NULLSOFT_CREATEFILEH +#define NULLSOFT_CREATEFILEH + + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/DXHEAD.C b/Src/Plugins/Input/in_mp3/DXHEAD.C new file mode 100644 index 00000000..f739240a --- /dev/null +++ b/Src/Plugins/Input/in_mp3/DXHEAD.C @@ -0,0 +1,54 @@ + +/*---- DXhead.c -------------------------------------------- + + +decoder MPEG Layer III + +handle Xing header + +mod 12/7/98 add vbr scale + +Copyright 1998 Xing Technology Corp. +-----------------------------------------------------------*/ +#include <windows.h> +#include <stdlib.h> +#include <stdio.h> +#include <float.h> +#include <math.h> +#include "dxhead.h" + + +/*-------------------------------------------------------------*/ +int SeekPoint(unsigned char TOC[100], int file_bytes, float percent) +{ + // interpolate in TOC to get file seek point in bytes + int a, seekpoint; + float fa, fb, fx; + + + if (percent < 0.0f) + percent = 0.0f; + if (percent > 100.0f) + percent = 100.0f; + + a = (int)percent; + if (a > 99) a = 99; + fa = TOC[a]; + if (a < 99) + { + fb = TOC[a + 1]; + } + else + { + fb = 256.0f; + } + + + fx = fa + (fb - fa) * (percent - a); + + seekpoint = (int) ((1.0f / 256.0f) * fx * file_bytes); + + + return seekpoint; +} +/*-------------------------------------------------------------*/ diff --git a/Src/Plugins/Input/in_mp3/DXHEAD.H b/Src/Plugins/Input/in_mp3/DXHEAD.H new file mode 100644 index 00000000..5b59e043 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/DXHEAD.H @@ -0,0 +1,43 @@ +/*---- DXhead.h -------------------------------------------- + + +decoder MPEG Layer III + +handle Xing header + + +Copyright 1998 Xing Technology Corp. +-----------------------------------------------------------*/ +// A Xing header may be present in the ancillary +// data field of the first frame of an mp3 bitstream +// The Xing header (optionally) contains +// frames total number of audio frames in the bitstream +// bytes total number of bytes in the bitstream +// toc table of contents + +// toc (table of contents) gives seek points +// for random access +// the ith entry determines the seek point for +// i-percent duration +// seek point in bytes = (toc[i]/256.0) * total_bitstream_bytes +// e.g. half duration seek point = (toc[50]/256.0) * total_bitstream_bytes + + +#define FRAMES_FLAG 0x0001 +#define BYTES_FLAG 0x0002 +#define TOC_FLAG 0x0004 +#define VBR_SCALE_FLAG 0x0008 + +#define FRAMES_AND_BYTES (FRAMES_FLAG | BYTES_FLAG) + + +int SeekPoint(unsigned char TOC[100], int file_bytes, float percent); +// return seekpoint in bytes (may be at eof if percent=100.0) +// TOC = table of contents from Xing header +// file_bytes = number of bytes in mp3 file +// percent = play time percentage of total playtime. May be +// fractional (e.g. 87.245) + + + + diff --git a/Src/Plugins/Input/in_mp3/DecodeThread.cpp b/Src/Plugins/Input/in_mp3/DecodeThread.cpp new file mode 100644 index 00000000..99654069 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/DecodeThread.cpp @@ -0,0 +1,810 @@ +#include "DecodeThread.h" +#include "giofile.h" +#include "main.h" +#include "pdtimer.h" +#include "mpegutil.h" +#include "../Winamp/wa_ipc.h" +#include "config.h" +#include <shlwapi.h> +#include "adts.h" +#include "adts_vlb.h" +#include <foundation/error.h> + +// {19450308-90D7-4E45-8A9D-DC71E67123E2} +static const GUID adts_aac_guid = +{ 0x19450308, 0x90d7, 0x4e45, { 0x8a, 0x9d, 0xdc, 0x71, 0xe6, 0x71, 0x23, 0xe2 } }; + +// {4192FE3F-E843-445c-8D62-51BE5EE5E68C} +static const GUID adts_mp2_guid = +{ 0x4192fe3f, 0xe843, 0x445c, { 0x8d, 0x62, 0x51, 0xbe, 0x5e, 0xe5, 0xe6, 0x8c } }; + +extern int m_is_stream; +extern bool m_is_stream_seekable; + +// post this to the main window at end of file (after playback as stopped) +#define WM_WA_MPEG_EOF WM_USER+2 + +/* public data */ +int last_decode_pos_ms; +int decode_pos_ms; // current decoding position, in milliseconds. +volatile int seek_needed; // if != -1, it is the point that the decode +// thread should seek to, in ms. +int g_ds; + +size_t g_bits; +int g_sndopened; +int g_bufferstat; +int g_length = -1000; +int g_vis_enabled; +volatile int g_closeaudio = 0; + +CGioFile *g_playing_file=0; +/* private data */ +static size_t g_samplebuf_used; +static int need_prebuffer; +static int g_srate, g_nch, g_br_add, g_br_div, g_avg_vbr_br; +int g_br; + +class EndCutter +{ +public: + EndCutter() : buffer(0), cutSize(0), filledSize(0), preCutSize(0), preCut(0), decoderDelay(0) + {} + ~EndCutter() + { + free(buffer); + } + void SetEndSize(int postSize) + { + postSize -= decoderDelay; + if (postSize < 0) + postSize = 0; + else if (postSize) + { + free(buffer); + buffer = (char *)calloc(postSize, sizeof(char)); + cutSize = postSize; + } + } + + void SetSize(int decoderDelaySize, int preSize, int postSize) + { + decoderDelay = decoderDelaySize; + SetEndSize(postSize); + + preCutSize = preSize; + preCut = preCutSize + decoderDelay; + } + + void Flush(int time_in_ms) + { + if (time_in_ms == 0) // TODO: calculate actual delay if we seek within the encoder delay area + preCut = preCutSize; // reset precut size if we seek to the start + + filledSize = 0; + mod.outMod->Flush(time_in_ms); + } + + void Write(char *out, int outSize) + { + if (!out && (!outSize)) + { + mod.outMod->Write(0, 0); + return ; + } + + // cut pre samples, if necessary + int pre = min(preCut, outSize); + out += pre; + outSize -= pre; + preCut -= pre; + + if (!outSize) + return ; + + int remainingFill = cutSize - filledSize; + int fillWrite = min(outSize - remainingFill, filledSize); // only write fill buffer if we've got enough left to fill it up + + if (fillWrite > 0) + { + mod.outMod->Write((char *)buffer, fillWrite); + if (cutSize - fillWrite) + memmove(buffer, buffer + fillWrite, cutSize - fillWrite); + filledSize -= fillWrite; + + } + remainingFill = cutSize - filledSize; + int outWrite = max(0, outSize - remainingFill); + if (outWrite) + mod.outMod->Write((char *)out, outWrite); + out += outWrite; + outSize -= outWrite; + + if (outSize) + { + memcpy(buffer + filledSize, out, outSize); + filledSize += outSize; + } + + + } + char *buffer; + int cutSize; + int filledSize; + int preCut, preCutSize, decoderDelay; +}; + +class DecodeLoop +{ +public: + DecodeLoop() : decoder(0) + { + isAac = 0; + isEAAC = 0; + + last_bpos = -1; + need_synclight = true; + done = 0; + br = 0; + + g_framesize = 0; + maxlatency = 0; + sampleFrameSize = 0; + memset(&g_samplebuf, 0, sizeof(g_samplebuf)); + } + + ~DecodeLoop() + { + if (decoder) + { + decoder->Close(); + decoder->Release(); + } + decoder=0; + + } + + DWORD Loop(); + DWORD OpenDecoder(); + void Seek(int seekPosition); + void PreBuffer(); + void Decode(); + void Viz(); + void CalculateCodecDelay(); + DWORD OpenOutput(int numChannels, int sampleRate, int bitsPerSample); + void SetupStream(); + + BYTE g_samplebuf[6*3*2*2*1152]; + + int g_framesize; + int isAac; + int isEAAC; + + CGioFile file; + + int maxlatency; + int last_bpos; + bool need_synclight; + int done; // set to TRUE if decoding has finished, 2 if all has been written + size_t br; + + EndCutter endCutter; + int sampleFrameSize; + adts *decoder; +}; + +static int CalcPreBuffer(int buffer_setting, int bitrate) +{ + if (bitrate < 8) + bitrate = 8; + else if (bitrate > 320) + bitrate = 320; + int prebuffer = (buffer_setting * bitrate) / 128; + if (prebuffer > 100) + prebuffer=100; + return prebuffer; +} + +void DecodeLoop::SetupStream() +{ + char buf[1024] = {0}; + int len; + + m_is_stream = file.IsStream(); + + //Wait until we have data... + while (!killDecodeThread && file.Peek(buf, 1024, &len) == NErr_Success && !len) + Sleep(50); + + m_is_stream_seekable = file.IsStreamSeekable(); + char *content_type = file.m_content_type; + if (content_type) + { + if (!_strnicmp(content_type, "misc/ultravox", 13)) + { + switch (file.uvox_last_message) + { + case 0x8001: + case 0x8003: + isEAAC = 1; + isAac = 1; + break; + + case 0x8000: + isAac = 1; + break; + } + } + else if (!_strnicmp(content_type, "audio/aac", 9)) + { + isEAAC = 1; + isAac = 1; + } + else if (!_strnicmp(content_type, "audio/aacp", 10)) + { + isEAAC = 1; + isAac = 1; + } + else if (!_strnicmp(content_type, "audio/apl", 10)) + { + isEAAC = 1; + isAac = 1; + } + } + + // todo: poll until connected to see if we get aac uvox frames or a content-type:aac header +} + +DWORD DecodeLoop::OpenOutput(int numChannels, int sampleRate, int bitsPerSample) +{ + maxlatency = mod.outMod->Open(sampleRate, numChannels, bitsPerSample, -1, -1); + + // maxlatency is the maxium latency between a outMod->Write() call and + // when you hear those samples. In ms. Used primarily by the visualization + // system. + + if (maxlatency < 0) // error opening device + { + PostMessage(mod.hMainWindow, WM_COMMAND, 40047, 0); + return 0; + } + g_sndopened = 1; + if (maxlatency == 0 && file.IsStream() == 2) // can't use with disk writer + { + if (!killDecodeThread) + { + EnterCriticalSection(&g_lfnscs); + WASABI_API_LNGSTRING_BUF(IDS_CANNOT_WRITE_STREAMS_TO_DISK,lastfn_status,256); + LeaveCriticalSection(&g_lfnscs); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + } + if (!killDecodeThread) Sleep(200); + if (!killDecodeThread) PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + g_bufferstat = 0; + g_closeaudio = 1; + + return 0; + } + + if (paused) mod.outMod->Pause(1); + + // set the output plug-ins default volume. + // volume is 0-255, -666 is a token for + // current volume. + + mod.outMod->SetVolume(-666); + return 1; +} + +void DecodeLoop::CalculateCodecDelay() +{ + int decoderDelaySamples = (int)decoder->GetDecoderDelay(); + + endCutter.SetSize(decoderDelaySamples*sampleFrameSize, + file.prepad*sampleFrameSize, + file.postpad*sampleFrameSize); +} + +void DecodeLoop::Viz() +{ + if (!config_fastvis || (decoder->GetLayer() != 3 || g_ds)) + { + int vis_waveNch; + int vis_specNch; + int csa = mod.SAGetMode(); + int is_vis_running = mod.VSAGetMode(&vis_specNch, &vis_waveNch); + if (csa || is_vis_running) + { + int l = 576 * sampleFrameSize; + int ti = decode_pos_ms; + { + if (g_ds == 2) + { + memcpy(g_samplebuf + g_samplebuf_used, g_samplebuf, g_samplebuf_used); + } + size_t pos = 0; + while (pos < g_samplebuf_used) + { + int a, b; + if (mod.SAGetMode()) mod.SAAddPCMData((char *)g_samplebuf + pos, g_nch, (int)g_bits, ti); + if (mod.VSAGetMode(&a, &b)) mod.VSAAddPCMData((char *)g_samplebuf + pos, g_nch, (int)g_bits, ti); + ti += ((l / sampleFrameSize * 1000) / g_srate); + pos += l >> g_ds; + } + } + } + } + else + { + int l = (576 * (int)g_bits * g_nch); + int ti = decode_pos_ms; + size_t pos = 0; + int x = 0; + while (pos < g_samplebuf_used) + { + do_layer3_vis((short*)(g_samplebuf + pos), &g_vis_table[x++][0][0][0], g_nch, ti); + ti += (l / g_nch / 2 * 1000) / g_srate; + pos += l; + } + } +} + +void DecodeLoop::Decode() +{ + while (g_samplebuf_used < (size_t)g_framesize && !killDecodeThread && seek_needed == -1) + { + size_t newl = 0; + size_t br=0; + size_t endCut=0; + int res = decoder->Decode(&file, g_samplebuf + g_samplebuf_used, sizeof(g_samplebuf) / 2 - g_samplebuf_used, &newl, &br, &endCut); + + if (config_gapless && endCut) + endCutter.SetEndSize((int)endCut* sampleFrameSize); + + // we're not using switch here because we sometimes need to break out of the while loop + if (res == adts::SUCCESS) + { + if (!file.m_vbr_frames) + { + if (br) { + bool do_real_br=false; + if (!(config_miscopts&2) && br != decoder->GetCurrentBitrate()) + { + do_real_br=true; + } + + int r = (int)br; + g_br_add += r; + g_br_div++; + r = (g_br_add + g_br_div / 2) / g_br_div; + if (g_br != r) + { + need_synclight = false; + g_br = r; + if (!file.m_vbr_frames && file.IsSeekable()) g_length = MulDiv(file.GetContentLength(), 8, g_br); + if (!do_real_br) + mod.SetInfo(g_br, -1, -1, 1); + } + if (do_real_br) + mod.SetInfo((int)br, -1, -1, 1); + } + } + else + { + if (br) { + int r; + if (!(config_miscopts&2) || !g_avg_vbr_br) + r = (int)br; + else r = g_avg_vbr_br; + if (g_br != r) + { + need_synclight = false; + g_br = r; + mod.SetInfo(g_br, -1, -1, 1); + } + } + } + if (need_synclight) + { + need_synclight = false; + mod.SetInfo(-1, -1, -1, 1); + } + g_samplebuf_used += newl; + } + else if (res == adts::ENDOFFILE) + { + done = 1; + break; + } + else if (res == adts::NEEDMOREDATA) + { + if (file.IsStream() && !need_synclight) + { + need_synclight = true; mod.SetInfo(-1, -1, -1, 0); + } + if (file.IsStream() && !mod.outMod->IsPlaying()) + { + need_prebuffer = CalcPreBuffer(config_http_prebuffer_underrun, (int)br); + } + break; + } + else + { + if (!need_synclight) mod.SetInfo(-1, -1, -1, 0); + need_synclight = true; + break; + } + } +} + +void DecodeLoop::PreBuffer() +{ + int p = file.RunStream(); + int pa = file.PercentAvailable(); + if (pa >= need_prebuffer || p == 2) + { + EnterCriticalSection(&g_lfnscs); + lastfn_status[0] = 0; + LeaveCriticalSection(&g_lfnscs); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + need_prebuffer = 0; + g_bufferstat = 0; + last_bpos = -1; + } + else + { + int bpos = pa * 100 / need_prebuffer; + if (!g_bufferstat) g_bufferstat = decode_pos_ms; + if (bpos != last_bpos) + { + last_bpos = bpos; + EnterCriticalSection(&g_lfnscs); + if (stricmp(lastfn_status, "stream temporarily interrupted")) + { + char langbuf[512] = {0}; + wsprintfA(lastfn_status, WASABI_API_LNGSTRING_BUF(IDS_BUFFER_X,langbuf,512), bpos); + } + LeaveCriticalSection(&g_lfnscs); + + int csa = mod.SAGetMode(); + char tempdata[75*2] = {0, }; + int x; + if (csa&1) + { + for (x = 0; x < bpos*75 / 100; x ++) + { + tempdata[x] = x * 16 / 75; + } + } + if (csa&2) + { + int offs = (csa & 1) ? 75 : 0; + x = 0; + while (x < bpos*75 / 100) + { + tempdata[offs + x++] = -6 + x * 14 / 75; + } + while (x < 75) + { + tempdata[offs + x++] = 0; + } + } + if (csa == 4) + { + tempdata[0] = tempdata[1] = (bpos * 127 / 100); + } + + if (csa) mod.SAAdd(tempdata, ++g_bufferstat, (csa == 3) ? 0x80000003 : csa); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + } + } +} + +void DecodeLoop::Seek(int seekPosition) +{ + if (done == 3) + return; + done=0; + int br = (int)decoder->GetCurrentBitrate(); + + need_prebuffer = CalcPreBuffer(config_http_prebuffer_underrun, br); + if (need_prebuffer < 1) need_prebuffer = 5; + + last_decode_pos_ms = decode_pos_ms = seekPosition; + + seek_needed = -1; + endCutter.Flush(decode_pos_ms); + decoder->Flush(&file); + done = 0; + g_samplebuf_used = 0; + + int r = g_br; + if (g_br_div) r = (g_br_add + g_br_div / 2) / g_br_div; + file.Seek(decode_pos_ms, r); + // need_prebuffer=config_http_prebuffer/8; + // g_br_add=g_br_div=0; + +} + +DWORD DecodeLoop::OpenDecoder() +{ + mod.UsesOutputPlug &= ~8; + if (isAac) + { + if (isEAAC) + { + waServiceFactory *factory = mod.service->service_getServiceByGuid(adts_aac_guid); + if (factory) + decoder = (adts *)factory->getInterface(); + + mod.UsesOutputPlug|=8; + } + if (!decoder) + { + decoder = new ADTS_VLB; + mod.UsesOutputPlug &= ~8; + } + } + else + { + waServiceFactory *factory = mod.service->service_getServiceByGuid(adts_mp2_guid); + if (factory) + decoder = (adts *)factory->getInterface(); + + mod.UsesOutputPlug|=8; + } + + if (decoder) { + decoder->SetDecoderHooks(mp3GiveVisData, mp2Equalize, mp3Equalize); + } + + if (decoder + && decoder->Initialize(AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"mono", false), + config_downmix == 2, + AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"surround", true), + (int)AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"bits", 16), true, false, + (config_miscopts&1)/*crc*/) == adts::SUCCESS + && decoder->Open(&file)) + { + // sync to stream + while (1) + { + switch (decoder->Sync(&file, g_samplebuf, sizeof(g_samplebuf), &g_samplebuf_used, &br)) + { + case adts::SUCCESS: + return 1; + case adts::FAILURE: + case adts::ENDOFFILE: + if (!killDecodeThread) + { + if (!lastfn_status_err) + { + EnterCriticalSection(&g_lfnscs); + WASABI_API_LNGSTRING_BUF(IDS_ERROR_SYNCING_TO_STREAM,lastfn_status,256); + LeaveCriticalSection(&g_lfnscs); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + } + } + if (!killDecodeThread) Sleep(200); + if (!killDecodeThread) PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return 0; + case adts::NEEDMOREDATA: + if (!killDecodeThread && file.IsStream()) Sleep(25); + if (killDecodeThread) return 0; + } + } + } + + return 0; +} + +DWORD DecodeLoop::Loop() +{ + last_decode_pos_ms = 0; + + if (file.Open(lastfn, config_max_bufsize_k) != NErr_Success) + { + if (!killDecodeThread) Sleep(200); + if (!killDecodeThread) PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return 0; + } + + if (file.IsSeekable()) mod.is_seekable = 1; + + wchar_t *ext = PathFindExtension(lastfn); + if (!_wcsicmp(ext, L".aac") + || !_wcsicmp(ext, L".vlb") + || !_wcsicmp(ext, L".apl")) + { + if (file.IsStream()) + SetupStream(); + else + { + isAac = 1; + if (!_wcsicmp(ext, L".aac") || !_wcsicmp(ext, L".apl")) isEAAC = 1; + } + } + else if (file.IsStream()) + SetupStream(); + + if (OpenDecoder() == 0) + return 0; + + EnterCriticalSection(&streamInfoLock); + g_playing_file = &file; + if (file.uvox_3901) + { + PostMessage(mod.hMainWindow, WM_WA_IPC, (WPARAM) "0x3901", IPC_METADATA_CHANGED); + PostMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE); + } + LeaveCriticalSection(&streamInfoLock); + + + EnterCriticalSection(&g_lfnscs); + lastfn_status[0] = 0; + LeaveCriticalSection(&g_lfnscs); + + lastfn_data_ready = 1; + +// TODO? if (decoder != &aacp) // hack because aac+ bitrate isn't accurate at this point + br = decoder->GetCurrentBitrate(); + + need_prebuffer = CalcPreBuffer(config_http_prebuffer, (int)br); + + if (((!(config_eqmode&4) && decoder->GetLayer() == 3) || + ((config_eqmode&8) && decoder->GetLayer() < 3))) + { + mod.UsesOutputPlug |= 2; + } + else + mod.UsesOutputPlug &= ~2; + + decoder->CalculateFrameSize(&g_framesize); + decoder->GetOutputParameters(&g_bits, &g_nch, &g_srate); + + if (!killDecodeThread && file.IsStream() == 1) + { + DWORD_PTR dw; + if (!killDecodeThread) SendMessageTimeout(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE, SMTO_BLOCK, 100, &dw); + if (!killDecodeThread) SendMessageTimeout(mod.hMainWindow, WM_TIMER, 38, 0, SMTO_BLOCK, 100, &dw); + } + + sampleFrameSize = g_nch * ((int)g_bits/8); + + if (config_gapless) + CalculateCodecDelay(); + + if (OpenOutput(g_nch, g_srate, (int)g_bits) == 0) + return 0; + + /* ----- send info to winamp and vis: bitrate, etc ----- */ + g_br = (int)decoder->GetCurrentBitrate(); + + g_br_add = g_br; + g_br_div = 1; + g_avg_vbr_br = file.GetAvgVBRBitrate(); + mod.SetInfo(g_br, g_srate / 1000, g_nch, 0); + + // initialize visualization stuff + mod.SAVSAInit((maxlatency << g_ds), g_srate); + mod.VSASetInfo(g_srate, g_nch); + /* ----- end send info to winamp and vis ----- */ + + if (file.IsSeekable() && g_br) + { + mod.is_seekable = 1; + if (!file.m_vbr_frames) g_length = MulDiv(file.GetContentLength(), 8, g_br); + else g_length = file.m_vbr_ms; + } + + if (file.IsStream()) + { + if (need_prebuffer < config_http_prebuffer / 2) + need_prebuffer = config_http_prebuffer / 2; + } + + while (!killDecodeThread) + { + if (seek_needed != -1) + Seek(seek_needed); + + if (need_prebuffer && file.IsStream() && maxlatency && !file.EndOf()) + PreBuffer(); + + int needsleep = 1; + + if (done == 2) // done was set to TRUE during decoding, signaling eof + { + mod.outMod->CanWrite(); // some output drivers need CanWrite + // to be called on a regular basis. + + if (!mod.outMod->IsPlaying()) + { + // we're done playing, so tell Winamp and quit the thread. + if (!killDecodeThread) PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + done=3; + break; + } + } + else + { + int fs = (g_framesize * ((mod.dsp_isactive() == 1) ? 2 : 1)); + // TODO: we should really support partial writes, there's no gaurantee that CanWrite() will EVER get big enough + if (mod.outMod->CanWrite() >= fs && (!need_prebuffer || !file.IsStream() || !maxlatency)) + // CanWrite() returns the number of bytes you can write, so we check that + // to the block size. the reason we multiply the block size by two if + // mod.dsp_isactive() is that DSP plug-ins can change it by up to a + // factor of two (for tempo adjustment). + { + int p = mod.SAGetMode(); + g_vis_enabled = ((p & 1) || p == 4); + if (!g_vis_enabled) + { + int s, a; + mod.VSAGetMode(&s, &a); + if (s) g_vis_enabled = 1; + } + + Decode(); + + if ((g_samplebuf_used >= (size_t)g_framesize || (done && g_samplebuf_used > 0)) && seek_needed == -1) + { + // adjust decode position variable + if (file.isSeekReset()) + last_decode_pos_ms = decode_pos_ms = 0; + else + decode_pos_ms += ((int)g_samplebuf_used / sampleFrameSize * 1000) / g_srate; + + // if we have a DSP plug-in, then call it on our samples + if (mod.dsp_isactive()) + { + g_samplebuf_used = mod.dsp_dosamples((short *)g_samplebuf, (int)g_samplebuf_used / sampleFrameSize, (int)g_bits, g_nch, g_srate) * sampleFrameSize; + } + Viz(); + endCutter.Write((char *)g_samplebuf, (int)g_samplebuf_used); + g_samplebuf_used = 0; + needsleep = 0; + //memcpy(g_samplebuf,g_samplebuf+r,g_samplebuf_used); + } + if (done) + { + endCutter.Write(0, 0); + done = 2; + } + } + } + if (decode_pos_ms > last_decode_pos_ms + 1000) + { + last_decode_pos_ms = decode_pos_ms; + } + + if (needsleep) Sleep(10); + // if we can't write data, wait a little bit. Otherwise, continue + // through the loop writing more data (without sleeping) + } + + /* ---- change some globals to let everyone know we're done */ + EnterCriticalSection(&g_lfnscs); + lastfn_status[0] = 0; + LeaveCriticalSection(&g_lfnscs); + g_bufferstat = 0; + g_closeaudio = 1; + /* ---- */ + + return 0; +} + +DWORD WINAPI DecodeThread(LPVOID b) +{ + DecodeLoop loop; + + + + DWORD ret = loop.Loop(); + + EnterCriticalSection(&streamInfoLock); + g_playing_file = 0; + LeaveCriticalSection(&streamInfoLock); + return ret; +} + diff --git a/Src/Plugins/Input/in_mp3/DecodeThread.h b/Src/Plugins/Input/in_mp3/DecodeThread.h new file mode 100644 index 00000000..b3fc6735 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/DecodeThread.h @@ -0,0 +1,19 @@ +#ifndef NULLSOFT_DECODETHREADH +#define NULLSOFT_DECODETHREADH + +#include <windows.h> + +DWORD WINAPI DecodeThread(LPVOID b); + +extern volatile int seek_needed; +extern CRITICAL_SECTION g_lfnscs; +extern int g_ds; +extern int g_sndopened; +extern int g_bufferstat; +extern int g_length; +extern volatile int g_closeaudio; +extern int decode_pos_ms; // current decoding position, in milliseconds. +extern int g_vis_enabled; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/ExtendedInfo.cpp b/Src/Plugins/Input/in_mp3/ExtendedInfo.cpp new file mode 100644 index 00000000..a3affdeb --- /dev/null +++ b/Src/Plugins/Input/in_mp3/ExtendedInfo.cpp @@ -0,0 +1,315 @@ +#include "main.h" +#include "Metadata.h" +#include "../Winamp/wa_ipc.h" +#include "../nu/ns_wc.h" +#include "uvox_3901.h" +#include "uvox_3902.h" +#include "Stopper.h" +#include <shlwapi.h> +#include "../Agave/Language/api_language.h" +#include <strsafe.h> + +extern CGioFile *g_playing_file; +static FILETIME ftLastWriteTime; + +// is used to determine if the last write time of the file has changed when +// asked to get the metadata for the same cached file so we can update things +BOOL HasFileTimeChanged(const wchar_t *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData = {0}; + if (GetFileAttributesExW(fn, GetFileExInfoStandard, &fileData) == TRUE) + { + if(CompareFileTime(&ftLastWriteTime, &fileData.ftLastWriteTime)) + { + ftLastWriteTime = fileData.ftLastWriteTime; + return TRUE; + } + } + return FALSE; +} + +void UpdateFileTimeChanged(const wchar_t *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData; + if (GetFileAttributesExW(fn, GetFileExInfoStandard, &fileData) == TRUE) + { + ftLastWriteTime = fileData.ftLastWriteTime; + } +} + +Metadata *m_ext_set_mp3info = NULL; +Metadata *m_ext_get_mp3info = NULL; +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] = '0'; + dest[1] = 0; + return 1; + } + if (!_stricmp(data, "rateable")) + { + dest[0] = '1'; + dest[1] = 0; + return 1; + } + else if (!_stricmp(data, "family")) + { + if (!fn || !fn[0]) return 0; + int len = lstrlenW(fn); + if (len < 4 || L'.' != fn[len - 4]) return 0; + const wchar_t *p = &fn[len - 3]; + if (!_wcsicmp(p, L"MP3")) { WASABI_API_LNGSTRINGW_BUF(IDS_FAMILY_STRING_MP3, dest, destlen); return 1; } + if (!_wcsicmp(p, L"MP2")) { WASABI_API_LNGSTRINGW_BUF(IDS_FAMILY_STRING_MP2, dest, destlen); return 1; } + if (!_wcsicmp(p, L"MP1")) { WASABI_API_LNGSTRINGW_BUF(IDS_FAMILY_STRING_MP1, dest, destlen); return 1; } + if (!_wcsicmp(p, L"AAC")) { WASABI_API_LNGSTRINGW_BUF(IDS_FAMILY_STRING_MPEG2_AAC, dest, destlen); return 1; } + if (!_wcsicmp(p, L"VLB")) { WASABI_API_LNGSTRINGW_BUF(IDS_FAMILY_STRING_DOLBY, dest, destlen); return 1; } + return 0; + } + else if (!_stricmp(data, "mime")) + { + if (!fn || !fn[0]) return 0; + int len = lstrlenW(fn); + if (len < 4 || L'.' != fn[len - 4]) return 0; + const wchar_t *p = &fn[len - 3]; + if (!_wcsicmp(p, L"MP3")) { StringCchCopyW(dest, destlen, L"audio/mpeg"); return 1; } + if (!_wcsicmp(p, L"MP2")) { StringCchCopyW(dest, destlen, L"audio/mpeg"); return 1; } + if (!_wcsicmp(p, L"MP1")) { StringCchCopyW(dest, destlen, L"audio/mpeg"); return 1; } + if (!_wcsicmp(p, L"AAC")) { StringCchCopyW(dest, destlen, L"audio/aac"); return 1; } + if (!_wcsicmp(p, L"VLB")) { StringCchCopyW(dest, destlen, L"audio/vlb"); return 1; } + return 0; + } + else if (!_strnicmp(data, "uvox/", 5)) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file) + { + if (g_playing_file->uvox_3901) // check again now that we've acquired the lock + { + Ultravox3901 uvox_metadata; + if (uvox_metadata.Parse(g_playing_file->uvox_3901) != API_XML_FAILURE) + { + LeaveCriticalSection(&streamInfoLock); + return uvox_metadata.GetExtendedData(data, dest, (int)destlen); + } + } + else if (g_playing_file->uvox_3902) + { + Ultravox3902 uvox_metadata; + if (uvox_metadata.Parse(g_playing_file->uvox_3902) != API_XML_FAILURE) + { + LeaveCriticalSection(&streamInfoLock); + return uvox_metadata.GetExtendedData(data, dest, (int)destlen); + } + } + } + LeaveCriticalSection(&streamInfoLock); + return 0; + } + else if (_stricmp(data, "0x3901") == 0) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file && g_playing_file->uvox_3901) // check again now that we've acquired the lock + { + if (dest == NULL) // It's empty, he's looking for the size of the 0x3901 + { + int size = MultiByteToWideChar(CP_UTF8, 0, g_playing_file->uvox_3901, -1, 0, 0); + LeaveCriticalSection(&streamInfoLock); + return size; + } + else + { + MultiByteToWideCharSZ(CP_UTF8, 0, g_playing_file->uvox_3901, -1, dest, (int)destlen); + LeaveCriticalSection(&streamInfoLock); + return 1; + } + } + LeaveCriticalSection(&streamInfoLock); + return 0; + } + else if (!_stricmp(data, "streamtype")) + { + if (lstrcmpW(lastfn, fn)) + return 0; + + if (g_playing_file) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file) // check again now that we've acquired the lock + { + StringCchPrintfW(dest, destlen, L"%d", g_playing_file->IsStream()); + LeaveCriticalSection(&streamInfoLock); + return 1; + } + LeaveCriticalSection(&streamInfoLock); + } + + return 0; + } + else if (!_stricmp(data, "streammetadata")) + { + if (lstrcmpW(lastfn, fn)) + return 0; + + if (g_playing_file) + { + uint32_t len=0; + EnterCriticalSection(&streamInfoLock); + if (g_playing_file && g_playing_file->GetID3v2(&len) && len > 0) // check again now that we've acquired the lock + { + lstrcpynW(dest, L"1", (int)destlen); + LeaveCriticalSection(&streamInfoLock); + return 1; // always return 1 to ensure we can do title lookups + } + LeaveCriticalSection(&streamInfoLock); + } + return 0; + } + else if (!_stricmp(data, "streamtitle")) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file) // check again now that we've acquired the lock + ConvertTryUTF8(g_playing_file->stream_current_title, dest, destlen); + else + dest[0]=0; + LeaveCriticalSection(&streamInfoLock); + return 1; + } + else if (!_stricmp(data, "streamname")) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file) // check again now that we've acquired the lock + ConvertTryUTF8(g_playing_file->stream_name, dest, destlen); + else + dest[0]=0; + LeaveCriticalSection(&streamInfoLock); + return 1; + } + else if (!_stricmp(data, "streamurl")) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file) // check again now that we've acquired the lock + ConvertTryUTF8(g_playing_file->stream_url, dest, destlen); + else + dest[0]=0; + LeaveCriticalSection(&streamInfoLock); + return 1; + } + else if (!_stricmp(data, "streamgenre")) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file) // check again now that we've acquired the lock + ConvertTryUTF8(g_playing_file->stream_genre, dest, destlen); + else + dest[0]=0; + LeaveCriticalSection(&streamInfoLock); + return 1; + } + else if (!_stricmp(data, "streaminformation")) + { + EnterCriticalSection(&streamInfoLock); + if (g_playing_file) + g_playing_file->GetStreamInfo(dest, destlen); + else + dest[0]=0; + LeaveCriticalSection(&streamInfoLock); + return 1; + } + + if (!fn || !fn[0]) return 0; + + if (!_wcsnicmp(fn, L"uvox://", 7)) + return 0; + + if (g_playing_file && PathIsURL(fn) && !lstrcmpW(lastfn, fn)) + { + EnterCriticalSection(&streamInfoLock); + uint32_t len = 0; + if (g_playing_file && g_playing_file->GetID3v2(&len) && len > 0) // check again now that we've acquired the lock + { + Metadata meta(g_playing_file, fn); + int ret = meta.GetExtendedData(data, dest, (int)destlen); + LeaveCriticalSection(&streamInfoLock); + return ret; + } + LeaveCriticalSection(&streamInfoLock); + } + + if (PathIsURL(fn)) + return 0; + + if (m_ext_get_mp3info && (!m_ext_get_mp3info->IsMe(fn) || HasFileTimeChanged(fn))) + { + m_ext_get_mp3info->Release(); + m_ext_get_mp3info=0; + } + + if (!m_ext_get_mp3info) + { + m_ext_get_mp3info = new Metadata; + m_ext_get_mp3info->Open(fn); + } + + return m_ext_get_mp3info->GetExtendedData(data, dest, (int)destlen); +} + +extern "C" + __declspec(dllexport) int winampSetExtendedFileInfoW(const wchar_t *fn, const char *data, const wchar_t *val) +{ + if (!m_ext_set_mp3info || (m_ext_set_mp3info && !m_ext_set_mp3info->IsMe(fn))) + { + if(m_ext_set_mp3info) m_ext_set_mp3info->Release(); + m_ext_set_mp3info = new Metadata; + m_ext_set_mp3info->Open(fn); + } + return m_ext_set_mp3info->SetExtendedData(data, val); +} + +extern "C" + __declspec(dllexport) int winampWriteExtendedFileInfo() +{ + // flush our read cache too :) + if(m_ext_get_mp3info) m_ext_get_mp3info->Release(); + m_ext_get_mp3info = NULL; + + if (!m_ext_set_mp3info) return 0; + + Stopper stopper; + if (m_ext_set_mp3info->IsMe(lastfn)) + stopper.Stop(); + + // just in-case something changed + if (!m_ext_set_mp3info) return 0; + + int ret = m_ext_set_mp3info->Save(); + stopper.Play(); + m_ext_set_mp3info->Release(); + m_ext_set_mp3info = NULL; + + // update last modified so we're not re-queried on our own updates + UpdateFileTimeChanged(lastfn); + + return !ret; +} + +extern "C" __declspec(dllexport) + int winampClearExtendedFileInfoW(const wchar_t *fn) +{ + Metadata meta; + if (meta.Open(fn)==METADATA_SUCCESS) + { + meta.id3v2.Clear(); + Stopper stopper; + if (meta.IsMe(lastfn)) + stopper.Stop(); + meta.Save(); + stopper.Play(); + + // update last modified so we're not re-queried on our own updates + UpdateFileTimeChanged(fn); + + return 1; + } + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/ExtendedRead.cpp b/Src/Plugins/Input/in_mp3/ExtendedRead.cpp new file mode 100644 index 00000000..7196aad9 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/ExtendedRead.cpp @@ -0,0 +1,323 @@ +#include "main.h" +#include "adts.h" +#include <memory.h> +#include <malloc.h> +#include <xutility> +#include <assert.h> +#include <shlwapi.h> +#include <foundation/error.h> +#include "../nu/RingBuffer.h" +#include <api/service/waservicefactory.h> + +// {19450308-90D7-4E45-8A9D-DC71E67123E2} +static const GUID adts_aac_guid = +{ 0x19450308, 0x90d7, 0x4e45, { 0x8a, 0x9d, 0xdc, 0x71, 0xe6, 0x71, 0x23, 0xe2 } }; + +// {4192FE3F-E843-445c-8D62-51BE5EE5E68C} +static const GUID adts_mp2_guid = +{ 0x4192fe3f, 0xe843, 0x445c, { 0x8d, 0x62, 0x51, 0xbe, 0x5e, 0xe5, 0xe6, 0x8c } }; + +class GapCutter +{ +public: + GapCutter() {} + + void SetEndSize( int postSize ); + void SetSize( int preSize, int postSize ); + void Flush( int time_in_ms ); + int Write( void *dest, void *input, size_t inputBytes ); + +private: + RingBuffer ringBuffer; + + int preCut = 0; + int preCutSize = 0; +}; + +void GapCutter::SetEndSize(int postSize) +{ + if (postSize < 0) + postSize = 0; + + if (postSize) + { + ringBuffer.Reset(); + ringBuffer.reserve(postSize); + } +} + +void GapCutter::SetSize( int preSize, int postSize ) +{ + if ( preSize < 0 ) + preSize = 0; + + if ( postSize < 0 ) + postSize = 0; + + SetEndSize( postSize ); + + preCutSize = preSize; + preCut = preSize; +} + +void GapCutter::Flush( int time_in_ms ) +{ + // if (time_in_ms == 0) // TODO: calculate actual delay if we seek within the encoder delay area + preCut = preCutSize; // reset precut size if we seek to the start + + ringBuffer.clear(); +} + +int GapCutter::Write( void *dest, void *input, size_t inputBytes ) // returns # of bytes written +{ + int bytesWritten = 0; + unsigned __int8 *in = (unsigned __int8 *)input; + unsigned __int8 *out = (unsigned __int8 *)dest; + // cut pre samples, if necessary + + intptr_t pre = min( preCut, (intptr_t)inputBytes ); + in += pre; + inputBytes -= pre; + preCut -= (int)pre; + + if ( !inputBytes ) + return bytesWritten; + + size_t remainingFill = ringBuffer.avail(); + intptr_t fillWrite = min( (intptr_t)( inputBytes - remainingFill ), (intptr_t)ringBuffer.size() ); // only write fill buffer if we've got enough left to fill it up + + if ( fillWrite > 0 ) + { + size_t written = ringBuffer.read( out, fillWrite ); + + bytesWritten += (int)written; + out += written; + } + + remainingFill = ringBuffer.avail(); + + int outWrite = (int)max( 0, (intptr_t)( inputBytes - remainingFill ) ); + if ( outWrite ) + memcpy( out, in, outWrite ); + + bytesWritten += outWrite; + in += outWrite; + inputBytes -= outWrite; + + if ( inputBytes ) + ringBuffer.write( in, inputBytes ); + + return bytesWritten; +} + + +struct ExtendedRead +{ + ExtendedRead() { memset(&data, 0, sizeof(data)); } + ~ExtendedRead() + { + file.Close(); + if ( decoder ) + { + decoder->Close(); + decoder->Release(); + } + } + + bool Open( const wchar_t *fn, int *size, int *bps, int *nch, int *srate, bool useFloat ); + + adts *decoder = NULL; + int bits = 0; + size_t initialData = 0; + int frameSize = 0; + + GapCutter cutter; + CGioFile file; + +#define DATA_SIZE (6*4*2*2*1152) + unsigned char data[DATA_SIZE]; +}; + +bool ExtendedRead::Open(const wchar_t *fn, int *size, int *bps, int *nch, int *srate, bool useFloat) +{ + if (file.Open(fn, config_max_bufsize_k) != NErr_Success) + return false; + + int downmix = 0; + bool allowsurround = 1; + if (*nch == 1) + { + downmix = 1; + allowsurround = 0; + } + else if (*nch == 2) + { + allowsurround = 0; + } + + if (useFloat) + bits=32; + else if (*bps == 24) + bits = 24; + else + { + bits = 16; + *bps = 16; + } + + wchar_t *ext = PathFindExtensionW(fn); + if (!_wcsicmp(ext, L".vlb")) + { + return false; + } + else if (!_wcsicmp(ext, L".aac") || !_wcsicmp(ext, L".apl")) + { + waServiceFactory *factory = mod.service->service_getServiceByGuid(adts_aac_guid); + if (factory) + decoder = (adts *)factory->getInterface(); + } + else + { + waServiceFactory *factory = mod.service->service_getServiceByGuid(adts_mp2_guid); + if (factory) + decoder = (adts *)factory->getInterface(); + } + + if (!decoder) + return false; + + decoder->Initialize(!!downmix, 0, allowsurround, bits, false, useFloat); + decoder->Open(&file); + size_t bitrate; + bool done=false; + while (!done) + { + switch (decoder->Sync(&file, data, sizeof(data), &initialData, &bitrate)) + { + case adts::SUCCESS: + done=true; + break; + case adts::FAILURE: + case adts::ENDOFFILE: + return false; + case adts::NEEDMOREDATA: + break; + } + } + + size_t numBits = 0; + decoder->GetOutputParameters(&numBits, nch, srate); + *bps = bits = (int)numBits; + frameSize = bits / 8 * *nch; + if (config_gapless) + cutter.SetSize((file.prepad + (int)decoder->GetDecoderDelay())*frameSize, (file.postpad - (int)decoder->GetDecoderDelay())*frameSize); + + if (file.m_vbr_samples) // exact number of samples in the LAME header, how nice :) + *size = (int)file.m_vbr_samples*frameSize; + else if (file.m_vbr_ms) // if we know the milliseconds accurately + *size = MulDiv(*srate * frameSize, file.m_vbr_ms, 1000); // our size should be mostly accurate + else // no helpful info to go on + { + // just guess based on bitrate and content length + bitrate=decoder->GetCurrentBitrate(); + int len_ms = MulDiv(file.GetContentLength(), 8, (int)bitrate); + *size = MulDiv(*srate * frameSize, len_ms, 1000); + } + + return true; +} + +extern "C" +{ + //returns handle!=0 if successful, 0 if error + //size will return the final nb of bytes written to the output, -1 if unknown + __declspec(dllexport) intptr_t winampGetExtendedRead_openW(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) + { + ExtendedRead *ext = new ExtendedRead; + if (ext) + { + if (ext->Open(fn, size, bps, nch, srate, false)) + return reinterpret_cast<intptr_t>(ext); + delete ext; + } + return 0; + } + + __declspec(dllexport) intptr_t winampGetExtendedRead_openW_float(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) + { + ExtendedRead *ext = new ExtendedRead; + if (ext) + { + if (ext->Open(fn, size, bps, nch, srate, true)) + return reinterpret_cast<intptr_t>(ext); + delete ext; + } + return 0; + } + + //returns nb of bytes read. -1 if read error (like CD ejected). if (ret==0), EOF is assumed + __declspec(dllexport) size_t winampGetExtendedRead_getData(intptr_t handle, char *dest, size_t len, int *killswitch) + { + ExtendedRead *ext = (ExtendedRead *)handle; + int copied = 0; + if (ext) + { + len -= (len % ext->frameSize); // only do whole frames + while (len) + { + size_t toMove = min(len, ext->initialData); + int toCopy = ext->cutter.Write(dest, ext->data, toMove); + + if (ext->initialData != toMove) + memmove(ext->data, ext->data + toMove, ext->initialData - toMove); + + ext->initialData -= toMove; + len -= toCopy; + copied += toCopy; + dest += toCopy; + + if (!ext->initialData) + { + size_t written = 0, bitrate, endCut = 0; + int ret = ext->decoder->Decode(&ext->file, ext->data, DATA_SIZE, &written, &bitrate, &endCut); + if (config_gapless && endCut) + ext->cutter.SetEndSize((int)(endCut - ext->decoder->GetDecoderDelay())*ext->frameSize); + ext->initialData = written; + if (/*ret != adts::SUCCESS && */!ext->initialData && (copied || ret == adts::ENDOFFILE)) + return copied; + + if (ret == adts::FAILURE) + return -1; + } + } + } + return copied; + } + + // return nonzero on success, zero on failure. + __declspec(dllexport) int winampGetExtendedRead_setTime(intptr_t handle, int millisecs) + { + ExtendedRead *ext = (ExtendedRead *)handle; + if (ext) + { + if (!ext->file.IsSeekable()) return 0; // not seekable + + int br = ext->file.GetAvgVBRBitrate(); + if (!br) br = (int)ext->decoder->GetCurrentBitrate(); + if (!br) return 0; // can't find a valid bitrate + + ext->cutter.Flush(millisecs); // fucko? + ext->decoder->Flush(&ext->file); + + ext->file.Seek(millisecs,br); + return 1; + } + return 0; + } + + __declspec(dllexport) void winampGetExtendedRead_close(intptr_t handle) + { + ExtendedRead *ext = (ExtendedRead *)handle; + if (ext) delete ext; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/FactoryHelper.h b/Src/Plugins/Input/in_mp3/FactoryHelper.h new file mode 100644 index 00000000..afbda372 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/FactoryHelper.h @@ -0,0 +1,25 @@ +#include "api__in_mp3.h" +#include <api/service/waservicefactory.h> + +template <class api_T> +void ServiceBuild(api_T *&api_t, GUID factoryGUID_t) +{ + if (mod.service) + { + waServiceFactory *factory = mod.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 (mod.service && api_t) + { + waServiceFactory *factory = mod.service->service_getServiceByGuid(factoryGUID_t); + if (factory) + factory->releaseInterface(api_t); + } + api_t = NULL; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/ID3Info.cpp b/Src/Plugins/Input/in_mp3/ID3Info.cpp new file mode 100644 index 00000000..99933dea --- /dev/null +++ b/Src/Plugins/Input/in_mp3/ID3Info.cpp @@ -0,0 +1,10 @@ +#include "main.h" +#include "MP3Info.h" +#include "../nu/AutoWide.h" +#include "id3.h" +#include "api.h" +#include <math.h> +#include "config.h" +#include "LAMEinfo.h" +#include <strsafe.h> + diff --git a/Src/Plugins/Input/in_mp3/ID3v1.cpp b/Src/Plugins/Input/in_mp3/ID3v1.cpp new file mode 100644 index 00000000..5fe25106 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/ID3v1.cpp @@ -0,0 +1,211 @@ +#include "ID3v1.h" +#include "../nu/ns_wc.h" +#include <windows.h> +#include "config.h" +#include <strsafe.h> + +const wchar_t *id3v1_genres[] = +{ + L"Blues", L"Classic Rock", L"Country", L"Dance", L"Disco", L"Funk", L"Grunge", + L"Hip-Hop", L"Jazz", L"Metal", L"New Age", L"Oldies", L"Other", L"Pop", L"R&B", + L"Rap", L"Reggae", L"Rock", L"Techno", L"Industrial", L"Alternative", L"Ska", + L"Death Metal", L"Pranks", L"Soundtrack", L"Euro-Techno", L"Ambient", L"Trip-Hop", + L"Vocal", L"Jazz+Funk", L"Fusion", L"Trance", L"Classical", L"Instrumental", + L"Acid", L"House", L"Game", L"Sound Clip", L"Gospel", L"Noise", L"Alt Rock", + L"Bass", L"Soul", L"Punk", L"Space", L"Meditative", L"Instrumental Pop", + L"Instrumental Rock", L"Ethnic", L"Gothic", L"Darkwave", L"Techno-Industrial", + L"Electronic", L"Pop-Folk", L"Eurodance", L"Dream", L"Southern Rock", L"Comedy", + L"Cult", L"Gangsta Rap", L"Top 40", L"Christian Rap", L"Pop/Funk", L"Jungle", + L"Native American", L"Cabaret", L"New Wave", L"Psychedelic", L"Rave", L"Showtunes", + L"Trailer", L"Lo-Fi", L"Tribal", L"Acid Punk", L"Acid Jazz", L"Polka", L"Retro", + L"Musical", L"Rock & Roll", L"Hard Rock", L"Folk", L"Folk-Rock", L"National Folk", + L"Swing", L"Fast-Fusion", L"Bebop", L"Latin", L"Revival", L"Celtic", L"Bluegrass", + L"Avantgarde", L"Gothic Rock", L"Progressive Rock", L"Psychedelic Rock", + L"Symphonic Rock", L"Slow Rock", L"Big Band", L"Chorus", L"Easy Listening", + L"Acoustic", L"Humour", L"Speech", L"Chanson", L"Opera", L"Chamber Music", L"Sonata", + L"Symphony", L"Booty Bass", L"Primus", L"Porn Groove", L"Satire", L"Slow Jam", + L"Club", L"Tango", L"Samba", L"Folklore", L"Ballad", L"Power Ballad", L"Rhythmic Soul", + L"Freestyle", L"Duet", L"Punk Rock", L"Drum Solo", L"A Cappella", L"Euro-House", + L"Dance Hall", L"Goa", L"Drum & Bass", L"Club-House", L"Hardcore", L"Terror", L"Indie", + L"BritPop", L"Afro-Punk", L"Polsk Punk", L"Beat", L"Christian Gangsta Rap", + L"Heavy Metal", L"Black Metal", L"Crossover", L"Contemporary Christian", + L"Christian Rock", L"Merengue", L"Salsa", L"Thrash Metal", L"Anime", L"JPop", + L"Synthpop", L"Abstract", L"Art Rock", L"Baroque", L"Bhangra", L"Big Beat", + L"Breakbeat", L"Chillout", L"Downtempo", L"Dub", L"EBM", L"Eclectic", L"Electro", + L"Electroclash", L"Emo", L"Experimental", L"Garage", L"Global", L"IDM", L"Illbient", + L"Industro-Goth", L"Jam Band", L"Krautrock", L"Leftfield", L"Lounge", L"Math Rock", + L"New Romantic", L"Nu-Breakz", L"Post-Punk", L"Post-Rock", L"Psytrance", L"Shoegaze", + L"Space Rock", L"Trop Rock", L"World Music", L"Neoclassical", L"Audiobook", + L"Audio Theatre", L"Neue Deutsche Welle", L"Podcast", L"Indie Rock", L"G-Funk", + L"Dubstep", L"Garage Rock", L"Psybient", L"Glam Rock", L"Dream Pop", L"Merseybeat", + L"K-Pop", L"Chiptune", L"Grime", L"Grindcore", L"Indietronic", L"Indietronica", + L"Jazz Rock", L"Jazz Fusion", L"Post-Punk Revival", L"Electronica", L"Psychill", + L"Ethnotronic", L"Americana", L"Ambient Dub", L"Digital Dub", L"Chillwave", L"Stoner Rock", + L"Slowcore", L"Softcore", L"Flamenco", L"Hi-NRG", L"Ethereal", L"Drone", L"Doom Metal", + L"Doom Jazz", L"Mainstream", L"Glitch", L"Balearic", L"Modern Classical", L"Mod", + L"Contemporary Classical", L"Psybreaks", L"Psystep", L"Psydub", L"Chillstep", L"Berlin School", + L"Future Jazz", L"Djent", L"Musique Concrète", L"Electroacoustic", L"Folktronica", L"Texas Country", L"Red Dirt", + L"Arabic", L"Asian", L"Bachata", L"Bollywood", L"Cajun", L"Calypso", L"Creole", L"Darkstep", L"Jewish", L"Reggaeton", L"Smooth Jazz", + L"Soca", L"Spiritual", L"Turntablism", L"Zouk", L"Neofolk", L"Nu Jazz", +}; + +size_t numGenres = sizeof(id3v1_genres)/sizeof(*id3v1_genres); + +ID3v1::ID3v1() +{ + title[0]=0; + artist[0]=0; + album[0]=0; + comment[0]=0; + year[0]=0; + genre=255; + track=0; + hasData=false; + dirty=false; + title[30]=0; + artist[30]=0; + album[30]=0; + comment[30]=0; + year[4]=0; +} + +int ID3v1::Decode(const void *data) +{ + const char *fbuf = (const char *)data; + ptrdiff_t x; + + hasData = false; + title[0] = artist[0] = album[0] = year[0] = comment[0] = 0; + genre = 255; track = 0; + + if (memcmp(fbuf, "TAG", 3)) + { + return 1; + } + memcpy(title, fbuf + 3, 30); x = 29; while (x >= 0 && title[x] == ' ') x--; title[x + 1] = 0; + memcpy(artist, fbuf + 33, 30); x = 29; while (x >= 0 && artist[x] == ' ') x--; artist[x + 1] = 0; + memcpy(album, fbuf + 63, 30); x = 29; while (x >= 0 && album[x] == ' ') x--; album[x + 1] = 0; + memcpy(year, fbuf + 93, 4); x = 3; while (x >= 0 && year[x] == ' ') x--; year[x + 1] = 0; + memcpy(comment, fbuf + 97, 30); x = 29; while (x >= 0 && comment[x] == ' ') x--; comment[x + 1] = 0; + if (fbuf[97 + 28] == 0 && fbuf[97 + 28 + 1] != 0) track = fbuf[97 + 28 + 1]; + genre = ((unsigned char *)fbuf)[127]; + hasData = 1; + return 0; +} + +int ID3v1::Encode(void *data) +{ + if (!hasData) + return 1; + char *fbuf = (char *)data; + size_t x; + fbuf[0] = 'T';fbuf[1] = 'A';fbuf[2] = 'G'; + if (title) strncpy(fbuf + 3, title, 30); for (x = 3 + strlen(title); x < 33; x ++) fbuf[x] = 0; + if (artist) strncpy(fbuf + 33, artist, 30); for (x = 33 + strlen(artist); x < 63; x ++) fbuf[x] = 0; + if (album) strncpy(fbuf + 63, album, 30); for (x = 63 + strlen(album); x < 93; x ++) fbuf[x] = 0; + if (year) strncpy(fbuf + 93, year, 4); for (x = 93 + strlen(year); x < 97; x ++) fbuf[x] = 0; + if (comment) strncpy(fbuf + 97, comment, 30); for (x = 97 + strlen(comment); x < 127; x ++) fbuf[x] = 0; + if (track) + { + fbuf[97 + 28] = 0; + fbuf[97 + 28 + 1] = track; + } + ((unsigned char *)fbuf)[127] = genre; + return 0; +} + +#define ID3V1_CODEPAGE ((config_read_mode==READ_LOCAL)?CP_ACP:28591) +int ID3v1::GetString(const char *tag, wchar_t *data, int dataLen) +{ + if (!hasData) + return 0; + + if (!_stricmp(tag, "title")) + { + MultiByteToWideCharSZ(ID3V1_CODEPAGE, 0, title, -1, data, dataLen); + return 1; + } + else if (!_stricmp(tag, "artist")) + { + MultiByteToWideCharSZ(ID3V1_CODEPAGE, 0, artist, -1, data, dataLen); + return 1; + } + else if (!_stricmp(tag, "album")) + { + MultiByteToWideCharSZ(ID3V1_CODEPAGE, 0, album, -1, data, dataLen); + return 1; + } + else if (!_stricmp(tag, "comment")) + { + MultiByteToWideCharSZ(ID3V1_CODEPAGE, 0, comment, -1, data, dataLen); + return 1; + } + else if (!_stricmp(tag, "year")) + { + MultiByteToWideCharSZ(ID3V1_CODEPAGE, 0, year, -1, data, dataLen); + return 1; + } + else if (!_stricmp(tag, "genre")) + { + if (genre >= numGenres) return -1; + StringCchCopyW(data, dataLen, id3v1_genres[genre]); + return 1; + } + else if (!_stricmp(tag, "track")) + { + if (track == 0) return -1; + StringCchPrintfW(data, dataLen, L"%u", track); + return 1; + } + else + return 0; +} + +int ID3v1::SetString(const char *tag, const wchar_t *data) +{ + if (!_stricmp(tag, "title")) + WideCharToMultiByteSZ(ID3V1_CODEPAGE, 0, data, -1, title, 31, 0 ,0); + else if (!_stricmp(tag, "artist")) + WideCharToMultiByteSZ(ID3V1_CODEPAGE, 0, data, -1, artist, 31, 0 ,0); + else if (!_stricmp(tag, "album")) + WideCharToMultiByteSZ(ID3V1_CODEPAGE, 0, data, -1, album, 31, 0 ,0); + else if (!_stricmp(tag, "comment")) + WideCharToMultiByteSZ(ID3V1_CODEPAGE, 0, data, -1, comment, 31, 0 ,0); + else if (!_stricmp(tag, "year")) + WideCharToMultiByteSZ(ID3V1_CODEPAGE, 0, data, -1, year, 5, 0 ,0); + else if (!_stricmp(tag, "genre")) + { + genre=255; + if (data) + { + for (size_t i=0;i<numGenres;i++) + { + if (!_wcsicmp(id3v1_genres[i], data)) + { + genre= (unsigned char)i; + } + } + } + } + else if (!_stricmp(tag, "track")) + { + int t = _wtoi(data); + if(t > 255) track = 0; + else track = t; + } + else + return 0; + + dirty=true; + hasData = 1; + return 1; +} + +void ID3v1::Clear() +{ + hasData=false; + dirty=true; + //clear data + title[0]=artist[0]=album[0]=comment[0]=year[0]=0; + genre = track = 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/ID3v1.h b/Src/Plugins/Input/in_mp3/ID3v1.h new file mode 100644 index 00000000..8763cd01 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/ID3v1.h @@ -0,0 +1,29 @@ +#ifndef NULLSOFT_IN_MP3_ID3V1_H +#define NULLSOFT_IN_MP3_ID3V1_H + +#include <bfc/platform/types.h> +class ID3v1 +{ +public: + ID3v1(); + int Decode(const void *data); + // return -1 for empty, 1 for OK, 0 for "don't understand tag name" + int GetString(const char *tag, wchar_t *data, int dataLen); + // returns 1 for OK, 0 for "don't understand tag name" + int SetString(const char *tag, const wchar_t *data); + int Encode(void *data); + bool IsDirty() { return dirty; } + bool HasData() { return hasData; } + void Clear(); + +private: + char title[31],artist[31],album[31],comment[31]; + char year[5]; + unsigned char genre; + unsigned char track; + + bool hasData; + bool dirty; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/ID3v2.cpp b/Src/Plugins/Input/in_mp3/ID3v2.cpp new file mode 100644 index 00000000..04600232 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/ID3v2.cpp @@ -0,0 +1,654 @@ +#include "ID3v2.h" +#include "id3.h" +#include "config.h" +#include "api__in_mp3.h" +#include "../Agave/AlbumArt/svc_albumArtProvider.h" +#include "../nu/AutoChar.h" +#include "../nu/AutoWide.h" +#include <strsafe.h> + +static inline const wchar_t *IncSafe(const wchar_t *val, int x) +{ + while (x--) + { + if (val && *val) + val++; + } + return val; +} + +extern const wchar_t *id3v1_genres[]; +extern size_t numGenres; + +ID3v2::ID3v2() +{ + hasData=false; + dirty=false; +} + +int ID3v2::Decode(const void *data, size_t len) +{ + if (!config_parse_id3v2 || !data) + { + hasData=false; + return 0; + } + + id3v2.Parse((uchar *)data, (uchar *)data+ID3_TAGHEADERSIZE); + if (id3v2.NumFrames() > 0) + { + hasData=true; + return 0; + } + else + return 1; +} + +// return -1 for empty, 1 for OK, 0 for "don't understand tag name" +int ID3v2::GetString(const char *tag, wchar_t *data, int dataLen) +{ + if (!_stricmp(tag, "title")) + return ID3_GetTagText(&id3v2, ID3FID_TITLE, data, dataLen)?1:-1; + else if (!_stricmp(tag, "album")) + return ID3_GetTagText(&id3v2, ID3FID_ALBUM, data, dataLen)?1:-1; + else if (!_stricmp(tag, "artist")) + return ID3_GetTagText(&id3v2, ID3FID_LEADARTIST, data, dataLen)?1:-1; + else if (!_stricmp(tag, "albumartist")) + { + if (!ID3_GetTagText(&id3v2, ID3FID_BAND, data, dataLen) && !ID3_GetUserText(&id3v2, L"ALBUM ARTIST", data, dataLen) && !ID3_GetUserText(&id3v2, L"ALBUMARTIST", data, dataLen)) + return ID3_GetUserText(&id3v2, L"Band", data, dataLen)?1:-1; + else + return 1; + } + else if (!_stricmp(tag, "comment")) + return ID3_GetComment(&id3v2, data, dataLen)?1:-1; + else if (!_stricmp(tag, "year")) + { + if (!ID3_GetTagText(&id3v2, ID3FID_RECORDINGTIME, data, dataLen)) + return ID3_GetTagText(&id3v2, ID3FID_YEAR, data, dataLen)?1:-1; + else + return 1; + } + else if (!_stricmp(tag, "composer")) + return ID3_GetTagText(&id3v2, ID3FID_COMPOSER, data, dataLen)?1:-1; + else if (!_stricmp(tag, "replaygain_track_gain")) + return ID3_GetUserText(&id3v2, L"replaygain_track_gain", data, dataLen)?1:-1; + else if (!_stricmp(tag, "replaygain_album_gain")) + return ID3_GetUserText(&id3v2, L"replaygain_album_gain", data, dataLen)?1:-1; + else if (!_stricmp(tag, "replaygain_track_peak")) + return ID3_GetUserText(&id3v2, L"replaygain_track_peak", data, dataLen)?1:-1; + else if (!_stricmp(tag, "replaygain_album_peak")) + return ID3_GetUserText(&id3v2, L"replaygain_album_peak", data, dataLen)?1:-1; + else if (!_stricmp(tag, "genre")) + { + data[0] = 0; + if (ID3_GetTagText(&id3v2, ID3FID_CONTENTTYPE, data, dataLen)) + { + wchar_t *tmp = data; + while (tmp && *tmp == ' ') tmp++; + if (tmp && (*tmp == '(' || (*tmp >= '0' && *tmp <= '9'))) // both (%d) and %d forms + { + int noparam = 0; + if (*tmp == '(') tmp++; + else noparam = 1; + size_t genre_index = _wtoi(tmp); + int cnt = 0; + while (tmp && *tmp >= '0' && *tmp <= '9') cnt++, tmp++; + while (tmp && *tmp == ' ') tmp++; + + if (tmp && (((!*tmp && noparam) || (!noparam && *tmp == ')')) && cnt > 0)) + { + if (genre_index < numGenres) + StringCchCopyW(data, dataLen, id3v1_genres[genre_index]); + } + } + return 1; + } + return -1; + } + else if (!_stricmp(tag, "track")) + return ID3_GetTagText(&id3v2, ID3FID_TRACKNUM, data, dataLen)?1:-1; + else if (!_stricmp(tag, "disc")) + return ID3_GetTagText(&id3v2, ID3FID_PARTINSET, data, dataLen)?1:-1; + else if (!_stricmp(tag, "bpm")) + return ID3_GetTagText(&id3v2, ID3FID_BPM, data, dataLen)?1:-1; + else if (!_stricmp(tag, "rating")) + return ID3_GetRating(&id3v2, data, dataLen)?1:-1; + else if (!_stricmp(tag, "conductor")) + return ID3_GetTagText(&id3v2, ID3FID_CONDUCTOR, data, dataLen)?1:-1; + else if (!_stricmp(tag, "key")) + return ID3_GetTagText(&id3v2, ID3FID_KEY, data, dataLen)?1:-1; + else if (!_stricmp(tag, "mood")) + return ID3_GetTagText(&id3v2, ID3FID_MOOD, data, dataLen)?1:-1; + else if (!_stricmp(tag, "subtitle")) + return ID3_GetTagText(&id3v2, ID3FID_SUBTITLE, data, dataLen)?1:-1; + else if (!_stricmp(tag, "lyricist")) + return ID3_GetTagText(&id3v2, ID3FID_LYRICIST, data, dataLen)?1:-1; + else if (!_stricmp(tag, "ISRC")) + return ID3_GetTagText(&id3v2, ID3FID_ISRC, data, dataLen)?1:-1; + else if (!_stricmp(tag, "media")) + return ID3_GetTagText(&id3v2, ID3FID_MEDIATYPE, data, dataLen)?1:-1; + else if (!_stricmp(tag, "remixing")) + return ID3_GetTagText(&id3v2, ID3FID_MIXARTIST, data, dataLen)?1:-1; + else if (!_stricmp(tag, "originalartist")) + return ID3_GetTagText(&id3v2, ID3FID_ORIGARTIST, data, dataLen)?1:-1; + else if (!_stricmp(tag, "encoder")) + return ID3_GetTagText(&id3v2, ID3FID_ENCODERSETTINGS, data, dataLen)?1:-1; + else if (!_stricmp(tag, "publisher")) + return ID3_GetTagText(&id3v2, ID3FID_PUBLISHER, data, dataLen)?1:-1; + else if (!_stricmp(tag, "copyright")) + return ID3_GetTagText(&id3v2, ID3FID_COPYRIGHT, data, dataLen)?1:-1; + else if (!_stricmp(tag, "compilation")) + return ID3_GetTagText(&id3v2, ID3FID_COMPILATION, data, dataLen)?1:-1; + else if (!_stricmp(tag, "url")) + return ID3_GetTagUrl(&id3v2, ID3FID_WWWUSER, data, dataLen)?1:-1; + else if (!_stricmp(tag, "GracenoteFileID")) + return ID3_GetGracenoteTagID(&id3v2, data, dataLen)?1:-1; + else if (!_stricmp(tag, "GracenoteExtData")) + { + if (!ID3_GetUserText(&id3v2, L"GN_ExtData", data, dataLen)) + return ID3_GetUserText(&id3v2, L"GN/ExtData", data, dataLen)?1:-1; + else + return 1; + } + else if (!_stricmp(tag, "tool")) + return ID3_GetTagText(&id3v2, ID3FID_ENCODEDBY, data, dataLen)?1:-1; + else if (!_stricmp(tag, "pregap")) + { + data[0] = 0; + // first, check for stupid iTunSMPB TXXX frame + wchar_t gaps[128] = L""; + const wchar_t *itr = ID3_GetComment(&id3v2, L"iTunSMPB", gaps, 128); + if (itr && *itr) + { + itr = IncSafe(itr, 9); + unsigned int prepad = wcstoul(itr, 0, 16); + StringCchPrintfW(data, dataLen, L"%u", prepad); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "postgap")) + { + data[0] = 0; + // first, check for stupid iTunSMPB TXXX frame + wchar_t gaps[128] = L""; + const wchar_t *itr = ID3_GetComment(&id3v2, L"iTunSMPB", gaps, 128); + if (itr && *itr) + { + itr = IncSafe(itr, 18); + unsigned int postpad = wcstoul(itr, 0, 16); + StringCchPrintfW(data, dataLen, L"%u", postpad); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "numsamples")) + { + data[0] = 0; + // first, check for stupid iTunSMPB TXXX frame + wchar_t gaps[128] = L""; + const wchar_t *itr = ID3_GetComment(&id3v2, L"iTunSMPB", gaps, 128); + if (itr && *itr) + { + itr = IncSafe(itr, 27); + unsigned __int64 samples = wcstoul(itr, 0, 16); + StringCchPrintfW(data, dataLen, L"%I64u", samples); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "endoffset")) + { + data[0] = 0; + // first, check for stupid iTunSMPB TXXX frame + wchar_t gaps[128] = L""; + const wchar_t *itr = ID3_GetComment(&id3v2, L"iTunSMPB", gaps, 128); + if (itr && *itr) + { + itr = IncSafe(itr, 53); + unsigned __int32 endoffset = wcstoul(itr, 0, 16); + StringCchPrintfW(data, dataLen, L"%I32u", endoffset); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "category")) + { + return ID3_GetTagText(&id3v2, ID3FID_CONTENTGROUP, data, dataLen)?1:-1; + } + // things generally added by Musicbrainz tagging (either specific or additional) + else if (!_stricmp(tag, "acoustid") || !_stricmp(tag, "acoustid_id")) + { + return ID3_GetUserText(&id3v2, L"Acoustid Id", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "acoustid_fingerprint")) + { + return ID3_GetUserText(&id3v2, L"Acoustid Fingerprint", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "asin")) + { + return ID3_GetUserText(&id3v2, L"ASIN", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "barcode")) + { + return ID3_GetUserText(&id3v2, L"BARCODE", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "catalognumber")) + { + return ID3_GetUserText(&id3v2, L"CATALOGNUMBER", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "script")) + { + return ID3_GetUserText(&id3v2, L"SCRIPT", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_recordingid")) // (track id) + { + return ID3_GetMusicbrainzRecordingID(&id3v2, data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_trackid")) // TODO not working (album track id) + { + return ID3_GetUserText(&id3v2, L"MusicBrainz Release Track Id", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_albumid")) + { + return ID3_GetUserText(&id3v2, L"MusicBrainz Album Id", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_artistid")) + { + return ID3_GetUserText(&id3v2, L"MusicBrainz Artist Id", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_albumartistid")) + { + return ID3_GetUserText(&id3v2, L"MusicBrainz Album Artist Id", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_releasestatus") || !_stricmp(tag, "musicbrainz_albumstatus")) + { + return ID3_GetUserText(&id3v2, L"MusicBrainz Album Status", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_releasetype") || !_stricmp(tag, "musicbrainz_albumtype")) + { + return ID3_GetUserText(&id3v2, L"MusicBrainz Album Type", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_releasecountry") || !_stricmp(tag, "musicbrainz_albumcountry")) + { + return ID3_GetUserText(&id3v2, L"MusicBrainz Album Release Country", data, dataLen)?1:-1; + } + else if (!_stricmp(tag, "musicbrainz_releasegroupid") || !_stricmp(tag, "musicbrainz_albumgroupid")) + { + return ID3_GetUserText(&id3v2, L"MusicBrainz Release Group Id", data, dataLen)?1:-1; + } + else + { + return 0; + } +} + +void ID3v2::add_set_latin_id3v2_frame(ID3_FrameID id, const wchar_t *c) +{ + ID3_Frame *f = id3v2.Find(id); + if (!c) + { + if (f) + id3v2.RemoveFrame(f); + } + else + { + if (f) + { + SetFrameEncoding(f, ENCODING_FORCE_ASCII); + AutoChar temp(c); //AutoChar temp(c, 28591); // todo: benski> changed back to local to keep old winamp tagged files working + f->Field(ID3FN_URL).SetLocal(temp); //f->Field(ID3FN_TEXT).SetLatin(temp);// todo: benski> changed back to local to keep old winamp tagged files working + } + else + { + f = new ID3_Frame(id); + SetFrameEncoding(f, ENCODING_FORCE_ASCII); + AutoChar temp(c); //AutoChar temp(c, 28591); // todo: benski> changed back to local to keep old winamp tagged files working + f->Field(ID3FN_URL).SetLocal(temp); //f->Field(ID3FN_TEXT).SetLatin(temp);// todo: benski> changed back to local to keep old winamp tagged files working + id3v2.AddFrame(f, TRUE); + } + } +} + +int ID3v2::SetString(const char *tag, const wchar_t *data) +{ + if (!_stricmp(tag, "artist")) + add_set_id3v2_frame(ID3FID_LEADARTIST, data); + else if (!_stricmp(tag, "album")) + add_set_id3v2_frame(ID3FID_ALBUM, data); + else if (!_stricmp(tag, "albumartist")) + { + add_set_id3v2_frame(ID3FID_BAND, data); + if (!data || !*data) // if we're deleting the field + { + ID3_AddUserText(&id3v2, L"ALBUM ARTIST", data); // delete this alternate field also, or it's gonna look like it didn't "take" with a fb2k file + ID3_AddUserText(&id3v2, L"ALBUMARTIST", data); // delete this alternate field also, or it's gonna look like it didn't "take" with an mp3tag file + ID3_AddUserText(&id3v2, L"Band", data); // delete this alternate field also, or it's gonna look like it didn't "take" with an audacity file + } + } + else if (!_stricmp(tag, "comment")) + ID3_AddSetComment(&id3v2, data); + else if (!_stricmp(tag, "title")) + add_set_id3v2_frame(ID3FID_TITLE, data); + else if (!_stricmp(tag, "year")) + { + add_set_id3v2_frame(ID3FID_YEAR, data); + if (id3v2.version >= 4) // work around the fact that our id3 code doesn't handle versioning like this too well + add_set_id3v2_frame(ID3FID_RECORDINGTIME, data); + else + add_set_id3v2_frame(ID3FID_RECORDINGTIME, (wchar_t *)0); + } + else if (!_stricmp(tag, "genre")) + add_set_id3v2_frame(ID3FID_CONTENTTYPE, data); + else if (!_stricmp(tag, "track")) + add_set_id3v2_frame(ID3FID_TRACKNUM, data); + else if (!_stricmp(tag, "disc")) + add_set_id3v2_frame(ID3FID_PARTINSET, data); + else if (!_stricmp(tag, "bpm")) + add_set_id3v2_frame(ID3FID_BPM, data); + else if (!_stricmp(tag, "rating")) + ID3_AddSetRating(&id3v2, data); + else if (!_stricmp(tag, "tool")) + add_set_id3v2_frame(ID3FID_ENCODEDBY, data); + else if (!_stricmp(tag, "composer")) + add_set_id3v2_frame(ID3FID_COMPOSER, data); + else if (!_stricmp(tag, "replaygain_track_gain")) + ID3_AddUserText(&id3v2, L"replaygain_track_gain", data, ENCODING_FORCE_ASCII); + else if (!_stricmp(tag, "replaygain_track_peak")) + ID3_AddUserText(&id3v2, L"replaygain_track_peak", data, ENCODING_FORCE_ASCII); + else if (!_stricmp(tag, "replaygain_album_gain")) + ID3_AddUserText(&id3v2, L"replaygain_album_gain", data, ENCODING_FORCE_ASCII); + else if (!_stricmp(tag, "replaygain_album_peak")) + ID3_AddUserText(&id3v2, L"replaygain_album_peak", data, ENCODING_FORCE_ASCII); + else if (!_stricmp(tag, "originalartist")) + add_set_id3v2_frame(ID3FID_ORIGARTIST, data); + else if (!_stricmp(tag, "encoder")) + add_set_id3v2_frame(ID3FID_ENCODERSETTINGS, data); + else if (!_stricmp(tag, "publisher")) + add_set_id3v2_frame(ID3FID_PUBLISHER, data); + else if (!_stricmp(tag, "copyright")) + add_set_id3v2_frame(ID3FID_COPYRIGHT, data); + else if (!_stricmp(tag, "compilation")) + add_set_id3v2_frame(ID3FID_COMPILATION, data); + else if (!_stricmp(tag, "remixing")) + add_set_id3v2_frame(ID3FID_MIXARTIST, data); + else if (!_stricmp(tag, "ISRC")) + add_set_id3v2_frame(ID3FID_ISRC, data); + else if (!_stricmp(tag, "url")) + add_set_latin_id3v2_frame(ID3FID_WWWUSER, data); // TODO: we should %## escape invalid characters + //add_set_id3v2_frame(ID3FID_WWWUSER, data); + else if (!_stricmp(tag, "GracenoteFileID")) + ID3_AddSetGracenoteTagID(&id3v2, data); + else if (!_stricmp(tag, "GracenoteExtData")) + { + ID3_AddUserText(&id3v2, L"GN_ExtData", data, ENCODING_FORCE_ASCII); + ID3_AddUserText(&id3v2, L"GN_ExtData",0); // delete this alternate field also + } + else if (!_stricmp(tag, "category")) + add_set_id3v2_frame(ID3FID_CONTENTGROUP, data); + else + return 0; + hasData=true; + dirty=true; + return 1; +} + +void ID3v2::add_set_id3v2_frame(ID3_FrameID id, const wchar_t *c) +{ + ID3_Frame *f = id3v2.Find(id); + if (!c || !*c) + { + if (f) + id3v2.RemoveFrame(f); + } + else + { + if (f) + { + SetFrameEncoding(f); + f->Field(ID3FN_TEXT).SetUnicode(c); + } + else + { + f = new ID3_Frame(id); + SetFrameEncoding(f); + f->Field(ID3FN_TEXT).SetUnicode(c); + id3v2.AddFrame(f, TRUE); + } + } +} + +uint32_t ID3v2::EncodeSize() +{ + if (!hasData) + return 0; // simple :) + + return (uint32_t)id3v2.Size(); +} + +int ID3v2::Encode(const void *data, size_t len) +{ + id3v2.Render((uchar *)data); + return 0; +} + +static bool NameToAPICType(const wchar_t *name, int &num) +{ + if (!name || !*name) // default to cover + num=0x3; + else if (!_wcsicmp(name, L"fileicon")) // 32x32 pixels 'file icon' (PNG only) + num=0x1; + else if (!_wcsicmp(name, L"icon")) // Other file icon + num=0x2; + else if (!_wcsicmp(name, L"cover")) // Cover (front) + num=0x3; + else if (!_wcsicmp(name, L"back")) // Cover (back) + num=0x4; + else if (!_wcsicmp(name, L"leaflet")) // Leaflet page + num=0x5; + else if (!_wcsicmp(name, L"media")) // Media (e.g. lable side of CD) + num=0x6; + else if (!_wcsicmp(name, L"leadartist")) //Lead artist/lead performer/soloist + num=0x7; + else if (!_wcsicmp(name, L"artist")) // Artist/performer + num=0x8; + else if (!_wcsicmp(name, L"conductor")) // Conductor + num=0x9; + else if (!_wcsicmp(name, L"band")) // Band/Orchestra + num=0xA; + else if (!_wcsicmp(name, L"composer")) // Composer + num=0xB; + else if (!_wcsicmp(name, L"lyricist")) // Lyricist/text writer + num=0xC; + else if (!_wcsicmp(name, L"location")) // Recording Location + num=0xD; + else if (!_wcsicmp(name, L"recording")) // During recording + num=0xE; + else if (!_wcsicmp(name, L"performance")) // During performance + num=0xF; + else if (!_wcsicmp(name, L"preview")) // Movie/video screen capture + num=0x10; + else if (!_wcsicmp(name, L"fish")) // A bright coloured fish + num=0x11; + else if (!_wcsicmp(name, L"illustration")) // Illustration + num=0x12; + else if (!_wcsicmp(name, L"artistlogo")) // Band/artist logotype + num=0x13; + else if (!_wcsicmp(name, L"publisherlogo")) // Publisher/Studio logotype + num=0x14; + else + return false; + return true; +} + +int ID3v2::GetAlbumArt(const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType) +{ + int pictype = 0; + if (NameToAPICType(type, pictype)) + { + // try to get our specific picture type + ID3_Frame *frame = id3v2.Find(ID3FID_PICTURE, ID3FN_PICTURETYPE, pictype); + + if (!frame && pictype == 3) // if not, just try a generic one + { + frame = id3v2.Find(ID3FID_PICTURE); + /*benski> CUT! + if (frame) + { + ID3_Field &field = frame->Field(ID3FN_PICTURETYPE); + if (field.Get()) + frame=0; + }*/ + } + + if (frame) + { + char *fulltype = ID3_GetString(frame, ID3FN_MIMETYPE); + char *type = 0; + if (fulltype && *fulltype) + { + type = strchr(fulltype, '/'); + } + + if (type && *type) + { + type++; + + char *type2 = strchr(type, '/'); + if (type2 && *type2) type2++; + else type2 = type; + + int typelen = MultiByteToWideChar(CP_ACP, 0, type2, -1, 0, 0); + *mimeType = (wchar_t *)WASABI_API_MEMMGR->sysMalloc(typelen * sizeof(wchar_t)); + MultiByteToWideChar(CP_ACP, 0, type2, -1, *mimeType, typelen); + free(fulltype); + } + else + { + // attempt to work out a mime type from known 'invalid' values + if (fulltype && *fulltype) + { + if (!strcmpi(fulltype, "png") || !strcmpi(fulltype, "bmp") || + !strcmpi(fulltype, "jpg") || !strcmpi(fulltype, "jpeg") || + !strcmpi(fulltype, "gif")) + { + int typelen = MultiByteToWideChar(CP_ACP, 0, fulltype, -1, 0, 0);// + 6; + *mimeType = (wchar_t*)WASABI_API_MEMMGR->sysMalloc(typelen * sizeof(wchar_t)); + MultiByteToWideChar(CP_ACP, 0, fulltype, -1, *mimeType, typelen); + CharLowerBuff(*mimeType, typelen); + free(fulltype); + fulltype = 0; + } + if (0 != fulltype) + { + free(fulltype); + fulltype = 0; + } + } + else + { + *mimeType = 0; // unknown! + } + } + + ID3_Field &field = frame->Field(ID3FN_DATA); + *len = field.Size(); + *bits = WASABI_API_MEMMGR->sysMalloc(*len); + field.Get((uchar *)*bits, *len); + return ALBUMARTPROVIDER_SUCCESS; + } + } + return ALBUMARTPROVIDER_FAILURE; +} + +int ID3v2::SetAlbumArt(const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType) +{ + int pictype; + if (NameToAPICType(type, pictype)) + { + // try to get our specific picture type + ID3_Frame *frame = id3v2.Find(ID3FID_PICTURE, ID3FN_PICTURETYPE, pictype); + + if (!frame && pictype == 3) // if not, just try a generic one + { + frame = id3v2.Find(ID3FID_PICTURE); + /* benski> cut + if (frame) + { + ID3_Field &field = frame->Field(ID3FN_PICTURETYPE); + if (field.Get()) + frame=0; + }*/ + } + bool newFrame=false; + if (!frame) + { + frame = new ID3_Frame(ID3FID_PICTURE); + newFrame = true; + } + + if (frame) + { + wchar_t mt[32] = {L"image/jpeg"}; + if (mimeType) + { + if (wcsstr(mimeType, L"/") != 0) + { + StringCchCopyW(mt, 32, mimeType); + } + else + { + StringCchPrintfW(mt, 32, L"image/%s", mimeType); + } + } + + frame->Field(ID3FN_MIMETYPE).SetLatin(AutoChar(mt, 28591)); + frame->Field(ID3FN_PICTURETYPE).Set(pictype); + frame->Field(ID3FN_DESCRIPTION).Clear(); + frame->Field(ID3FN_DATA).Set((uchar *)bits, len); + if (newFrame) + id3v2.AddFrame(frame, TRUE); + dirty=1; + return ALBUMARTPROVIDER_SUCCESS; + } + } + return ALBUMARTPROVIDER_FAILURE; +} + +int ID3v2::DeleteAlbumArt(const wchar_t *type) +{ + int pictype; + if (NameToAPICType(type, pictype)) + { + // try to get our specific picture type + ID3_Frame *frame = id3v2.Find(ID3FID_PICTURE, ID3FN_PICTURETYPE, pictype); + + if (!frame && pictype == 3) // if not, just try a generic one + { + frame = id3v2.Find(ID3FID_PICTURE); + /* benski> cut + if (frame) + { + ID3_Field &field = frame->Field(ID3FN_PICTURETYPE); + if (field.Get()) + frame=0; + } + */ + } + if (frame) + { + id3v2.RemoveFrame(frame); + dirty=1; + return ALBUMARTPROVIDER_SUCCESS; + } + } + return ALBUMARTPROVIDER_FAILURE; +} + +void ID3v2::Clear() +{ + dirty=1; + hasData=false; + id3v2.Clear(); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/ID3v2.h b/Src/Plugins/Input/in_mp3/ID3v2.h new file mode 100644 index 00000000..f7f21f0e --- /dev/null +++ b/Src/Plugins/Input/in_mp3/ID3v2.h @@ -0,0 +1,33 @@ +#ifndef NULLSOFT_IN_MP3_ID3v2_H +#define NULLSOFT_IN_MP3_ID3v2_H + +#include "../id3v2/id3_tag.h" + +class ID3v2 +{ +public: + ID3v2(); + bool HasData() { return hasData; } + bool IsDirty() { return dirty; } + int Decode(const void *data, size_t len); + int Encode(const void *data, size_t len); + uint32_t EncodeSize(); + // return -1 for empty, 1 for OK, 0 for "don't understand tag name" + int GetString(const char *tag, wchar_t *data, int dataLen); + int SetString(const char *tag, const wchar_t *data); + + int GetAlbumArt(const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType); + int SetAlbumArt(const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType); + int DeleteAlbumArt(const wchar_t *type); + + void Clear(); +private: + void add_set_id3v2_frame(ID3_FrameID id, const wchar_t *c); + void add_set_latin_id3v2_frame(ID3_FrameID id, const wchar_t *c); + bool hasData; + bool dirty; +public: + ID3_Tag id3v2; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/IN2.H b/Src/Plugins/Input/in_mp3/IN2.H new file mode 100644 index 00000000..3c6f95e7 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/IN2.H @@ -0,0 +1,4 @@ +#ifndef UNICODE_INPUT_PLUGIN +#define UNICODE_INPUT_PLUGIN +#endif +#include "../Winamp/in2.h"
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/LAMEinfo.cpp b/Src/Plugins/Input/in_mp3/LAMEinfo.cpp new file mode 100644 index 00000000..febfec98 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/LAMEinfo.cpp @@ -0,0 +1,398 @@ +#include "LAMEinfo.h" +#include <windows.h> +#include <memory.h> +#include <math.h> +#include "api__in_mp3.h" +#include "resource.h" +#include "in2.h" +#pragma intrinsic(memcmp) + +extern In_Module mod; + +// Xing header - +// 4 Xing +// 4 flags +// 4 frames +// 4 bytes +// 100 toc +// 4 bytes VBR quality + +// Lame tag +// 9 bytes - release name +// 11 + +// Lame extended info tag + +// http://gabriel.mp3-tech.org/mp3infotag.html + + +/*-------------------------------------------------------------*/ +static int32_t ExtractI4(unsigned char *buf) +{ + int x; + // big endian extract + + x = buf[0]; + x <<= 8; + x |= buf[1]; + x <<= 8; + x |= buf[2]; + x <<= 8; + x |= buf[3]; + + return x; +} + +static int16_t ExtractI2(unsigned char *buf) +{ + int x; + // big endian extract + + x = buf[0]; + x <<= 8; + x |= buf[1]; + + return x; +} + + +const static int bitrateV1L3[] = { 0, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 0}; +const static int bitrateV1L1[] = { 0, 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000, 0}; +const static int bitrateV1L2[] = { 0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000, 0}; +const static int bitrateV2L1[] = { 0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000, 0}; +const static int bitrateV2L2L3[] = { 0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 0}; + +const static int sampleRateV1[] = {44100, 48000, 32000, 0}; +const static int sampleRateV2[] = {22050, 24000, 16000, 0}; +const static int sampleRateV2_5[] = {11025, 12000, 8000, 0}; + +// [mpeg_version][layer] +static const int samples_per_frame[4][4] = +{ + // Layer 3, Layer 2, Layer 1 + { 0, 576, 1152, 384}, // MPEG2.5 + { 0, }, + { 0, 576, 1152, 384}, // MPEG2 + { 0, 1152, 1152, 384}, // MPEG1 +}; + +void MPEGFrame::ReadBuffer(const unsigned char *buffer) + { + sync = ((unsigned short)buffer[0] << 3) | (buffer[1] >> 5); + mpegVersion = (buffer[1] >> 3) & 3; + layer = (buffer[1] >> 1) & 3; + protection = (buffer[1]) & 1; + bitrateIndex = (buffer[2] >> 4) & 0xF; + sampleRateIndex = (buffer[2] >> 2) & 3; + paddingBit = (buffer[2] >> 1) & 1; + privateBit = buffer[2] & 1; + channelMode = (buffer[3] >> 6) & 3; + modeExtension = (buffer[3] >> 4) & 3; + copyright = (buffer[3] >> 3) & 1; + original = (buffer[3] >> 2) & 1; + emphasis = (buffer[3]) & 3; + } + bool MPEGFrame::IsSync() + { + return sync == 0x07FF + && layer != LayerError + && mpegVersion != MPEG_Error + && bitrateIndex != 15 + && bitrateIndex != 0 + && sampleRateIndex != 3 + && !(mpegVersion == MPEG2 && layer != Layer3) + && !(mpegVersion == MPEG2_5 && layer != Layer3); + + } + int MPEGFrame::GetBitrate() + { + switch (mpegVersion) + { + case MPEG1: + switch (layer) + { + case Layer1: + return bitrateV1L1[bitrateIndex]; + case Layer2: + return bitrateV1L2[bitrateIndex]; + case Layer3: + return bitrateV1L3[bitrateIndex]; + } + break; + case MPEG2: + case MPEG2_5: + switch (layer) + { + case Layer1: + return bitrateV2L1[bitrateIndex]; + case Layer2: + case Layer3: + return bitrateV2L2L3[bitrateIndex]; + } + break; + } + + return 0; // shouldn't get here + } + int MPEGFrame::GetPadding() + { + if (paddingBit == NotPadded) + return 0; + + if (layer == Layer1) + return 4; + else + return 1; + } + int MPEGFrame::HeaderSize() + { + if (protection == CRC) + return 4 + 2; // 32bits frame header, 16bits CRC + else + return 4; // 32bits frame ehader + } + + int MPEGFrame::GetSampleRate() const + { + switch(mpegVersion) + { + case MPEG1: return sampleRateV1[sampleRateIndex]; + case MPEG2:return sampleRateV2[sampleRateIndex]; + case MPEG2_5:return sampleRateV2_5[sampleRateIndex]; + default: return 99999999; // return something that will hopefully cause the framesize to be 0 + } + + } + + int MPEGFrame::GetSamplesPerFrame() const + { + return samples_per_frame[mpegVersion][layer]; + } + + bool MPEGFrame::IsCopyright() + { + return copyright == 1; + } + bool MPEGFrame::IsCRC() + { + return protection == CRC; + } + + bool MPEGFrame::IsOriginal() + { + return original == 1; + } + + const char *MPEGFrame::GetEmphasisString() + { + static char tempGE[32]; + switch (emphasis) + { + case Emphasis_None: + return WASABI_API_LNGSTRING_BUF(IDS_NONE,tempGE,32); + case Emphasis_50_15_ms: + return WASABI_API_LNGSTRING_BUF(IDS_50_15_MICROSEC,tempGE,32); + case Emphasis_reserved: + return WASABI_API_LNGSTRING_BUF(IDS_INVALID,tempGE,32); + case Emphasis_CCIT_J_17: + return "CITT j.17"; + default: + return WASABI_API_LNGSTRING_BUF(IDS_ERROR,tempGE,32); + } + } + + int MPEGFrame::FrameSize() + { + if (layer == Layer1) + { + return (int)floor((48.0f*(float)GetBitrate())/GetSampleRate()) + GetPadding(); + } + else if (layer == Layer2 || layer == Layer3) + { + if (mpegVersion == MPEG1) + return (int)floor((144.0f*(float)GetBitrate())/GetSampleRate()) + GetPadding(); + else + return (int)floor((72.0f*(float)GetBitrate())/GetSampleRate()) + GetPadding(); + } + return 0; + } + + const char *MPEGFrame::GetMPEGVersionString() + { + switch(mpegVersion) + { + case MPEG1: + return "MPEG-1"; + case MPEG2: + return "MPEG-2"; + case MPEG2_5: + return "MPEG-2.5"; + default: + static char tempMF[16]; + return WASABI_API_LNGSTRING_BUF(IDS_ERROR,tempMF,16); + } + } + + const char *MPEGFrame::GetChannelModeString() + { + static char tempGC[32]; + switch(channelMode) + { + case Stereo: + return WASABI_API_LNGSTRING_BUF(IDS_STEREO,tempGC,32); + case JointStereo: + return WASABI_API_LNGSTRING_BUF(IDS_JOINT_STEREO,tempGC,32); + case DualChannel: + return WASABI_API_LNGSTRING_BUF(IDS_2_CHANNEL,tempGC,32); + case Mono: + return WASABI_API_LNGSTRING_BUF(IDS_MONO,tempGC,32); + default: + return WASABI_API_LNGSTRING_BUF(IDS_ERROR,tempGC,32); + } + } + + int MPEGFrame::GetLayer() + { + switch(layer) + { + case Layer1: + return 1; + case Layer2: + return 2; + case Layer3: + return 3; + default: + return 0; + } + } + + int MPEGFrame::GetNumChannels() + { + switch(channelMode) + { + case Stereo: + return 2; + case JointStereo: + return 2; + case DualChannel: + return 2; + case Mono: + return 1; + default: + return 0; + } + } + +int ReadLAMEinfo(unsigned char *buffer, LAMEinfo *lameInfo) +{ + int flags; + MPEGFrame frame; + frame.ReadBuffer(buffer); + + if (!frame.IsSync()) + return 0; + + lameInfo->h_id = frame.mpegVersion & 1; + lameInfo->samprate = frame.GetSampleRate(); + // determine offset of header + if (frame.mpegVersion == MPEGFrame::MPEG1) // MPEG 1 + { + if (frame.channelMode == MPEGFrame::Mono) + buffer += (17 + 4);//frame.HeaderSize()); + + else + buffer += (32 + 4);//frame.HeaderSize()); + } + else if (frame.mpegVersion == MPEGFrame::MPEG2) // MPEG 2 + { + if (frame.channelMode == MPEGFrame::Mono) + buffer += (9 + 4);//frame.HeaderSize()); + else + buffer += (17 + 4);//frame.HeaderSize()); + } + else if (frame.mpegVersion == MPEGFrame::MPEG2_5) // MPEG 2 + { + if (frame.channelMode == MPEGFrame::Mono) + buffer += (9 + 4);//frame.HeaderSize()); + else + buffer += (17 + 4);//frame.HeaderSize()); + } + + if (!memcmp(buffer, "Info", 4)) + lameInfo->cbr=1; + else if (memcmp(buffer, "Xing", 4) && memcmp(buffer, "Lame", 4)) + return 0; + + buffer += 4; // skip Xing tag + flags = lameInfo->flags = ExtractI4(buffer); + buffer += 4; // skip flags + + if (flags & FRAMES_FLAG) + { + lameInfo->frames = ExtractI4(buffer); + buffer += 4; // skip frames + } + if (flags & BYTES_FLAG) + { + lameInfo->bytes = ExtractI4(buffer); + buffer += 4; + } + if (flags & TOC_FLAG) + { + if (lameInfo->toc) + { + for (int i = 0;i < 100;i++) + lameInfo->toc[i] = buffer[i]; + } + buffer += 100; + } + + lameInfo->vbr_scale = -1; + if (flags & VBR_SCALE_FLAG) + { + lameInfo->vbr_scale = ExtractI4(buffer); + buffer += 4; + } + + if (!memcmp(buffer, "LAME", 4)) + { + for (int i=0;i<9;i++) + lameInfo->lameTag[i]=*buffer++; + lameInfo->lameTag[9]=0; // null terminate in case tag used all 20 characters + + lameInfo->encodingMethod = (*buffer++)&0xF; // we'll grab the VBR method + lameInfo->lowpass = (*buffer++)*100; // lowpass value + lameInfo->peak=*((float *)buffer); // read peak value + buffer+=4; // skip peak value + + // read track gain + int16_t gain_word = ExtractI2(buffer); + if ((gain_word & 0xFC00) == 0x2C00) + { + lameInfo->replaygain_track_gain = (float)(gain_word & 0x01FF); + lameInfo->replaygain_track_gain /= 10; + if (gain_word & 0x0200) + lameInfo->replaygain_track_gain = -lameInfo->replaygain_track_gain; + } + buffer+=2; + + // read album gain + gain_word = ExtractI2(buffer); + if ((gain_word & 0xFC00) == 0x4C00) + { + lameInfo->replaygain_album_gain = (float)(gain_word & 0x01FF); + lameInfo->replaygain_album_gain /= 10; + if (gain_word & 0x0200) + lameInfo->replaygain_album_gain = -lameInfo->replaygain_album_gain; + } + buffer+=2; + + buffer+=1; // skip encoding flags + ATH type + buffer+=1; // skip bitrate + + // get the encoder delay and padding, annoyingly as 12 bit values packed into 3 bytes + lameInfo->encoderDelay = ((unsigned short)buffer[0] << 4) | (buffer[1] >> 4); + lameInfo->padding = ((unsigned short)(buffer[1]&0x0F) << 8) | (buffer[2]); + } + return frame.FrameSize(); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/LAMEinfo.h b/Src/Plugins/Input/in_mp3/LAMEinfo.h new file mode 100644 index 00000000..d8e6db04 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/LAMEinfo.h @@ -0,0 +1,118 @@ +#ifndef NULLSOFT_LAMEINFOH +#define NULLSOFT_LAMEINFOH + + +#define FRAMES_FLAG 0x0001 +#define BYTES_FLAG 0x0002 +#define TOC_FLAG 0x0004 +#define VBR_SCALE_FLAG 0x0008 + +#define FRAMES_AND_BYTES (FRAMES_FLAG | BYTES_FLAG) +#include <memory.h> +#pragma intrinsic(memset) +struct LAMEinfo +{ + LAMEinfo() + { + memset(this, 0, sizeof(LAMEinfo)); + } + + int cbr; // set to 1 if the file is actually just CBR + // Xing + int h_id; + int samprate; // determined from MPEG header + int flags; // from Xing header data + int frames; // total bit stream frames from Xing header data + int bytes; // total bit stream bytes from Xing header data + int vbr_scale; // encoded vbr scale from Xing header data + unsigned char *toc; // pointer to unsigned char toc_buffer[100] + // may be NULL if toc not desired + + // LAME + char lameTag[10]; // 9 characters, but we'll add an extra NULL just in case + float peak; + float replaygain_album_gain; + float replaygain_track_gain; + unsigned short lowpass; + unsigned short encoderDelay; + unsigned short padding; + int encodingMethod; +}; + +enum +{ + ENCODING_METHOD_LAME = 0, + ENCODING_METHOD_CBR = 1, + ENCODING_METHOD_ABR = 2, + ENCODING_METHOD_VBR1 = 3, + ENCODING_METHOD_VBR2 = 4, + ENCODING_METHOD_VBR3 = 5, + ENCODING_METHOD_VBR4 = 6, + ENCODING_METHOD_CBR_2PASS = 8, + ENCODING_METHOD_ABR_2PASS = 9, +}; + +int ReadLAMEinfo(unsigned char *buffer, LAMEinfo *lameInfo); + +class MPEGFrame +{ +public: + int GetNumChannels(); + + void ReadBuffer(const unsigned char *buffer); + bool IsSync(); + int GetBitrate(); + int GetPadding(); + int HeaderSize(); + int GetSampleRate() const; + int FrameSize(); + const char *GetMPEGVersionString(); + const char *GetChannelModeString(); + const char *GetEmphasisString(); + int GetLayer(); + bool IsCRC(); + bool IsCopyright(); + bool IsOriginal(); + int MPEGFrame::GetSamplesPerFrame() const; + + enum + { + NotPadded=0, + Padded=1, + CRC = 0, + NoProtection = 1, + Stereo = 0, + JointStereo = 1, + DualChannel = 2, + Mono = 3, + MPEG1 = 3, + MPEG2 = 2, + MPEG_Error = 1, + MPEG2_5 = 0, + Layer1 = 3, + Layer2 = 2, + Layer3 = 1, + LayerError = 0, + Emphasis_None = 0, + Emphasis_50_15_ms = 1, + Emphasis_reserved = 2, + Emphasis_CCIT_J_17 = 3, + }; + + unsigned int sync:11, +mpegVersion:2, +layer:2, +protection:1, +bitrateIndex:4, +paddingBit:1, +privateBit:1, +channelMode:2, +modeExtension:2, +sampleRateIndex:2, +copyright:1, +original:1, +emphasis:2; +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/Lyrics3.cpp b/Src/Plugins/Input/in_mp3/Lyrics3.cpp new file mode 100644 index 00000000..bdd8ece6 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/Lyrics3.cpp @@ -0,0 +1,193 @@ +#include <windows.h> +#include "Lyrics3.h" +#include "config.h" +#include <strsafe.h> + +// http://www.id3.org/Lyrics3v2 + +Lyrics3::Lyrics3() +{ + artist=0; + title=0; + album=0; + hasData=false; + dirty=false; +} + +Lyrics3::~Lyrics3() +{ + free(artist); + free(title); + free(album); +} + +static wchar_t *CopyField(const uint8_t *buffer, uint32_t size) +{ + int converted = MultiByteToWideChar(28591, 0,(LPCSTR)buffer, size, 0, 0); + wchar_t *str = (wchar_t *)calloc((converted+1), sizeof(wchar_t)); + if (str) + { + converted = MultiByteToWideChar(28591, 0, (LPCSTR)buffer, size, str, converted); + str[converted]=0; + } + + return str; +} + +int Lyrics3::Decode(const void *data, size_t datalen) +{ + if (!config_parse_lyrics3) + return 1; + + if (memcmp(data, "LYRICSBEGIN", 11) == 0) + { + hasData = true; + + datalen-=11; + uint8_t *buffer = (uint8_t *)data+11; + + while (datalen > 8) + { + uint8_t fid[4] = {0}; + uint8_t sizeT[6] = {0}; + uint32_t size; + fid[3] = 0; + sizeT[5] = 0; + + memcpy(fid, buffer, 3); + buffer+=3; datalen-=3; + + memcpy(sizeT, buffer, 5); + buffer+=5; datalen-=5; + + size = strtoul((char *)sizeT, 0, 10); + + if (datalen >= size) + { + /*if ( memcmp(fid, "IND", 3) == 0) // the IND field + { + if ( buff2[ posn + 8 + 1 ] == '1') + stampsUsed = true; + } + else */ + if (memcmp(fid, "ETT", 3) == 0) // the TITLE field + { + title = CopyField(buffer, size); + } + else if (strcmp((char *) fid, "EAR") == 0) // the ARTIST field + { + artist = CopyField(buffer, size); + } + else if (strcmp((char *) fid, "EAL") == 0) // the ALBUM field + { + album = CopyField(buffer, size); + } + /*else if ( strcmp((char *) fid, "LYR") == 0) // the LYRICS field + { + char *text; + luint newSize; + + newSize = ID3_CRLFtoLF((char *) & buff2[ posn + 8 ], size); + + if ( stampsUsed) + newSize = ID3_StripTimeStamps((char *) & buff2[ posn + 8 ], newSize); + + if ( text = (char*)malloc(newSize + 1)) + { + text[ newSize ] = 0; + + memcpy( text, &buff2[ posn + 8 ], newSize); + + ID3_AddLyrics( this, text); + + free(text); + } + else + ID3_THROW( ID3E_NoMemory); + }*/ + + datalen-=size; + buffer+=size; + } + else + break; + } + return 0; + } + return 1; + +} + +int Lyrics3::GetString(const char *tag, wchar_t *data, int dataLen) +{ + if (!hasData) + return 0; + + if (!_stricmp(tag, "title")) + { + if (title && *title) + { + StringCchCopyW(data, dataLen, title); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "artist")) + { + if (artist && *artist) + { + StringCchCopyW(data, dataLen, artist); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "album")) + { + if (album && *album) + { + StringCchCopyW(data, dataLen, album); + return 1; + } + return -1; + } + + return 0; +} + +int Lyrics3::SetString(const char *tag, const wchar_t *data) +{ + int ret=0; + if (!_stricmp(tag, "title")) + { + if (title) free(title); + title = _wcsdup(data); + ret = 1; + } + else if (!_stricmp(tag, "artist")) + { + if ( artist ) free(artist); + artist = _wcsdup(data); + ret = 1; + } + else if (!_stricmp(tag, "album")) + { + if ( album ) free(album); + album = _wcsdup(data); + ret = 1; + } + + if(ret) + { + hasData=true; + } + return ret; +} + +void Lyrics3::Clear() +{ + free(artist); artist=0; + free(album); album=0; + free(title); title=0; + dirty=true; + hasData=false; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/Lyrics3.h b/Src/Plugins/Input/in_mp3/Lyrics3.h new file mode 100644 index 00000000..68a30f4d --- /dev/null +++ b/Src/Plugins/Input/in_mp3/Lyrics3.h @@ -0,0 +1,25 @@ +#ifndef NULLSOFT_IN_MP3_LYRICS3_H +#define NULLSOFT_IN_MP3_LYRICS3_H + +#include <bfc/platform/types.h> +class Lyrics3 +{ +public: + Lyrics3(); + ~Lyrics3(); + bool HasData() { return hasData; } + bool IsDirty() { return dirty; } + void Clear(); + void ResetDirty() { dirty=0; }; + int Decode(const void *data, size_t datalen); +// return -1 for empty, 1 for OK, 0 for "don't understand tag name" + int GetString(const char *tag, wchar_t *data, int dataLen); + int SetString(const char *tag, const wchar_t *data); + +private: + bool hasData; + bool dirty; + wchar_t *title, *album, *artist; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/MP3Info.cpp b/Src/Plugins/Input/in_mp3/MP3Info.cpp new file mode 100644 index 00000000..bc3d7158 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/MP3Info.cpp @@ -0,0 +1,612 @@ +#include "main.h" +#include "LAMEInfo.h" +#include "AACFrame.h" +#include "config.h" +#include "../winamp/wa_ipc.h" +#include <Richedit.h> +#include "api__in_mp3.h" +#include "FactoryHelper.h" +#include <shlwapi.h> +#include <strsafe.h> +#include <foundation/error.h> + +int fixAACCBRbitrate(int br); + +#if 0 +/* + +*/ +/* +int MP3Info::remove_id3v1() +{ +char temp[3] = {0, 0, 0}; +DWORD x; +int err = 0; +HANDLE hFile = CreateFile(file, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); +if (hFile == INVALID_HANDLE_VALUE) +{ + return 0; //1; +} +SetFilePointer(hFile, -128, NULL, FILE_END); +ReadFile(hFile, temp, 3, &x, NULL); +if (!memcmp(temp, "TAG", 3)) +{ + SetFilePointer(hFile, -128, NULL, FILE_END); + if (!SetEndOfFile(hFile)) + { + err = 1; + } +} +CloseHandle(hFile); +if (!err) fbuf[0] = 0; +return err; + +return 0; +} +*/ + + + + +/* +da_tag.SetUnsync(false); +da_tag.SetExtendedHeader(true); +da_tag.SetCompression(false); +da_tag.SetPadding(true); +*/ + + +#endif + + +int FindAverageAACBitrate(unsigned __int8 *data, int sizeBytes) +{ + AACFrame aacFrame; + aacFrame.ReadBuffer(data); + + if (aacFrame.OK()) + { + int aac_frame_length = aacFrame.frameLength; + int no_rawdb = aacFrame.numDataBlocks; + + int fc_tot = aac_frame_length; + int fc_cnt = no_rawdb + 1; + + unsigned char *aa = data + aac_frame_length; + int tt = sizeBytes - aac_frame_length; + while (tt >= 8) + { + AACFrame nextFrame; + nextFrame.ReadBuffer(aa); + if (!nextFrame.OK()) break; // error + int fcaac_frame_length = nextFrame.frameLength; + int fcno_rawdb = nextFrame.numDataBlocks; + + fc_cnt += fcno_rawdb + 1; + fc_tot += fcaac_frame_length; + + aa += fcaac_frame_length; + tt -= fcaac_frame_length; + } + + + int avg_framesize = fc_tot / (fc_cnt ? fc_cnt : 1); + return fixAACCBRbitrate(MulDiv(avg_framesize * 8, aacFrame.GetSampleRate(), 1024 * 1000)); + } + return 0; +} + +static bool ScanForFrame(CGioFile *file, int *bytesRead) +{ + unsigned char buffer[512] = {0}; /* don't want to read too much, since most MP3's start at 0 */ + int buflen = 0; + + int checked=0; + if (file->Peek(buffer, sizeof(buffer), &buflen) != NErr_Success) + return false; + unsigned char *b = buffer; + while (buflen >= 4) + { + MPEGFrame frame1; + frame1.ReadBuffer(b); + if (frame1.IsSync()) + { + if (checked) + file->Read(buffer, checked, &buflen); + *bytesRead=checked; + return true; + } + + checked++; + buflen--; + b++; + } + if (checked) + file->Read(buffer, checked, &buflen); + *bytesRead=checked; + return false; +} + +static bool mp3sync(CGioFile *file) +{ + unsigned char buffer[1448 + 4] = {0}; /* large enough for one max-size frame and the header of the second */ + int buflen = 0; + + static const unsigned long gdwHeaderSyncMask = 0xfffe0c00L; + unsigned long ulHdr1=0; + unsigned long ulHdr2=0; + + int bytesChecked=0; + while (bytesChecked<32768) + { + int bytesRead=0; + if (ScanForFrame(file, &bytesRead)) + { + if (file->Peek(buffer, sizeof(buffer), &buflen) != NErr_Success) + return false; + + if (buflen >= 4) + { + MPEGFrame frame1; + frame1.ReadBuffer(buffer); + if (frame1.IsSync()) + { + ulHdr1 = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; + int framelength= frame1.FrameSize(); + if (buflen >= (framelength+4)) + { + unsigned char *b = buffer + framelength; + buflen -= frame1.FrameSize(); + MPEGFrame frame2; + frame2.ReadBuffer(b); + ulHdr2 = (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]; + if (!((ulHdr1 ^ ulHdr2) & gdwHeaderSyncMask) && frame2.IsSync()) + return true; + else + { + + } + } + } + file->Read(buffer, 1, &buflen); + bytesChecked++; + } + } + else if (file->EndOf()) + return 0; + + bytesChecked+=bytesRead; + } + return false; +} + +static const wchar_t *GetMPEGVersionString(int mpegVersion) +{ + switch (mpegVersion) + { + case MPEGFrame::MPEG1: + return L"MPEG-1"; + case MPEGFrame::MPEG2: + return L"MPEG-2"; + case MPEGFrame::MPEG2_5: + return L"MPEG-2.5"; + default: + static wchar_t temp[64]; + return WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,temp,64); + } +} + +static const wchar_t *GetEmphasisString(int emphasis) +{ + static wchar_t tempE[32]; + switch (emphasis) + { + case 0: + return WASABI_API_LNGSTRINGW_BUF(IDS_NONE,tempE,32); + case 1: + return WASABI_API_LNGSTRINGW_BUF(IDS_50_15_MICROSEC,tempE,32); + case 2: + return WASABI_API_LNGSTRINGW_BUF(IDS_INVALID,tempE,32); + case 3: + return L"CITT j.17"; + default: + return WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,tempE,32); + } +} + +static const wchar_t *GetChannelModeString(int channelMode) +{ + static wchar_t tempM[32]; + switch (channelMode) + { + case 0: + return WASABI_API_LNGSTRINGW_BUF(IDS_STEREO,tempM,32); + case 1: + return WASABI_API_LNGSTRINGW_BUF(IDS_JOINT_STEREO,tempM,32); + case 2: + return WASABI_API_LNGSTRINGW_BUF(IDS_2_CHANNEL,tempM,32); + case 3: + return WASABI_API_LNGSTRINGW_BUF(IDS_MONO,tempM,32); + default: + return WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,tempM,32); + } +} +#define INFO_READ_SIZE 32768 +void GetFileDescription(const wchar_t *file, CGioFile &_file, wchar_t *data, size_t datalen) +{ + int hdroffs = 0; + size_t size = datalen; + wchar_t *mt = data; + wchar_t *ext = PathFindExtension(file); + + wchar_t langbuf[256] = {0}; + int flen = _file.GetContentLength(); + StringCchPrintfExW(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_PAYLOAD_SIZE, langbuf, 256), _file.GetContentLength()); + if (!_wcsicmp(ext, L".aac") || !_wcsicmp(ext, L".vlb")) + { + #if 0 // TODO! + if (_wcsicmp(ext, L".vlb")) // aacplus can't do VLB + { + if (aacPlus) + { + aacPlus->EasyOpen(AACPLUSDEC_OUTPUTFORMAT_INT16_HOSTENDIAN, 6); + AACPLUSDEC_EXPERTSETTINGS *pConf = aacPlus->GetDecoderSettingsHandle(); + pConf->bEnableOutputLimiter = 1; + pConf->bDoUpsampling = 1; + aacPlus->SetDecoderSettings(); + + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW_BUF(IDS_FORMAT_AAC, langbuf, 256), &mt, &size, 0); + + char buffer[INFO_READ_SIZE] = {0}; + int inputRead; + _file.Read(buffer, INFO_READ_SIZE, &inputRead); + AACPLUSDEC_BITSTREAMBUFFERINFO bitbufInfo = { inputRead, 0, 0}; + aacPlus->StreamFeed((unsigned char *)buffer, &bitbufInfo); + + unsigned char tempBuf[65536] = {0}; // grr, can't we find a better way to do this? + AACPLUSDEC_AUDIOBUFFERINFO audioBufInfo = {65536, 0, 0}; + aacPlus->StreamDecode(tempBuf, &audioBufInfo, 0, 0); + audioBufInfo.nBytesBufferSizeIn -= audioBufInfo.nBytesWrittenOut; + aacPlus->StreamDecode(tempBuf + audioBufInfo.nBytesWrittenOut, &audioBufInfo, 0, 0); + AACPLUSDEC_STREAMPROPERTIES *streamProperties = aacPlus->GetStreamPropertiesHandle(); + if (streamProperties->nDecodingState == AACPLUSDEC_DECODINGSTATE_STREAMVERIFIED) + { + AACPLUSDEC_PROGRAMPROPERTIES *currentProgram = &(streamProperties->programProperties[streamProperties->nCurrentProgram]); + + switch (currentProgram->nStreamType) + { + case AACPLUSDEC_MPEG2_PROFILE_AACMAIN: + StringCchCatEx(mt, size, L"\r\nMPEG-2 AAC", &mt, &size, 0); + break; + case AACPLUSDEC_MPEG2_PROFILE_AACLC: + if (currentProgram->bProgramSbrEnabled) + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW_BUF(IDS_MPEG2_HE_AAC_IS, langbuf, 256), &mt, &size, 0); + else + StringCchCatEx(mt, size, L"\r\nMPEG-2 AAC LC", &mt, &size, 0); + break; + case AACPLUSDEC_MPEG4_AOT_AACMAIN: + StringCchCatEx(mt, size, L"\r\nMPEG-4 AAC", &mt, &size, 0); + break; + case AACPLUSDEC_MPEG4_AOT_AACLC: + if (currentProgram->bProgramSbrEnabled) + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW_BUF(IDS_MPEG4_HE_AAC_IS, langbuf, 256), &mt, &size, 0); + else + StringCchCatEx(mt, size, L"\r\nMPEG-4 AAC LC", &mt, &size, 0); + break; + case AACPLUSDEC_MPEG4_AOT_SBR: + StringCchCatEx(mt, size, L"\r\nMPEG-4 HE-AAC", &mt, &size, 0); + break; + } + + if (currentProgram->nAacSamplingRate != currentProgram->nOutputSamplingRate) + StringCchPrintfEx(mt, size, &mt, &size, 0, + WASABI_API_LNGSTRINGW(IDS_SAMPLE_RATE_OUTPUT), + currentProgram->nAacSamplingRate, currentProgram->nOutputSamplingRate); + else + StringCchPrintfEx(mt, size, &mt, &size, 0, + WASABI_API_LNGSTRINGW(IDS_SAMPLE_RATE), + currentProgram->nAacSamplingRate); + int srate = currentProgram->nOutputSamplingRate; + + if (currentProgram->bProgramSbrEnabled) + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_SBR_PRESENT), &mt, &size, 0); + else + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_SBR_NOT_PRESENT), &mt, &size, 0); + + if (currentProgram->nAacChannels != currentProgram->nOutputChannels) + StringCchPrintfEx(mt, size, &mt, &size, 0, + WASABI_API_LNGSTRINGW(IDS_CHANNELS_OUTPUT), + currentProgram->nAacChannels, currentProgram->nOutputChannels); + else + StringCchPrintfEx(mt, size, &mt, &size, 0, + WASABI_API_LNGSTRINGW(IDS_CHANNELS), + currentProgram->nAacChannels); + switch (currentProgram->nChannelMode) + { + case AACPLUSDEC_CHANNELMODE_MONO: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_MONO), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_STEREO: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_STEREO), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_PARAMETRIC_STEREO: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_PARAMETRIC_STEREO), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_DUAL_CHANNEL: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_DUAL_CHANNEL), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_4_CHANNEL_2CPE: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_4_CHANNEL_2_CPE), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_4_CHANNEL_MPEG: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_4_CHANNEL_MPEG), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_5_CHANNEL: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_5_CHANNEL), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_5_1_CHANNEL: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_5_1), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_6_1_CHANNEL: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_6_1), &mt, &size, 0); + break; + case AACPLUSDEC_CHANNELMODE_7_1_CHANNEL: + StringCchCatEx(mt, size, WASABI_API_LNGSTRINGW(IDS_MODE_7_1), &mt, &size, 0); + break; + } + + if (streamProperties->nBitrate) + { + StringCchPrintfEx(mt, size, &mt, &size, 0, + WASABI_API_LNGSTRINGW(IDS_BITRATE), + streamProperties->nBitrate); + } + else + { + int avg_bitrate = FindAverageAACBitrate((unsigned char *)buffer, inputRead); + StringCchPrintfEx(mt, size, &mt, &size, 0, + WASABI_API_LNGSTRINGW(IDS_AVERAGE_BITRATE), + avg_bitrate); + } + } + } + } + + if (!aacPlus) + #endif + { + char buffer[INFO_READ_SIZE] = {0}; + int inputRead = 0; + _file.Read(buffer, INFO_READ_SIZE, &inputRead); + + StringCchCopyEx(mt, size, WASABI_API_LNGSTRINGW(IDS_FORMAT_AAC), &mt, &size, 0); + unsigned char *a = (unsigned char *)buffer; + while (inputRead-- >= 8) + { + AACFrame aacFrame; + aacFrame.ReadBuffer(a); + + if (aacFrame.OK()) + { + int aac_frame_length = aacFrame.frameLength; + int no_rawdb = aacFrame.numDataBlocks; + + /*size_t size = 1024; + char *mt = mpeg_description;*/ + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW(IDS_HEADER_FOUND_AT_X_BYTES), hdroffs); + StringCchPrintfEx(mt, size, &mt, &size, 0, L"\r\nMPEG-%d AAC", aacFrame.GetMPEGVersion()); + + int fc_tot = aac_frame_length; + int fc_cnt = no_rawdb + 1; + + unsigned char *aa = a + aac_frame_length; + int tt = inputRead - aac_frame_length; + while (tt >= 8) + { + AACFrame nextFrame; + nextFrame.ReadBuffer(aa); + if (!nextFrame.OK()) break; // error + int fcaac_frame_length = nextFrame.frameLength; + int fcno_rawdb = nextFrame.numDataBlocks; + + fc_cnt += fcno_rawdb + 1; + fc_tot += fcaac_frame_length; + + aa += fcaac_frame_length; + tt -= fcaac_frame_length; + } + + { + int avg_framesize = fc_tot / (fc_cnt ? fc_cnt : 1); + int srate = aacFrame.GetSampleRate(); + int avg_bitrate = fixAACCBRbitrate(MulDiv(avg_framesize * 8, srate, 1024 * 1000)); + + int len_s = MulDiv(flen, 1024, avg_framesize * srate); + + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW(IDS_LENGTH_X_SECONDS), len_s); + StringCchPrintfEx(mt, size, &mt, &size, 0, L"\r\nCBR %d kbps", avg_bitrate); + } + + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW(IDS_PROFILE), aacFrame.GetProfileName()); + StringCchPrintfEx(mt, size, &mt, &size, 0, L"\r\n%dHz %s", aacFrame.GetSampleRate(), aacFrame.GetChannelConfigurationName()); + StringCchPrintfEx(mt, size, &mt, &size, 0, L"\r\nCRC: %s", WASABI_API_LNGSTRINGW((aacFrame.protection == 0 ? IDS_YES : IDS_NO))); + break; + } + + a++; + hdroffs++; + } + } + } + else + { + unsigned char mp3syncbuf[INFO_READ_SIZE] = {0}; + int inputRead = 0; + + DWORD start = _file.GetCurrentPosition(); + // find position of first sync + if (!mp3sync(&_file)) + return; + int syncposition = _file.GetCurrentPosition()-start; + + // advance to first sync + _file.Peek(mp3syncbuf, INFO_READ_SIZE, &inputRead); + + unsigned int padding = 0; + unsigned int encoderDelay = 0; + + MPEGFrame frame; + frame.ReadBuffer(mp3syncbuf); + + + int framelen = frame.FrameSize(); + + //const CMp3StreamInfo *info = decoder.GetStreamInfo(); + //const CMpegHeader *header = decoder.m_Mbs.GetHdr(); + + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_HEADER_FOUND_AT_X_BYTES, langbuf, 256), _file.GetHeaderOffset() + syncposition); + if (!padding) padding = _file.postpad; + if (!encoderDelay) encoderDelay = _file.prepad; + + if (padding || encoderDelay) + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_ENC_DELAY_ZERO_PADDING, langbuf, 256), encoderDelay, padding); + + int is_vbr_lens = _file.m_vbr_ms; + int iLen = (is_vbr_lens ? is_vbr_lens/1000 : ((flen * 8) / frame.GetBitrate())); + if(iLen > 0) + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_LENGTH_X_SECONDS, langbuf, 256), iLen); + else + { + float fLen = (is_vbr_lens ? is_vbr_lens/1000.0f : ((flen * 8.0f) / frame.GetBitrate())); + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_LENGTH_X_PART_SECONDS, langbuf, 256), fLen); + } + + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_S_LAYER_X, langbuf, 256), GetMPEGVersionString(frame.mpegVersion), frame.GetLayer()); + + int frames = _file.m_vbr_frames; + + int is_vbr = _file.m_vbr_flag || _file.m_vbr_hdr; + if (!is_vbr || _file.encodingMethod == ENCODING_METHOD_CBR) + { + if (frames) + { + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_X_KBIT, langbuf, 256), frame.GetBitrate() / 1000, frames); + } + else + { + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_X_KBIT_APPROX, langbuf, 256), frame.GetBitrate() / 1000, MulDiv(flen, 8, framelen)); + } + } + else if (is_vbr && _file.encodingMethod == ENCODING_METHOD_ABR) + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_X_KBIT_ABR, langbuf, 256), _file.GetAvgVBRBitrate(), frames); + else + { + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_X_KBIT_VBR, langbuf, 256), _file.GetAvgVBRBitrate(), _file.m_vbr_hdr?L"I":L"", frames); + } + + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_X_HZ_S, langbuf, 256), frame.GetSampleRate(), GetChannelModeString(frame.channelMode)); + StringCchPrintfEx(mt, size, &mt, &size, 0, L"\r\nCRC: %s", WASABI_API_LNGSTRINGW_BUF((frame.CRC ? IDS_YES : IDS_NO), langbuf, 256)); + wchar_t tmp[16] = {0}; + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_COPYRIGHTED, langbuf, 256), WASABI_API_LNGSTRINGW_BUF((frame.copyright ? IDS_YES : IDS_NO),tmp,16)); + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_ORIGINAL, langbuf, 256), WASABI_API_LNGSTRINGW_BUF((frame.original ? IDS_YES : IDS_NO),tmp,16)); + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_EMPHASIS, langbuf, 256), GetEmphasisString(frame.emphasis)); + + if (_file.m_vbr_frame_len && !_file.lengthVerified) + StringCchPrintfEx(mt, size, &mt, &size, 0, WASABI_API_LNGSTRINGW_BUF(IDS_MP3_HAS_BEEN_MODIFIED_NOT_ALL_MAY_BE_CORRECT, langbuf, 256)); + } +} + +void GetAudioInfo(const wchar_t *filename, CGioFile *file, int *len, int *channels, int *bitrate, int *vbr, int *sr) +{ + *bitrate=0; + *len=0; + *vbr=0; + *channels=0; + if (file) + { + wchar_t *ext = PathFindExtension(filename); + if (!_wcsicmp(ext, L".aac") || !_wcsicmp(ext, L".vlb")) + { + unsigned char t[INFO_READ_SIZE*2] = {0}; + unsigned char *a = t; + int n = 0; + + if (file->Read(t, sizeof(t), &n) != NErr_Success) + return; + + while (n-- >= 8) + { + AACFrame aacFrame; + aacFrame.ReadBuffer(a); + + if (aacFrame.OK()) + { + int aac_frame_length = aacFrame.frameLength; + + int fc_tot = aac_frame_length; + int fc_cnt = aacFrame.numDataBlocks + 1; + + unsigned char *aa = a + aac_frame_length; + int tt = n - aac_frame_length; + while (tt >= 8 && aac_frame_length) + { + AACFrame nextFrame; + nextFrame.ReadBuffer(aa); + if (!nextFrame.OK()) break; // error + int fcaac_frame_length = nextFrame.frameLength; + int fcno_rawdb = nextFrame.numDataBlocks; + + fc_cnt += fcno_rawdb + 1; + fc_tot += fcaac_frame_length; + + aa += fcaac_frame_length; + tt -= fcaac_frame_length; + } + + int avg_framesize = fc_tot / (fc_cnt ? fc_cnt : 1); + + int br = MulDiv(avg_framesize * 8, aacFrame.GetSampleRate(), 1024); + *len = MulDiv(file->GetContentLength(), 1024*8, br); + *bitrate = fixAACCBRbitrate(br/1000)*1000; + + *sr = aacFrame.GetSampleRate(); + *channels = aacFrame.GetNumChannels(); + + break; + } + a++; + } + } + else + { + if (*bitrate = file->GetAvgVBRBitrate()*1000) + { + *len = file->m_vbr_ms; + *vbr = file->m_vbr_flag || file->m_vbr_hdr; + } + + if (!mp3sync(file)) + return; + + unsigned char t[4] = {0}; + int n = 0; + + if (file->Peek(t, sizeof(t), &n) != NErr_Success) + return; + + MPEGFrame frame; + frame.ReadBuffer(t); + if (frame.IsSync()) + { + if (!*bitrate) + { + *bitrate = frame.GetBitrate(); + *len = MulDiv(file->GetContentLength(), 1000*8, *bitrate); + } + *channels = frame.GetNumChannels(); + *sr = frame.GetSampleRate(); + } + } + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/MP3Info.h b/Src/Plugins/Input/in_mp3/MP3Info.h new file mode 100644 index 00000000..b161feeb --- /dev/null +++ b/Src/Plugins/Input/in_mp3/MP3Info.h @@ -0,0 +1,47 @@ +#ifndef NULLSOFT_IN_MP3_MP3_INFO_H +#define NULLSOFT_IN_MP3_MP3_INFO_H + +#include "Metadata.h" +#include <windows.h> + +class MP3Info +{ +public: + MP3Info(const wchar_t *fn); + + char mpeg_description[1024]; + + bool isOld(); + + void get_file_info(); + int write_id3v1(); + int remove_id3v1(); + void display_id3v1(HWND hwndDlg); + void get_id3v1_values(HWND hwndDlg); + void display_id3v2(HWND hwndDlg); + void get_id3v2_values(HWND hwndDlg); + void write_id3v2(HWND hwndDlg); + void do_enable_id3v1(HWND hwndDlg, int en); + void do_enable_id3v2(HWND hwndDlg, int en); + + BOOL CALLBACK id3Proc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam); + + int setExtendedFileInfoW(const char *data, wchar_t *val); + int writeExtendedFileInfo(); + + bool IsMe(const wchar_t *fn) + { + return !lstrcmpW(file, fn); + } +protected: + // Keep track of file timestamp for file system change notification handling + FILETIME last_write_time; + +private: + void SetField(const wchar_t *value, wchar_t *&v2, char *v1, size_t v1size); + Metadata metadata; + wchar_t file[MAX_PATH]; +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/Metadata.cpp b/Src/Plugins/Input/in_mp3/Metadata.cpp new file mode 100644 index 00000000..89e87697 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/Metadata.cpp @@ -0,0 +1,616 @@ +#include "Metadata.h" +#include "main.h" +#include "api__in_mp3.h" +#include "LAMEInfo.h" +#include "AACFrame.h" +#include "config.h" +#include "LAMEInfo.h" +#include <shlwapi.h> +#include <assert.h> +#include <foundation/error.h> +#include <strsafe.h> + +#define INFO_READ_SIZE 32768 + +Metadata::Metadata( CGioFile *_file, const wchar_t *_filename ) +{ + if ( !PathIsURL( _filename ) ) + filename = _wcsdup( _filename ); + + ReadTags( _file ); + if ( bitrate = _file->GetAvgVBRBitrate() * 1000 ) + { + length_ms = _file->m_vbr_ms; + vbr = _file->m_vbr_flag || _file->m_vbr_hdr; + } +} + +void GetFileDescription(const wchar_t *file, CGioFile &_file, wchar_t *data, size_t datalen); +void GetAudioInfo(const wchar_t *filename, CGioFile *file, int *len, int *channels, int *bitrate, int *vbr, int *sr); + +int Metadata::Open(const wchar_t *_filename) +{ + if ( filename && *filename ) + free( filename ); + + filename = _wcsdup(_filename); + if (file.Open(filename, INFO_READ_SIZE/1024) != NErr_Success) + return 1; + + GetAudioInfo(filename, &file, &length_ms, &channels, &bitrate, &vbr, &sampleRate); + ReadTags(&file); + file.Close(); + return METADATA_SUCCESS; +} + +Metadata::~Metadata() +{ + if (filename) + { + free(filename); + filename=0; + } +} + +void Metadata::ReadTags(CGioFile *_file) +{ + // Process ID3v1 + if (config_parse_id3v1) + { + void *id3v1_data = _file->GetID3v1(); + if (id3v1_data) + id3v1.Decode(id3v1_data); + } + + if (config_parse_id3v2) + { + uint32_t len = 0; + void *id3v2_data = _file->GetID3v2(&len); + if (id3v2_data) + id3v2.Decode(id3v2_data, len); + } + + if (config_parse_lyrics3) + { + uint32_t len = 0; + void *lyrics3_data = _file->GetLyrics3(&len); + if (lyrics3_data) + lyrics3.Decode(lyrics3_data, len); + } + + if (config_parse_apev2) + { + uint32_t len = 0; + void *apev2_data = _file->GetAPEv2(&len); + if (apev2_data) + apev2.Decode(apev2_data, len); + } +} + +static int ID3Write(const wchar_t *filename, HANDLE infile, DWORD offset, void *data, DWORD len) +{ + wchar_t tempFile[MAX_PATH] = {0}; + StringCchCopyW(tempFile, MAX_PATH, filename); + PathRemoveExtension(tempFile); + StringCchCatW(tempFile, MAX_PATH, L".tmp"); + + // check to make sure the filename was actually different! + // benski> TODO: we should just try to mangle the filename more rather than totally bail out + if (!_wcsicmp(tempFile, filename)) + return SAVE_ERROR_CANT_OPEN_TEMPFILE; + + // TODO: overlapped I/O + HANDLE outfile = CreateFile(tempFile, GENERIC_WRITE|GENERIC_READ, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, 0); + if (outfile != INVALID_HANDLE_VALUE) + { + DWORD written=0; + if (data && len) + WriteFile(outfile, data, len, &written, NULL); + SetFilePointer(infile, offset, 0, FILE_BEGIN); + + DWORD read=0; + do + { + char data[4096] = {0}; + written = read = 0; + ReadFile(infile, data, 4096, &read, NULL); + if (read) WriteFile(outfile, data, read, &written, NULL); + } + while (read != 0); + CloseHandle(outfile); + CloseHandle(infile); + if (!MoveFile(tempFile, filename)) + { + if (!CopyFile(tempFile, filename, FALSE)) + { + DeleteFile(tempFile); + return SAVE_ERROR_ERROR_OVERWRITING; + } + DeleteFile(tempFile); + } + return SAVE_SUCCESS; + } + return SAVE_ERROR_CANT_OPEN_TEMPFILE; + +} + +bool Metadata::IsDirty() +{ + return id3v1.IsDirty() || id3v2.IsDirty() || lyrics3.IsDirty() || apev2.IsDirty(); +} + +int Metadata::Save() +{ + if (!IsDirty()) + return SAVE_SUCCESS; + + int err=SAVE_SUCCESS; + if (GetFileAttributes(filename)&FILE_ATTRIBUTE_READONLY) + return SAVE_ERROR_READONLY; + + HANDLE metadataFile = CreateFile(filename, GENERIC_WRITE|GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); + if (metadataFile == INVALID_HANDLE_VALUE) + return SAVE_ERROR_OPENING_FILE; + + if (file.Open(filename, INFO_READ_SIZE/1024) != NErr_Success) + { + CloseHandle(metadataFile); + return SAVE_ERROR_OPENING_FILE; + } + + bool strippedID3v1=false; // this flag will get set to true when we remove ID3v1 as a side effect of removing APEv2 or Lyrics3 (or ID3v2.4 end-tag if/when we implement) + bool strippedLyrics3=false; + + /* Strip APEv2 */ + if (config_parse_apev2 && config_write_apev2 && apev2.IsDirty()) + { + uint32_t len = 0; + void *apev2_data = file.GetAPEv2(&len); + if (apev2_data) + { + uint32_t lyrics3_len = 0; + void *lyrics3_data = file.GetLyrics3(&lyrics3_len); + if (lyrics3_data) + SetFilePointer(metadataFile, -(LONG)(len + 15 + lyrics3_len + (file.GetID3v1()?128:0)), NULL, FILE_END); + else + SetFilePointer(metadataFile, -(LONG)(len + (file.GetID3v1()?128:0)), NULL, FILE_END); + SetEndOfFile(metadataFile); + strippedLyrics3=true; + strippedID3v1=true; + } + } + + /* Strip Lyrics3 tag */ + if (!strippedLyrics3 && config_parse_lyrics3 && lyrics3.IsDirty()) + { + uint32_t len = 0; + void *lyrics3_data = file.GetLyrics3(&len); + if (lyrics3_data) + { + SetFilePointer(metadataFile, -(LONG)(len + 15 + (file.GetID3v1()?128:0)), NULL, FILE_END); + SetEndOfFile(metadataFile); + strippedID3v1=true; + } + } + + /* Strip ID3v1(.1) tag */ + if (!strippedID3v1 /* if we stripped lyrics3 tag, then we ended up stripping id3v1 also */ + && config_parse_id3v1 && config_write_id3v1 && id3v1.IsDirty()) + { + if (file.GetID3v1()) // see if we have ID3v1 + { + SetFilePointer(metadataFile, -128, NULL, FILE_END); + SetEndOfFile(metadataFile); + } + } + + /* Write APEv2 */ + if (config_parse_apev2 && config_write_apev2 && apev2.IsDirty() && apev2.HasData()) + { + switch(config_apev2_header) + { + case ADD_HEADER: + apev2.SetFlags(APEv2::FLAG_HEADER_HAS_HEADER, APEv2::FLAG_HEADER_HAS_HEADER); + break; + case REMOVE_HEADER: + apev2.SetFlags(0, APEv2::FLAG_HEADER_HAS_HEADER); + break; + } + + size_t apev2_len = apev2.EncodeSize(); + void *apev2_data = malloc(apev2_len); + if (apev2_data && apev2.Encode(apev2_data, apev2_len) == APEv2::APEV2_SUCCESS) + { + SetFilePointer(metadataFile, 0, NULL, FILE_END); + DWORD bytesWritten=0; + WriteFile(metadataFile, apev2_data, (DWORD)apev2_len, &bytesWritten, 0); + free(apev2_data); + apev2_data = 0; + if (bytesWritten != apev2_len) + { + err=SAVE_APEV2_WRITE_ERROR; + goto fail; + } + } + else + { + free(apev2_data); + apev2_data = 0; + err=SAVE_APEV2_WRITE_ERROR; + goto fail; + } + } + + /* Write Lyrics3 */ + if (strippedLyrics3) /* if we need to rewrite it because we stripped it (e.g. removing an APEv2 tag)*/ + { + /* since we don't modify lyrics3 (yet) we'll just rewrite the original binary data */ + uint32_t len = 0; + void *lyrics3_data = file.GetLyrics3(&len); + if (lyrics3_data) + { + SetFilePointer(metadataFile, 0, NULL, FILE_END); + DWORD bytesWritten=0; + WriteFile(metadataFile, lyrics3_data, len, &bytesWritten, NULL); + if (bytesWritten != len) + { + err=SAVE_LYRICS3_WRITE_ERROR; + goto fail; + } + char temp[7] = {0}; + StringCchPrintfA(temp, 7, "%06u", len); + bytesWritten = 0; + WriteFile(metadataFile, temp, 6, &bytesWritten, NULL); + if (bytesWritten != 6) + { + err=SAVE_LYRICS3_WRITE_ERROR; + goto fail; + } + bytesWritten = 0; + WriteFile(metadataFile, "LYRICS200", 9, &bytesWritten, NULL); + if (bytesWritten != 9) + { + err=SAVE_LYRICS3_WRITE_ERROR; + goto fail; + } + } + } + + /* Write ID3v1 */ + if (config_parse_id3v1 && config_write_id3v1 && id3v1.IsDirty()) + { + uint8_t id3v1_data[128] = {0}; + if (id3v1.Encode(id3v1_data) == METADATA_SUCCESS) + { + SetFilePointer(metadataFile, 0, NULL, FILE_END); + DWORD bytesWritten=0; + WriteFile(metadataFile, id3v1_data, 128, &bytesWritten, NULL); + if (bytesWritten != 128) + { + err=SAVE_ID3V1_WRITE_ERROR; + goto fail; + } + } + } + else if (strippedID3v1) + { + /** if we stripped lyrics3 or apev2 but didn't modify id3v1 (or are configured not to use it), + ** we need to rewrite it back to the original data + **/ + void *id3v1_data=file.GetID3v1(); + if (id3v1_data) + { + SetFilePointer(metadataFile, 0, NULL, FILE_END); + DWORD bytesWritten=0; + WriteFile(metadataFile, id3v1_data, 128, &bytesWritten, NULL); + if (bytesWritten != 128) + { + err=SAVE_ID3V1_WRITE_ERROR; + goto fail; + } + } + } + + /* Write ID3v2 */ + if (config_parse_id3v2 && config_write_id3v2 && id3v2.IsDirty()) + { + uint32_t oldlen=0; + void *old_id3v2_data = file.GetID3v2(&oldlen); + id3v2.id3v2.SetPadding(false); // turn off padding to see if we can get away with non re-writing the file + uint32_t newlen = id3v2.EncodeSize(); + if (old_id3v2_data && !newlen) // there's an old tag, but no new tag + { + err = ID3Write(filename, metadataFile, oldlen, 0, 0); + if (err == SAVE_SUCCESS) + metadataFile = INVALID_HANDLE_VALUE; // ID3Write returns true if it closed the handle + else + goto fail; + } + else if (!old_id3v2_data && !newlen) // no old tag, no new tag.. easy :) + { + } + else + { + id3v2.id3v2.SetPadding(true); + if (newlen <= oldlen) // if we can fit in the old tag + { + if (oldlen != newlen) + id3v2.id3v2.ForcePading(oldlen-newlen); // pad out the rest of the tag + else + id3v2.id3v2.SetPadding(false); + assert(id3v2.EncodeSize() == oldlen); + newlen = oldlen; + uint8_t *new_id3v2_data = (uint8_t *)calloc(newlen, sizeof(uint8_t)); + if (new_id3v2_data && id3v2.Encode(new_id3v2_data, newlen) == METADATA_SUCCESS) + { + // TODO: deal with files with multiple starting id3v2 tags + SetFilePointer(metadataFile, 0, NULL, FILE_BEGIN); + DWORD bytesWritten=0; + WriteFile(metadataFile, new_id3v2_data, newlen, &bytesWritten, NULL); + free(new_id3v2_data); + new_id3v2_data = 0; + if (bytesWritten != newlen) + { + err = SAVE_ID3V2_WRITE_ERROR; + goto fail; + } + } + else + { + free(new_id3v2_data); + new_id3v2_data = 0; + err = SAVE_ID3V2_WRITE_ERROR; + goto fail; + } + } + else // otherwise we have to pad out the start + { + newlen = id3v2.EncodeSize(); + uint8_t *new_id3v2_data = (uint8_t *)calloc(newlen, sizeof(uint8_t)); + if (new_id3v2_data && id3v2.Encode(new_id3v2_data, newlen) == METADATA_SUCCESS) + { + // TODO: deal with files with multiple starting id3v2 tags + SetFilePointer(metadataFile, 0, NULL, FILE_BEGIN); + DWORD bytesWritten=0; + err = ID3Write(filename, metadataFile, oldlen, new_id3v2_data, newlen); + free(new_id3v2_data); + new_id3v2_data = 0; + if (err == SAVE_SUCCESS) + metadataFile = INVALID_HANDLE_VALUE; // ID3Write returns true if it closed the handle + else + goto fail; + } + else + { + free(new_id3v2_data); + new_id3v2_data = 0; + err = SAVE_ID3V2_WRITE_ERROR; + goto fail; + } + } + } + } + +fail: + file.Close(); + if (metadataFile != INVALID_HANDLE_VALUE) + CloseHandle(metadataFile); + return err; +} + +int Metadata::GetExtendedData(const char *tag, wchar_t *data, int dataLen) +{ + int understood=0; + switch (id3v2.GetString(tag, data, dataLen)) + { + case -1: + data[0]=0; + understood=1; + break; + + case 1: + return 1; + } + + switch (apev2.GetString(tag, data, dataLen)) + { + case -1: + data[0]=0; + understood=1; + break; + + case 1: + return 1; + } + + switch (lyrics3.GetString(tag, data, dataLen)) + { + case -1: + data[0]=0; + understood=1; + break; + + case 1: + return 1; + } + + switch (id3v1.GetString(tag, data, dataLen)) + { + case -1: + data[0]=0; + understood=1; + break; + + case 1: + return 1; + } + + switch (GetString(tag, data, dataLen)) + { + case -1: + data[0]=0; + understood=1; + break; + + case 1: + return 1; + } + + return understood; +} + +int Metadata::SetExtendedData(const char *tag, const wchar_t *data) +{ + int understood=0; + if (config_create_id3v2 || id3v2.HasData()) + understood |= id3v2.SetString(tag, data); + if (config_create_apev2 || apev2.HasData()) + understood |= apev2.SetString(tag, data); + if (config_create_id3v1 || id3v1.HasData()) + understood |= id3v1.SetString(tag, data); + return understood; +} + +int Metadata::GetString(const char *tag, wchar_t *data, int dataLen) +{ + if (!_stricmp(tag, "formatinformation")) + { + data[0]=0; + if (filename) + { + if (file.Open(filename, INFO_READ_SIZE/1024) == NErr_Success) + GetFileDescription(filename, file, data, dataLen); + file.Close(); + } + } + else if (!_stricmp(tag, "length")) + { + StringCchPrintfW(data, dataLen, L"%d", length_ms); + } + else if (!_stricmp(tag, "stereo")) + { + StringCchPrintfW(data, dataLen, L"%d", channels==2); + } + else if (!_stricmp(tag, "vbr")) + { + StringCchPrintfW(data, dataLen, L"%d", vbr); + } + else if (!_stricmp(tag, "bitrate")) + { + StringCchPrintfW(data, dataLen, L"%d", bitrate/1000); + } + else if (!_stricmp(tag, "gain")) + { + StringCchPrintfW(data, dataLen, L"%-+.2f dB", file.GetGain()); + } + else if (!_stricmp(tag, "pregap")) + { + if (file.prepad) + { + StringCchPrintfW(data, dataLen, L"%u", file.prepad); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "postgap")) + { + if (file.prepad) // yes, we check for this because postpad could legitimately be 0 + { + StringCchPrintfW(data, dataLen, L"%u", file.postpad); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "numsamples")) + { + if (file.m_vbr_samples) + { + StringCchPrintfW(data, dataLen, L"%I64u", file.m_vbr_samples); + return 1; + } + return -1; + } + else if (!_stricmp(tag, "endoffset")) + { + if (file.m_vbr_frames) + { + int totalFrames = file.m_vbr_frames; + if (totalFrames > 8) + { + int seekPoint = 0; + // we're using m_vbr_bytes here instead of file.ContentLength(), because we're already trusting the other LAME header info + #define MAX_SIZE_8_FRAMES (1448 * 8) // mp3 frames won't be ever be any bigger than this (320kbps 32000Hz + padding) + if (file.m_vbr_bytes > MAX_SIZE_8_FRAMES) + seekPoint = (int)(file.m_vbr_bytes - MAX_SIZE_8_FRAMES); + else + seekPoint = 0; + + size_t offsets[8] = {0}; + size_t offsetsRead = 0; + size_t offsetPosition = 0; + + unsigned char header[6] = {0}; + MPEGFrame frame; + + if (file.Open(filename, INFO_READ_SIZE/1024) != NErr_Success) + return -1; + + // first we need to sync + while (1) + { + file.SetCurrentPosition(seekPoint, CGioFile::GIO_FILE_BEGIN); + int read = 0; + file.Read(header, 6, &read); + if (read != 6) + break; + frame.ReadBuffer(header); + if (frame.IsSync() && frame.GetLayer() == 3) + { + // make sure this isn't false sync - see if we can get another sync... + int nextPoint = seekPoint + frame.FrameSize(); + file.SetCurrentPosition(nextPoint, CGioFile::GIO_FILE_BEGIN); + file.Read(header, 6, &read); + if (read != 6) // must be EOF + break; + frame.ReadBuffer(header); + if (frame.IsSync() && frame.GetLayer() == 3) + break; + } + seekPoint++; + } + while (1) + { + file.SetCurrentPosition(seekPoint, CGioFile::GIO_FILE_BEGIN); + int read = 0; + file.Read(header, 6, &read); + if (read != 6) + break; + frame.ReadBuffer(header); + if (frame.IsSync() && frame.GetLayer() == 3) + { + offsets[offsetPosition] = seekPoint; + offsetPosition = (offsetPosition + 1) % 8; + offsetsRead++; + seekPoint += frame.FrameSize(); + } + else + break; + } + if (offsetsRead >= 8) + { + StringCchPrintfW(data, dataLen, L"%I32d", offsets[offsetPosition] + file.m_vbr_frame_len); + file.Close(); + return 1; + } + + file.Close(); + } + } + return -1; + } + else + return 0; + return 1; +} + +int fixAACCBRbitrate(int br);
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/Metadata.h b/Src/Plugins/Input/in_mp3/Metadata.h new file mode 100644 index 00000000..f84ce34a --- /dev/null +++ b/Src/Plugins/Input/in_mp3/Metadata.h @@ -0,0 +1,64 @@ +#ifndef NULLSOFT_IN_MP3_METADATA +#define NULLSOFT_IN_MP3_METADATA + +#include "giofile.h" +#include "ID3v1.h" +#include "ID3v2.h" +#include "Lyrics3.h" +#include "apev2.h" + +enum +{ + METADATA_SUCCESS = 0, + SAVE_SUCCESS = 0, + SAVE_ERROR_OPENING_FILE = 1, + SAVE_ID3V1_WRITE_ERROR = 2, + SAVE_ID3V2_WRITE_ERROR = 3, + SAVE_ERROR_READONLY = 4, + SAVE_ERROR_CANT_OPEN_TEMPFILE = 5, + SAVE_ERROR_ERROR_OVERWRITING = 6, + SAVE_LYRICS3_WRITE_ERROR = 7, + SAVE_APEV2_WRITE_ERROR = 8, +}; + + +class Metadata +{ +public: + Metadata() {} + Metadata(CGioFile *_file, const wchar_t *_filename); + ~Metadata(); + + int Open(const wchar_t *filename); + int GetExtendedData(const char *tag, wchar_t *data, int dataLen); + int SetExtendedData(const char *tag, const wchar_t *data); + int Save(); + bool IsMe(const wchar_t *fn) { return filename && !_wcsicmp(filename, fn); } + + void AddRef() { InterlockedIncrement(&refs); } + void Release() { if(!InterlockedDecrement(&refs)) delete this; } + +private: + bool IsDirty(); + void ReadTags(CGioFile *_file); + int GetString(const char *tag, wchar_t *data, int dataLen); + + int sampleRate = 0; + int bitrate = 0; + int vbr = 0; + int channels = 0; + int length_ms = 0; + CGioFile file; + +public: + ID3v1 id3v1; + ID3v2 id3v2; + Lyrics3 lyrics3; + APE apev2; + + wchar_t *filename = 0; +protected: + volatile LONG refs = 1; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/MetadataFactory.cpp b/Src/Plugins/Input/in_mp3/MetadataFactory.cpp new file mode 100644 index 00000000..a95d0450 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/MetadataFactory.cpp @@ -0,0 +1,62 @@ +#include "MetadataFactory.h" +#include "api__in_mp3.h" +#include "WasabiMetadata.h" + +static const char serviceName[] = "MP3 Stream Metadata Provider"; + +FOURCC MetadataFactory::GetServiceType() +{ + return MP3StreamMetadata::getServiceType(); +} + +const char *MetadataFactory::GetServiceName() +{ + return serviceName; +} + +GUID MetadataFactory::GetGUID() +{ + return MP3StreamMetadataGUID; +} + +void *MetadataFactory::GetInterface(int global_lock) +{ + return new MP3StreamMetadata; +} + +int MetadataFactory::SupportNonLockingInterface() +{ + return 1; +} + +int MetadataFactory::ReleaseInterface(void *ifc) +{ + //plugin.service->service_unlock(ifc); + svc_metaTag *metadata = static_cast<svc_metaTag *>(ifc); + MP3StreamMetadata *mp3metadata = static_cast<MP3StreamMetadata *>(metadata); + delete mp3metadata; + return 1; +} + +const char *MetadataFactory::GetTestString() +{ + return 0; +} + +int MetadataFactory::ServiceNotify(int msg, int param1, int param2) +{ + return 1; +} + +#define CBCLASS MetadataFactory +START_DISPATCH; +CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType) +CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName) +CB(WASERVICEFACTORY_GETGUID, GetGUID) +CB(WASERVICEFACTORY_GETINTERFACE, GetInterface) +CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface) +CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface) +CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString) +CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/MetadataFactory.h b/Src/Plugins/Input/in_mp3/MetadataFactory.h new file mode 100644 index 00000000..e25c40e4 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/MetadataFactory.h @@ -0,0 +1,24 @@ +#ifndef NULLSOFT_MP3_METADATAFACTORY_H +#define NULLSOFT_MP3_METADATAFACTORY_H + +#include <api/service/waservicefactory.h> +#include <api/service/services.h> + +class MetadataFactory : public waServiceFactory +{ +public: + FOURCC GetServiceType(); + const char *GetServiceName(); + GUID GetGUID(); + void *GetInterface(int global_lock); + int SupportNonLockingInterface(); + int ReleaseInterface(void *ifc); + const char *GetTestString(); + int ServiceNotify(int msg, int param1, int param2); + +protected: + RECVS_DISPATCH; +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/OFL.cpp b/Src/Plugins/Input/in_mp3/OFL.cpp new file mode 100644 index 00000000..230fa907 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/OFL.cpp @@ -0,0 +1,165 @@ +#include "OFL.h" +#include "foundation/error.h" + +static void crcofl(unsigned short crcPoly, unsigned short crcMask, unsigned long *crc, unsigned char byte) +{ + int i; + for (i=0; i<8; i++) + { + unsigned short flag = (*crc) & crcMask ? 1:0; + flag ^= (byte & 0x80 ? 1 : 0); + (*crc)<<=1; + byte <<= 1; + if(flag) + (*crc) ^= crcPoly; + } +} + +int OFL::GetGaps(size_t *pregap, size_t *postgap) +{ + /* TODO: verify the postgap calculation */ + if (codec_delay >= 529) + { + *pregap = codec_delay; + size_t endcut; + endcut = samples_per_frame - ((total_length + codec_delay) % samples_per_frame); // how many 0 samples had to be added? + *postgap = endcut; + return NErr_Success; + } + return NErr_Empty; +} + + +double OFL::GetLengthSeconds() const +{ + return (double)GetSamples() / (double)sample_rate; +} + +uint64_t OFL::GetSamples() const +{ + return total_length; +} + +uint32_t OFL::GetFrames() const +{ + uint64_t real_samples = (total_length+codec_delay)*samples_per_frame; + return (uint32_t) (real_samples/samples_per_frame); +} + +int OFL::Read(const MPEGFrame &header, const uint8_t *buffer, size_t buffer_len) +{ + if (header.layer != MPEGFrame::Layer3) + return NErr_False; + + sample_rate = header.GetSampleRate(); + samples_per_frame = header.GetSamplesPerFrame(); + + if (header.channelMode == MPEGFrame::Mono) + { + if (header.mpegVersion == MPEGFrame::MPEG1) + { + // 0-9 : main_data_end + int16_t main_data_end = (buffer[0] << 1) | (buffer[1] >> 7); + + // read the 2 part2_3_lengths out so we know how big the main data section is + uint16_t part2_3_length = ((buffer[2] & 0x3F) << 6) | (buffer[3]>>2); // bits 18-30 + part2_3_length += ((buffer[9] & 0x7) << 9) | (buffer[10] << 1) | (buffer[11] >> 7) ; // bits 77-89 + + size_t offset = 17 + (part2_3_length+7)/8; + if (offset+9 < buffer_len && buffer[offset] == 0xb4) + { + unsigned long crc=255; + for (int i=0;i<9;i++) + crcofl(0x0045, 0x0080, &crc, buffer[offset+i]); + + if ((crc & 0xFF) == buffer[offset+9]) + { + total_length = (buffer[offset+3] << 24) | (buffer[offset+4] << 16) | (buffer[offset+5] << 8) | (buffer[offset+6]); + codec_delay = (buffer[offset+1] << 8) | (buffer[offset+2]); + additional_delay= (buffer[offset+7] << 8) | (buffer[offset+8]); + return NErr_Success; + } + } + } + else + { // MPEG2 and 2.5 + // 0-8 : main_data_end + uint16_t main_data_end = buffer[0]; + + // read the 2 part2_3_lengths out so we know how big the main data section is + uint16_t part2_3_length = ((buffer[1] & 0x7F) << 5) | (buffer[2]>>3); // bits 9-21 + + size_t offset = 9 + (part2_3_length+7)/8; + if (offset+9 < buffer_len && buffer[offset] == 0xb4) + { + unsigned long crc=255; + for (int i=0;i<9;i++) + crcofl(0x0045, 0x0080, &crc, buffer[offset+i]); + + if ((crc & 0xFF) == buffer[offset+9]) + { + total_length = (buffer[offset+3] << 24) | (buffer[offset+4] << 16) | (buffer[offset+5] << 8) | (buffer[offset+6]); + codec_delay = (buffer[offset+1] << 8) | (buffer[offset+2]); + additional_delay= (buffer[offset+7] << 8) | (buffer[offset+8]); + return NErr_Success; + } + } + } + } + else + { + if (header.mpegVersion == MPEGFrame::MPEG1) + { + // 0-9 : main_data_end + uint16_t main_data_end = (buffer[0] << 1) | (buffer[1] >> 7); + + // read the 4 part2_3_lengths out so we know how big the main data section is + uint16_t part2_3_length = ((buffer[2] & 0xF) << 8) | buffer[3]; // bits 20-32 + part2_3_length += ((buffer[9] & 0x1) << 11) | (buffer[10] << 3) | (buffer[11] >> 5) ; // bits 79-91 + part2_3_length += ((buffer[17] & 0x3F) << 6) | (buffer[18] >> 2); // bits 138-150 + part2_3_length += ((buffer[24] & 0x7) << 9) | (buffer[25] << 1) | (buffer[26] >> 7); // bits 197-209 + + size_t offset = 32 + (part2_3_length+7)/8; + if (offset+9 < buffer_len && buffer[offset] == 0xb4) + { + unsigned long crc=255; + for (int i=0;i<9;i++) + crcofl(0x0045, 0x0080, &crc, buffer[offset+i]); + + if ((crc & 0xFF) == buffer[offset+9]) + { + total_length = (buffer[offset+3] << 24) | (buffer[offset+4] << 16) | (buffer[offset+5] << 8) | (buffer[offset+6]); + codec_delay = (buffer[offset+1] << 8) | (buffer[offset+2]); + additional_delay= (buffer[offset+7] << 8) | (buffer[offset+8]); + return NErr_Success; + } + } + } + else + { // MPEG2 and 2.5 + // 0-8 : main_data_end + uint16_t main_data_end = buffer[0]; + + // read the 4 part2_3_lengths out so we know how big the main data section is + uint16_t part2_3_length = ((buffer[1] & 0x3F) << 6) | (buffer[2] >> 2); // bits 10-22 + part2_3_length += ((buffer[8] & 0x7) << 9) | (buffer[9] << 1) | (buffer[10] >> 7) ; // bits 69-81 + + size_t offset = 17 + (part2_3_length+7)/8; + if (offset+9 < buffer_len && buffer[offset] == 0xb4) + { + unsigned long crc=255; + for (int i=0;i<9;i++) + crcofl(0x0045, 0x0080, &crc, buffer[offset+i]); + + if ((crc & 0xFF) == buffer[offset+9]) + { + total_length = (buffer[offset+3] << 24) | (buffer[offset+4] << 16) | (buffer[offset+5] << 8) | (buffer[offset+6]); + codec_delay = (buffer[offset+1] << 8) | (buffer[offset+2]); + additional_delay= (buffer[offset+7] << 8) | (buffer[offset+8]); + return NErr_Success; + } + } + } + } + return NErr_False; +} diff --git a/Src/Plugins/Input/in_mp3/OFL.h b/Src/Plugins/Input/in_mp3/OFL.h new file mode 100644 index 00000000..11e787bf --- /dev/null +++ b/Src/Plugins/Input/in_mp3/OFL.h @@ -0,0 +1,21 @@ +#pragma once +#include "LAMEInfo.h" +#include <bfc/platform/types.h> +class OFL +{ +public: + int Read(const MPEGFrame &header, const uint8_t *buffer, size_t buffer_len); + double GetLengthSeconds() const; + uint64_t GetSamples() const; + uint32_t GetFrames() const; + int GetGaps(size_t *pregap, size_t *postgap); + +private: + int samples_per_frame; + uint32_t total_length; + uint16_t codec_delay; + uint16_t additional_delay; + + unsigned int sample_rate; +}; + diff --git a/Src/Plugins/Input/in_mp3/RawMediaReader.cpp b/Src/Plugins/Input/in_mp3/RawMediaReader.cpp new file mode 100644 index 00000000..5a532639 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/RawMediaReader.cpp @@ -0,0 +1,84 @@ +#include "RawMediaReader.h" +#include <limits.h> + +bool IsMyExtension(const wchar_t *filename); + +int RawMediaReaderService::CreateRawMediaReader(const wchar_t *filename, ifc_raw_media_reader **out_reader) +{ + if (IsMyExtension(filename)) + { + CGioFile *file = new CGioFile(); + if (!file) + return NErr_OutOfMemory; + + if (file->Open(filename, 0) != NErr_Success) + { + delete file; + return NErr_FileNotFound; + } + + RawMediaReader *reader = new RawMediaReader(file); + if (!reader) + { + file->Close(); + delete file; + return NErr_OutOfMemory; + } + + *out_reader = reader; + return NErr_Success; + } + else + { + return NErr_False; + } +} + +#define CBCLASS RawMediaReaderService +START_DISPATCH; +CB(CREATERAWMEDIAREADER, CreateRawMediaReader); +END_DISPATCH; +#undef CBCLASS + +RawMediaReader::RawMediaReader(CGioFile *file) : file(file) +{} + +int RawMediaReader::Read( void *buffer, size_t buffer_size, size_t *bytes_read ) +{ + if ( buffer_size > INT_MAX ) + return NErr_BadParameter; + + int file_bytes_read = 0; + int ret = file->Read( buffer, (int)buffer_size, &file_bytes_read ); + + if ( ret == NErr_Success ) + { + *bytes_read = (size_t)file_bytes_read; + if ( !file_bytes_read && file->IsEof() ) + return NErr_EndOfFile; + + return NErr_Success; + } + else + return NErr_Error; +} + +size_t RawMediaReader::Release() +{ + file->Close(); + + delete file; + file = NULL; + + delete this; + + return 0; +} + + +#define CBCLASS RawMediaReader +START_DISPATCH; +CB( RELEASE, Release ); +CB( RAW_READ, Read ); +END_DISPATCH; +#undef CBCLASS diff --git a/Src/Plugins/Input/in_mp3/RawMediaReader.h b/Src/Plugins/Input/in_mp3/RawMediaReader.h new file mode 100644 index 00000000..5da1238c --- /dev/null +++ b/Src/Plugins/Input/in_mp3/RawMediaReader.h @@ -0,0 +1,30 @@ +#pragma once +#include "../Agave/DecodeFile/svc_raw_media_reader.h" +#include "../Agave/DecodeFile/ifc_raw_media_reader.h" +#include "giofile.h" + +// {5EC19CF3-E1ED-4AA5-AFD3-8E93149692BD} +static const GUID mpeg_audio_raw_reader_guid = +{ 0x5ec19cf3, 0xe1ed, 0x4aa5, { 0xaf, 0xd3, 0x8e, 0x93, 0x14, 0x96, 0x92, 0xbd } }; + +class RawMediaReaderService : public svc_raw_media_reader +{ +public: + static const char *getServiceName() { return "MPEG-1/2 Audio Raw Reader"; } + static GUID getServiceGuid() { return mpeg_audio_raw_reader_guid; } + int CreateRawMediaReader(const wchar_t *filename, ifc_raw_media_reader **reader); +protected: + RECVS_DISPATCH; +}; + +class RawMediaReader : public ifc_raw_media_reader +{ +public: + RawMediaReader(CGioFile *file); + int Read(void *buffer, size_t buffer_size, size_t *bytes_read); + size_t Release(); +protected: + RECVS_DISPATCH; +private: + CGioFile *file; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/Stopper.cpp b/Src/Plugins/Input/in_mp3/Stopper.cpp new file mode 100644 index 00000000..53c8d749 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/Stopper.cpp @@ -0,0 +1,44 @@ +#include "Stopper.h" +#include "main.h" +#include "../Winamp/wa_ipc.h" + +Stopper::Stopper() : isplaying(0), timems(0) +{ +} + +void Stopper::ChangeTracking(bool mode) +{ + SendMessage(mod.hMainWindow, WM_USER, mode, IPC_ALLOW_PLAYTRACKING); // enable / disable stats updating +} + +void Stopper::Stop() +{ + isplaying = (int)SendMessage(mod.hMainWindow, WM_USER, 0, IPC_ISPLAYING); + if (isplaying) + { + ChangeTracking(0); // disable stats updating + timems = (int)SendMessage(mod.hMainWindow, WM_USER, 0, IPC_GETOUTPUTTIME); + SendMessage(mod.hMainWindow, WM_COMMAND, 40047, 0); // Stop + } +} + +void Stopper::Play() +{ + if (isplaying) // this works _most_ of the time, not sure why a small portion of the time it doesnt hrmph :/ + // ideally we should replace it with a system that pauses the decode thread, closes its file, + // does the shit, and reopens and reseeks to the new offset. for gaplessness + { + if (timems) + { + m_force_seek = timems; // SendMessage(mod.hMainWindow,WM_USER,timems,106); + } + else m_force_seek = -1; + SendMessage(mod.hMainWindow, WM_COMMAND, 40045, 0); // Play + m_force_seek = -1; + if (isplaying & 2) + { + SendMessage(mod.hMainWindow, WM_COMMAND, 40046, 0); // Pause + } + ChangeTracking(1); // enable stats updating + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/Stopper.h b/Src/Plugins/Input/in_mp3/Stopper.h new file mode 100644 index 00000000..05d5ac6f --- /dev/null +++ b/Src/Plugins/Input/in_mp3/Stopper.h @@ -0,0 +1,10 @@ +#pragma once +class Stopper +{ +public: + Stopper(); + void ChangeTracking(bool); + void Stop(); + void Play(); + int isplaying, timems; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/StreamInfo.cpp b/Src/Plugins/Input/in_mp3/StreamInfo.cpp new file mode 100644 index 00000000..1a550255 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/StreamInfo.cpp @@ -0,0 +1,9 @@ +#include "main.h" +#include "MP3Info.h" + +StreamInfo::StreamInfo(void *buffer) : ID3Info() +{ + unsigned __int8 *header = (unsigned __int8 *)buffer; + da_tag.Parse(header, &header[ID3_TAGHEADERSIZE]); + GetID3V2Values(); +} diff --git a/Src/Plugins/Input/in_mp3/WasabiMetadata.cpp b/Src/Plugins/Input/in_mp3/WasabiMetadata.cpp new file mode 100644 index 00000000..15838b7f --- /dev/null +++ b/Src/Plugins/Input/in_mp3/WasabiMetadata.cpp @@ -0,0 +1,67 @@ +#include "WasabiMetadata.h" +#include <shlwapi.h> +#include "../nu/AutoChar.h" + +const wchar_t *MP3StreamMetadata::GetName() +{ + return L"MP3 Stream Metadata"; +} + +GUID MP3StreamMetadata::getGUID() +{ + return MP3StreamMetadataGUID; +} + +int MP3StreamMetadata::getFlags() +{ + return METATAG_FILE_INFO; +} + +int MP3StreamMetadata::isOurFile(const wchar_t *filename) +{ + if (PathIsURL(filename) && !_wcsicmp(PathFindExtension(filename), L".mp3")) + return 1; + else + return 0; +} + +int MP3StreamMetadata::metaTag_open(const wchar_t *filename) +{ + if (metadata.Open(filename) == METADATA_SUCCESS) + return METATAG_SUCCESS; + else + return METATAG_FAILED; +} + +void MP3StreamMetadata::metaTag_close() +{ + delete this; +} + +int MP3StreamMetadata::getMetaData(const wchar_t *tag, __int8 *buf, int buflenBytes, int datatype) +{ + if (datatype == METATYPE_STRING) + { + if (metadata.GetExtendedData(AutoChar(tag), (wchar_t *)buf, buflenBytes/sizeof(wchar_t))) + return METATAG_SUCCESS; + else + return METATAG_UNKNOWN_TAG; + } + else + return METATAG_FAILED; +} + +#define CBCLASS MP3StreamMetadata +START_DISPATCH; +CB(SVC_METATAG_GETNAME,getName) +CB(SVC_METATAG_GETGUID,getGUID) +CB(SVC_METATAG_GETFLAGS,getFlags) +CB(SVC_METATAG_ISOURFILE,isOurFile) +CB(SVC_METATAG_OPEN,metaTag_open) +VCB(SVC_METATAG_CLOSE,metaTag_close) +//CB(SVC_METATAG_ENUMTAGS,enumSupportedTag) +//CB(SVC_METATAG_GETTAGSIZE,getTagSize) +CB(SVC_METATAG_GETMETADATA,getMetaData) +//CB(SVC_METATAG_SETMETADATA,setMetaData) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/WasabiMetadata.h b/Src/Plugins/Input/in_mp3/WasabiMetadata.h new file mode 100644 index 00000000..dcd5dc24 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/WasabiMetadata.h @@ -0,0 +1,30 @@ +#pragma once +#include "../Agave/Metadata/svc_metatag.h" +#include "Metadata.h" + +// {9937E02D-205B-4964-86A9-F784D9C05F5D} +static const GUID MP3StreamMetadataGUID = + { 0x9937e02d, 0x205b, 0x4964, { 0x86, 0xa9, 0xf7, 0x84, 0xd9, 0xc0, 0x5f, 0x5d } }; + +class MP3StreamMetadata : public svc_metaTag +{ +private: + /* These methods are to be used by api_metadata */ + const wchar_t *GetName(); + GUID getGUID(); // this needs to be the same GUID that you use when registering your service factory + int getFlags(); // how this service gets its info + int isOurFile(const wchar_t *filename); + int metaTag_open(const wchar_t *filename); + void metaTag_close(); // self-destructs when this is called (you don't need to call serviceFactory->releaseInterface) + + /* user API starts here */ + const wchar_t *enumSupportedTag(int n, int *datatype = NULL); // returns a list of understood tags. might not be complete (see note [1]) + int getTagSize(const wchar_t *tag, size_t *sizeBytes); // always gives you BYTES, not characters (be careful with your strings) + int getMetaData(const wchar_t *tag, __int8 *buf, int buflenBytes, int datatype = METATYPE_STRING); // buflen is BYTES, not characters (be careful with your strings) + int setMetaData(const wchar_t *tag, const __int8 *buf, int buflenBytes, int datatype = METATYPE_STRING); +private: + Metadata metadata; + + RECVS_DISPATCH; +}; + diff --git a/Src/Plugins/Input/in_mp3/about.cpp b/Src/Plugins/Input/in_mp3/about.cpp new file mode 100644 index 00000000..7bc3ae04 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/about.cpp @@ -0,0 +1,576 @@ +#include <windows.h> +#include "main.h" +#if 1 +BOOL CALLBACK AboutProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) +{ + if (uMsg == WM_COMMAND && (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)) EndDialog(hwndDlg,0); + return 0; +} + +#else +#include <commctrl.h> + +#include ".\graphics\image.h" +#include ".\graphics\imagefilters.h" +#include <strsafe.h> + + + +#define random( min, max ) (( rand() % (int)((( max ) + 1 ) - ( min ))) + ( min )) + +HBITMAP LoadImageFromResource(INT_PTR handle); + +#define LEGAL_COUNT 4 +wchar_t *legal[] = {L"Copyright (C) 1998-2006 - Nullsoft, Inc.", + L"MPEG Layer-3 audio compression technology licensed by Fraunhofer IIS and THOMSON multimedia.", + L"VLB decoding copyright 1998-2002 by Dolby Laboratories, Inc. All rights reserved.", + L"AAC && aacPlus decoding copyright 1998-2006 by Coding Technologies, Inc. All rights reserved."}; + +HFONT fntPlugin, fntC, fntLegal; +HBRUSH brhDlgBG; + +#define IMAGES_COUNT 6 +#define IMAGES_INDEX_MY 0 +#define IMAGES_INDEX_WA 1 +#define IMAGES_INDEX_CT 2 +#define IMAGES_INDEX_IIS 3 +#define IMAGES_INDEX_ID3V2 4 +#define IMAGES_INDEX_MP3S 5 + +RECT rectLogo[IMAGES_COUNT]; +MLImage *imgLogo[IMAGES_COUNT] = {NULL, NULL, NULL, NULL, NULL, NULL}; +MLImage *imgLlama = NULL, *imgLlamaOrig = NULL, *imgMy = NULL; +RECT rcLlama; + +int idxSelected; + +wchar_t *url[IMAGES_COUNT - 1] = { L"http://winamp.com/", + L"http://www.codingtechnologies.com/index.htm", + L"http://www.iis.fraunhofer.de/index.html", + L"http://www.id3.org/", + L"http://www.iis.fraunhofer.de/amm/download/mp3surround/index.html"}; + +HWND hwndTT; +wchar_t strTT[] = L"click here to visit this website"; + +#define TIMER_ID_WATERRENDER 1980 +#define TIMER_ID_WATERPULSE 1978 +#define TIMER_DELAY_WATERRENDER 48 +#define TIMER_DELAY_WATERPULSE 12000 +#define TIMER_ID_LLAMAFADE 1987 +#define TIMER_DELAY_LLAMAFADE 200 + +#define TIMER_ID_LOADDATA 1959 +#define TIMER_DELAY_LOADDATA 10 +MLImage *tmpImage = NULL; + +MLImageFilterWater *fltrWater = NULL; +HCURSOR curHand = NULL; + +void about_OnInit(HWND hwndDlg); +void about_OnDestroy(HWND hwndDlg); +void about_OnMouseDown(HWND hwndDlg, int cx, int cy); +void about_OnMouseMove(HWND hwndDlg, int cx, int cy); +void about_OnDraw(HWND hwndDlg); +void timer_OnWaterPulse(HWND hwndDlg); +void timer_OnLlamaFade(HWND hwndDlg); +void timer_OnLoadData(HWND hwndDlg); + + +void SetRects(int cx, int cy) +{ + int i, xl, yl; + + i = IMAGES_INDEX_MY; xl = (cx - imgLogo[i]->GetWidth())/2; yl = 64; + if (imgLogo[i]) SetRect(&rectLogo[i], xl, yl, xl + imgLogo[i]->GetWidth(), yl + imgLogo[i]->GetHeight()); + xl = 2; yl = 2; i = IMAGES_INDEX_WA; + if (imgLogo[i]) SetRect(&rectLogo[i], xl, yl, xl + imgLogo[i]->GetWidth(), yl + imgLogo[i]->GetHeight()); + xl = 2; yl = cy - 88; i = IMAGES_INDEX_CT; + if (imgLogo[i]) SetRect(&rectLogo[i], xl, yl, xl + imgLogo[i]->GetWidth(), yl + imgLogo[i]->GetHeight()); + xl = rectLogo[i].right; i = IMAGES_INDEX_IIS; + if (imgLogo[i]) SetRect(&rectLogo[i], xl, yl, xl + imgLogo[i]->GetWidth(), yl + imgLogo[i]->GetHeight()); + xl = rectLogo[i].right; i = IMAGES_INDEX_ID3V2; + if (imgLogo[i]) SetRect(&rectLogo[i], xl, yl, xl + imgLogo[i]->GetWidth(), yl + imgLogo[i]->GetHeight()); + xl = rectLogo[i].right; i = IMAGES_INDEX_MP3S; + if (imgLogo[i]) SetRect(&rectLogo[i], xl, yl, xl + imgLogo[i]->GetWidth(), yl + imgLogo[i]->GetHeight()); + + if(imgLlama) SetRect(&rcLlama, 2, 1, imgLlama->GetWidth() + 2, imgLlama->GetHeight() + 1); +} +void CreateToolTipWnd(HWND hwndDlg) +{ + INITCOMMONCONTROLSEX iccex; + iccex.dwICC = ICC_WIN95_CLASSES; + iccex.dwSize = sizeof(INITCOMMONCONTROLSEX); + InitCommonControlsEx(&iccex); + + hwndTT = CreateWindowExW(WS_EX_TOPMOST, TOOLTIPS_CLASSW, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwndDlg, NULL, mod.hDllInstance, NULL); + + SetWindowPos(hwndTT, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); +} + +void UpdateToolTips(HWND hwndDlg) +{ + TOOLINFOW ti; + unsigned int uid = 0; + + // delete tools + int ttCount = SendMessage(hwndTT, TTM_GETTOOLCOUNT, 0, 0); + for(int i = 0; i < ttCount; i++) + { + if (SendMessageW(hwndTT, TTM_ENUMTOOLSW, (WPARAM)i, (LPARAM)&ti)) SendMessageW(hwndTT, TTM_DELTOOLW, 0, (LPARAM)&ti); + } + + /// add tools + + for (int i = 1; i < IMAGES_COUNT -1; i++) + { + ti.cbSize = sizeof(TOOLINFO); + ti.uFlags = TTF_SUBCLASS; + ti.hwnd = hwndDlg; + ti.hinst = mod.hDllInstance; + ti.uId = uid; + ti.lpszText = strTT; + ti.rect = rectLogo[i]; + SendMessageW(hwndTT, TTM_ADDTOOLW, 0, (LPARAM) (LPTOOLINFO) &ti); + } +} +BOOL CALLBACK AboutProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) +{ + switch(uMsg) + { + case WM_INITDIALOG: + about_OnInit(hwndDlg); + break; + case WM_DESTROY: + about_OnDestroy(hwndDlg); + break; + case WM_COMMAND: + if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) + { + EndDialog(hwndDlg,0); + } + break; + case WM_SIZE: + if (wParam != SIZE_MINIMIZED) + { + SetRects(LOWORD(lParam), HIWORD(lParam)); + UpdateToolTips(hwndDlg); + } + case WM_MOUSEMOVE: + about_OnMouseMove(hwndDlg, LOWORD(lParam), HIWORD(lParam)); + break; + case WM_LBUTTONDOWN: + about_OnMouseDown(hwndDlg, LOWORD(lParam), HIWORD(lParam)); + break; + case WM_PAINT: + about_OnDraw(hwndDlg); + break; + case WM_TIMER: + switch(wParam) + { + case TIMER_ID_WATERRENDER: + if (idxSelected != -1 && fltrWater) + { + fltrWater->Render(tmpImage, imgLogo[idxSelected]); + InvalidateRect(hwndDlg, &rectLogo[idxSelected], FALSE); + } + break; + case TIMER_ID_WATERPULSE: + timer_OnWaterPulse(hwndDlg); + break; + case TIMER_ID_LLAMAFADE: + timer_OnLlamaFade(hwndDlg); + break; + case TIMER_ID_LOADDATA: + timer_OnLoadData(hwndDlg); + break; + } + break; + case WM_CTLCOLORDLG: + return (BOOL)brhDlgBG; + break; + } + return 0; +} +void about_OnInit(HWND hwndDlg) +{ + HDC hdc = GetDC(hwndDlg); + fntPlugin = CreateFontW(-MulDiv(20, GetDeviceCaps(hdc, LOGPIXELSY), 72), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, L"Microsoft Sans Serif"); + fntC = CreateFontW(-MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72), 0, 0, 0, 0, TRUE, 0, 0, 0, 0, 0, ANTIALIASED_QUALITY, 0, L"Arial"); + fntLegal = CreateFontW(-MulDiv(6, GetDeviceCaps(hdc, LOGPIXELSY), 72), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ANTIALIASED_QUALITY, 0, L"Microsoft Sans Serif"); + ReleaseDC(hwndDlg, hdc); + + brhDlgBG = CreateSolidBrush(RGB(255,255,255)); + + SetTimer(hwndDlg, TIMER_ID_LOADDATA, TIMER_DELAY_LOADDATA, NULL); +} + +void about_OnDestroy(HWND hwndDlg) +{ + for (int i = 0; i < IMAGES_COUNT; i++) + { + if (imgLogo[i]) delete(imgLogo[i]); + imgLogo[i] = NULL; + } + + if (imgLlama) delete(imgLlama); + imgLlama = NULL; + + if (imgLlamaOrig) delete(imgLlamaOrig); + imgLlamaOrig = NULL; + + if (imgMy) delete(imgMy); + imgMy = NULL; + + if (fntPlugin) DeleteObject(fntPlugin); + if (fntC) DeleteObject(fntC); + if (fntLegal) DeleteObject(fntLegal); + fntPlugin = NULL; + fntC = NULL; + fntLegal = NULL; + + if (brhDlgBG) DeleteObject(brhDlgBG); + brhDlgBG = NULL; + + if (fltrWater) delete (fltrWater); + fltrWater = NULL; + + if (tmpImage) delete(tmpImage); + tmpImage = NULL; + + if (curHand) DestroyCursor(curHand); + curHand = NULL; +} + +void about_OnMouseDown(HWND hwndDlg, int cx, int cy) +{ + + if (idxSelected == -1) return; + + HCURSOR curWait = (HCURSOR)LoadImage(NULL, MAKEINTRESOURCE(32514/*OCR_WAIT*/), IMAGE_CURSOR, 0, 0, LR_DEFAULTCOLOR); + SetCursor(curWait); + ShellExecuteW(hwndDlg, L"open", url[idxSelected -1], NULL, L"c:\\", SW_SHOW); + SetCursor(curHand); + DestroyCursor(curWait); +} +void about_OnMouseMove(HWND hwndDlg, int cx, int cy) +{ + POINT pt = {cx, cy}; + int idxNew = -1; + for (int i = 1; i < IMAGES_COUNT - 1; i ++) + { + if (PtInRect(&rectLogo[i], pt)) + { + if (!curHand) + { + curHand = (HCURSOR)LoadImage(mod.hDllInstance, MAKEINTRESOURCE(IDC_CUR_HAND), IMAGE_CURSOR, 0, 0, LR_DEFAULTCOLOR); + } + SetCursor(curHand); + idxNew = i; + } + } + if (idxNew != idxSelected) + { + // stop animation + KillTimer(hwndDlg, TIMER_ID_WATERPULSE); + KillTimer(hwndDlg, TIMER_ID_WATERRENDER); + + if (idxSelected != -1) // redraw previously animated + { + InvalidateRect(hwndDlg, &rectLogo[idxSelected], FALSE); + } + // set new one + idxSelected = idxNew; + if (fltrWater) delete(fltrWater); + fltrWater = NULL; + if (tmpImage) delete(tmpImage); + tmpImage = NULL; + if (idxSelected != -1 && idxSelected != IMAGES_INDEX_WA) SetTimer(hwndDlg, TIMER_ID_WATERPULSE, 30, NULL); // start delay + } +} +void about_OnDraw(HWND hwndDlg) +{ + PAINTSTRUCT ps; + HDC hdc; + hdc = BeginPaint(hwndDlg, &ps); + RECT rc, ri; + GetClientRect(hwndDlg, &rc); + + // Draw Llama + RECT rl; + SetRect(&rl, rcLlama.left, rcLlama.top, rcLlama.right, rcLlama.bottom); + if (imgLlama && IntersectRect(&ri, &rl, &ps.rcPaint)) + { + HRGN hrgn = CreateRectRgn(rcLlama.left, rcLlama.top, rcLlama.right, rcLlama.bottom); + + HRGN hrgn1 = CreateRectRgn(rectLogo[IMAGES_INDEX_MY].left, + rectLogo[IMAGES_INDEX_MY].top, + rectLogo[IMAGES_INDEX_MY].right, + rectLogo[IMAGES_INDEX_MY].bottom); + CombineRgn(hrgn, hrgn, hrgn1, RGN_DIFF); + DeleteObject(hrgn1); + hrgn1 = CreateRectRgn(rectLogo[IMAGES_INDEX_WA].left, + rectLogo[IMAGES_INDEX_WA].top, + rectLogo[IMAGES_INDEX_WA].right, + rectLogo[IMAGES_INDEX_WA].bottom); + CombineRgn(hrgn, hrgn, hrgn1, RGN_DIFF); + SelectClipRgn(hdc, hrgn); + DeleteObject(hrgn); + DeleteObject(hrgn1); + + imgLlama->Draw(hdc, rl.left, rl.top); + SelectClipRgn(hdc, NULL); + } + + MLImage *img; + for (int i = 0; i < IMAGES_COUNT -1; i++) + { + + if (IntersectRect(&ri, &rectLogo[i], &ps.rcPaint)) + { + if (idxSelected == i && tmpImage) + { + img = tmpImage; + HRGN hrgn = CreateRectRgn(rectLogo[i].left + 3, rectLogo[i].top + 3 , rectLogo[i].right - 3, rectLogo[i].bottom - 3); + SelectClipRgn(hdc, hrgn); + DeleteObject(hrgn); + } + else img = imgLogo[i]; + if (img == NULL || imgLlama == NULL) continue; + if (i == IMAGES_INDEX_MY) + { // blend Llama First + MLImageFilter_Blend1(imgMy, img, 0, 0, + imgLlama->GetWidth() - (rectLogo[i].left - rcLlama.left), img->GetHeight(), imgLlama, + rectLogo[i].left - rcLlama.left, rectLogo[i].top - rcLlama.top, RGB(255,255,255)); + img = imgMy; + } + img->Draw(hdc, rectLogo[i].left, rectLogo[i].top); + SelectClipRgn(hdc, NULL); + } + } + + RECT rt; + SetBkMode(hdc, TRANSPARENT); + rc.left += 6; + rc.bottom -= 2; + rc.right -= 4; + + HFONT oldF = NULL; + + SetRect(&rt, rc.right - 192, rc.top, rc.right, rc.top + 16); + if (IntersectRect(&ri, &rt, &ps.rcPaint)) + { + /// Nullsoft (c) + HFONT oldF = (HFONT)SelectObject(hdc, fntC); + SetTextColor(hdc, RGB(100,100,200)); + DrawTextW(hdc, legal[0], -1, &rt, DT_LEFT | DT_SINGLELINE); + } + + SetRect(&rt, rc.left - 2, rc.bottom - 33, rc.right, rc.bottom); + if (IntersectRect(&ri, &rt, &ps.rcPaint)) + { + /// Separator + MoveToEx(hdc, rt.left, rt.top, NULL); + HPEN lp = CreatePen(PS_SOLID,1, RGB(190, 190, 190)); + HPEN op = (HPEN)SelectObject(hdc, lp); + LineTo(hdc, rt.right, rt.top); + SelectObject(hdc, lp); + DeleteObject(lp); + + /// Legal... + oldF = (oldF != NULL) ? oldF : (HFONT)SelectObject(hdc, fntLegal); + SetTextColor(hdc, RGB(0,0,0)); + for(int i = 1; i < LEGAL_COUNT; i++) + { + int y = rc.bottom - (LEGAL_COUNT - i)*10; + SetRect(&rt, rc.left, y, rc.right, y +10); + if (IntersectRect(&ri, &rt, &ps.rcPaint)) + DrawTextW(hdc, legal[i], -1, &rt, DT_LEFT | DT_SINGLELINE); + } + } + if (oldF != NULL) SelectObject(hdc, oldF); + EndPaint(hwndDlg, &ps); +} +void timer_OnLoadData(HWND hwndDlg) +{ + static step = 0; + KillTimer(hwndDlg, TIMER_ID_LOADDATA); + RECT rc; + GetClientRect(hwndDlg, &rc); + for (int i = 0; i < IMAGES_COUNT; i++) + { + imgLogo[i] = new MLImage(LoadImageFromResource, TRUE); + imgLogo[i]->Load(); + } + + imgMy = new MLImage(); + MLImage::Copy(imgMy, imgLogo[IMAGES_INDEX_MY]); + + imgLlamaOrig = new MLImage(LoadImageFromResource, TRUE); + imgLlamaOrig->Load(); + imgLlama = new MLImage(); + MLImage::Copy(imgLlama, imgLlamaOrig); + CreateToolTipWnd(hwndDlg); + UpdateToolTips(hwndDlg); + idxSelected = -1; + SetRects(rc.right - rc.left, rc.bottom - rc.top); + InvalidateRect(hwndDlg, &rc, FALSE); +// SetTimer(hwndDlg, TIMER_ID_LLAMAFADE, TIMER_DELAY_LLAMAFADE, NULL); + +} +void timer_OnWaterPulse(HWND hwndDlg) +{ + if (idxSelected == -1) return; + + BOOL startRender = (!fltrWater); + + if(!fltrWater) + { + KillTimer(hwndDlg, TIMER_ID_WATERPULSE); // stop timer - will change to slower interval + fltrWater = new MLImageFilterWater(); + fltrWater->CreateFor(imgLogo[idxSelected]); + tmpImage = new MLImage(imgLogo[idxSelected]->GetWidth(), imgLogo[idxSelected]->GetHeight()); + } + + int ow = imgLogo[idxSelected]->GetWidth(); + int oh = imgLogo[idxSelected]->GetHeight(); + + for (int i = 0; i < oh*ow/1024; i++) + { + fltrWater->SineBlob(-1, -1, random(4,min(oh,ow)), 600, 0); + } + + if (startRender) + { + SetTimer(hwndDlg, TIMER_ID_WATERRENDER, TIMER_DELAY_WATERRENDER, NULL); + SetTimer(hwndDlg, TIMER_ID_WATERPULSE, TIMER_DELAY_WATERPULSE, NULL); + } +} +void timer_OnLlamaFade(HWND hwndDlg) +{ + static int c = -2; + static int step = 2; + c = c + step; + if (c > 30 && step > 0) {step = 0 - step; c = 30;} + else if (c < 0 && step < 0) { step = 0 - step; c = 0; } + MLImageFilter_Fader3(imgLlama, imgLlamaOrig, c); + InvalidateRect(hwndDlg, &rcLlama, FALSE); +} + +HBITMAP LoadImageFromResource(INT_PTR handle) +{ + int id = 0; + BOOL incSize; + MLImage *img = (MLImage*)handle; + + if (img == imgLogo[IMAGES_INDEX_WA]) {id = IDB_LOGO_WAPLUGINS; incSize = FALSE;} + else if (img == imgLogo[IMAGES_INDEX_IIS]) {id = IDB_LOGO_IIS; incSize = TRUE;} + else if (img == imgLogo[IMAGES_INDEX_CT]) {id = IDB_LOGO_CODETECH; incSize = TRUE;} + else if (img == imgLogo[IMAGES_INDEX_ID3V2]) {id = IDB_LOGO_ID3V2; incSize = TRUE;} + else if (img == imgLogo[IMAGES_INDEX_MP3S]) {id = IDB_LOGO_MP3SURROUND; incSize = FALSE;} // because we don't use it for now + else if (img == imgLogo[IMAGES_INDEX_MY]) {id = IDB_LOGO_MY; incSize = FALSE;} + else if (img == imgLlamaOrig) {id = IDB_LLAMA; incSize = FALSE;} + if (id == 0) return NULL; + + HBITMAP hbmp = LoadBitmap(mod.hDllInstance, MAKEINTRESOURCE(id)); + if (hbmp == NULL) return hbmp; + + if (incSize) + {// we want to add additional white border + HDC hdcWnd = GetWindowDC(NULL); + HDC hdcSrc = CreateCompatibleDC(hdcWnd); + HDC hdcDest = CreateCompatibleDC(hdcWnd); + BITMAP bmp; + GetObject(hbmp, sizeof(BITMAP), &bmp); + int extraW = 40; + int extraH = 16; + HBITMAP hbmpNew = CreateBitmap(bmp.bmWidth + extraW, bmp.bmHeight + extraH, bmp.bmPlanes, bmp.bmBitsPixel, NULL); + + SelectObject(hdcSrc, hbmp); + SelectObject(hdcDest, hbmpNew); + + // fill borders + HBRUSH br = CreateSolidBrush(RGB(255,255,255)); + RECT rc; + SetRect(&rc, 0,0, bmp.bmWidth + extraW, extraH/2); + FillRect(hdcDest, &rc, br); + SetRect(&rc, 0, bmp.bmHeight + extraH/2, bmp.bmWidth + extraW, bmp.bmHeight + extraH); + FillRect(hdcDest, &rc, br); + SetRect(&rc, 0, extraH/2, extraW/2, bmp.bmHeight + extraH/2); + FillRect(hdcDest, &rc, br); + SetRect(&rc, bmp.bmWidth + extraW/2, extraH/2, bmp.bmWidth + extraW, bmp.bmHeight + extraH/2); + FillRect(hdcDest, &rc, br); + // copy original + BitBlt(hdcDest, extraW/2, extraH/2, bmp.bmWidth, bmp.bmHeight, hdcSrc, 0,0, SRCCOPY); + + DeleteObject(br); + DeleteObject(hbmp); + DeleteDC(hdcSrc); + DeleteDC(hdcDest); + ReleaseDC(NULL, hdcWnd); + + hbmp = hbmpNew; + } + if (img == imgLogo[IMAGES_INDEX_MY]) + { // need to add vesion number + HDC hdcWnd = GetWindowDC(NULL); + HDC hdcSrc = CreateCompatibleDC(hdcWnd); + HDC hdcDest = CreateCompatibleDC(hdcWnd); + + HFONT fnt = CreateFontW(-MulDiv(90, GetDeviceCaps(hdcDest, LOGPIXELSY), 72), 22, 0, 0, FW_BOLD, 0, 0, 0, 0, 0, 0, ANTIALIASED_QUALITY, 0, L"Agency FB"); + SelectObject(hdcDest, fnt); + + char *ver = mod.description + lstrlen(mod.description); + while(ver != mod.description && *ver != ' ') + { + ver = CharPrevA(mod.description, ver); + } + if (*ver == ' ') ver++; + + RECT rc; + SetRect(&rc, 0, 0, 0, 0); + DrawText(hdcDest, ver, -1, &rc, DT_CALCRECT | DT_RIGHT |DT_SINGLELINE); + + int extraW = rc.right + 6; + int extraH = 16; + + BITMAP bmp; + GetObject(hbmp, sizeof(BITMAP), &bmp); + HBITMAP hbmpNew = CreateBitmap(bmp.bmWidth + extraW, bmp.bmHeight + extraH, bmp.bmPlanes, bmp.bmBitsPixel, NULL); + + SelectObject(hdcSrc, hbmp); + SelectObject(hdcDest, hbmpNew); + + // fill new area + HBRUSH br = CreateSolidBrush(RGB(255,255,255)); + + SetRect(&rc, bmp.bmWidth,0, bmp.bmWidth + extraW, bmp.bmHeight + extraH); + FillRect(hdcDest, &rc, br); + SetRect(&rc, 0,0, bmp.bmWidth, extraH); + FillRect(hdcDest, &rc, br); + // copy original + BitBlt(hdcDest, 0, extraH, bmp.bmWidth, bmp.bmHeight, hdcSrc, 0,0, SRCCOPY); + // draw number + + SetTextColor(hdcDest, RGB(80, 60, 10)); + SetBkMode(hdcDest, TRANSPARENT); + SetRect(&rc, bmp.bmWidth + 6, rc.top -= 22, bmp.bmWidth + extraW, bmp.bmHeight + extraH); + DrawText(hdcDest, ver, -1, &rc, DT_RIGHT |DT_SINGLELINE); + DeleteObject(fnt); + DeleteObject(br); + DeleteObject(hbmp); + DeleteDC(hdcSrc); + DeleteDC(hdcDest); + ReleaseDC(NULL, hdcWnd); + + hbmp = hbmpNew; + + } + + + return hbmp; + +} + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/adts.h b/Src/Plugins/Input/in_mp3/adts.h new file mode 100644 index 00000000..d6e4fb95 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/adts.h @@ -0,0 +1,37 @@ +#ifndef NULLSOFT_IN_MP3_ADTS_H +#define NULLSOFT_IN_MP3_ADTS_H + +#include "ifc_mpeg_stream_reader.h" +#include <bfc/std_mkncc.h> // for MKnCC() +class adts +{ +protected: + adts() {} + ~adts() {} +public: + static FOURCC getServiceType() { return MK4CC('a','d','t','s'); } + virtual int Initialize(bool forceMono, bool reverseStereo, bool allowSurround, int maxBits, bool allowRG, bool _useFloat = false, bool _useCRC = false)=0; + virtual bool Open(ifc_mpeg_stream_reader *file)=0; + virtual int Sync(ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate)=0; + virtual void CalculateFrameSize(int *frameSize)=0; + virtual void GetOutputParameters(size_t *numBits, int *numChannels, int *sampleRate)=0; + virtual void Flush(ifc_mpeg_stream_reader *file)=0; + virtual void Close() = 0; + + enum + { + SUCCESS = 0, + FAILURE=1, + ENDOFFILE = 2, + NEEDMOREDATA = 3, + NEEDSYNC = 4, + }; + virtual int Decode(ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate, size_t *endCut)=0; + virtual size_t GetCurrentBitrate()=0; + virtual size_t GetDecoderDelay()=0; + virtual int GetLayer()=0; + virtual void Release()=0; + virtual void SetDecoderHooks(void *layer3_vis, void *layer2_eq, void *layer3_eq) {} +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/adts_mp2.cpp b/Src/Plugins/Input/in_mp3/adts_mp2.cpp new file mode 100644 index 00000000..0ad9858e --- /dev/null +++ b/Src/Plugins/Input/in_mp3/adts_mp2.cpp @@ -0,0 +1,400 @@ +#include "main.h" +#include "adts_mp2.h" +#include "../winamp/wa_ipc.h" +#include <math.h> +#include "mpegutil.h" +#include "../nsutil/pcm.h" + +extern int g_ds; + +<<<<<<< HEAD:in_mp3/adts_mp2.cpp +DecoderHooks hooks={mp3GiveVisData, mp2Equalize, mp3Equalize}; + +======= +>>>>>>> 5058463... fix old-school vis/eq mp3 stuff:mp3/adts_mp2.cpp +ADTS_MP2::ADTS_MP2() : decoder(0), gain(1.f) +{ + memset(&hooks, 0, sizeof(hooks)); +#ifndef NO_MP3SURROUND + lineFilled=false; + saDecHandle=0; + saMode = SA_DEC_OFF; +#endif + decoderDelay = 529; + endcut=0; + + outputFrameSize = 0; + bitsPerSample = 0; + allowRG = false; + useFloat = false; + channels = 0; + sampleRate = 0; + + memset(&delayline, 0, sizeof(delayline)); + delaylineSize = 0; +} + +void ADTS_MP2::SetDecoderHooks(void *layer3_vis, void *layer2_eq, void *layer3_eq) +{ +<<<<<<< HEAD:in_mp3/adts_mp2.cpp + //*(void **)&hooks.layer3_vis = layer3_vis; +======= + *(void **)&hooks.layer3_vis = layer3_vis; +>>>>>>> 5058463... fix old-school vis/eq mp3 stuff:mp3/adts_mp2.cpp + *(void **)&hooks.layer2_eq = layer2_eq; + *(void **)&hooks.layer3_eq = layer3_eq; +} + +int ADTS_MP2::Initialize(bool forceMono, bool reverseStereo, bool allowSurround, int maxBits, bool _allowRG, bool _useFloat, bool _useCRC) +{ + allowRG = _allowRG; + useFloat = _useFloat; + int downmix = 0; + if (reverseStereo) + downmix = 2; + else + downmix = forceMono ? 1 : 0; + bitsPerSample = maxBits; +<<<<<<< HEAD:in_mp3/adts_mp2.cpp + decoder = new CMpgaDecoder(&hooks, g_ds, downmix, !!_useCRC); + +======= + decoder = new CMpgaDecoder(hooks.layer3_vis?&hooks:(DecoderHooks *)0, MPEGAUDIO_QUALITY_FULL/*g_ds*/, downmix, _useCRC); +>>>>>>> 5058463... fix old-school vis/eq mp3 stuff:mp3/adts_mp2.cpp +#ifndef NO_MP3SURROUND + if (allowSurround) + IIS_SADec_Init(&saDecHandle, 6); +#endif + return 0; +} + +bool ADTS_MP2::Open(ifc_mpeg_stream_reader *file) +{ + decoder->Connect((CGioFile *)file); + if (allowRG) + gain = file->MPEGStream_Gain(); + return true; +} + +void ADTS_MP2::Close() +{ + if (decoder) + { + delete decoder; + decoder = 0; + } +#ifndef NO_MP3SURROUND + if (saDecHandle) + IIS_SADec_Free(&saDecHandle); + saDecHandle=0; +#endif +} + +void ADTS_MP2::GetOutputParameters(size_t *numBits, int *numChannels, int *sampleRate) +{ + *sampleRate = this->sampleRate; + *numChannels = channels; + *numBits = bitsPerSample; +} + +void ADTS_MP2::CalculateFrameSize(int *frameSize) +{ + *frameSize = outputFrameSize; + if (decoder->GetStreamInfo()->GetLayer() == 1) + *frameSize *= 3; +} + +void ADTS_MP2::Flush(ifc_mpeg_stream_reader *file) +{ + decoder->Reset(); +#ifndef NO_MP3SURROUND + if (saDecHandle) + IIS_SADec_Reset(saDecHandle); + lineFilled=false; +#endif +} + +size_t ADTS_MP2::GetCurrentBitrate() +{ + return decoder->GetStreamInfo()->GetBitrate() / 1000; +} + +size_t ADTS_MP2::GetDecoderDelay() +{ +#ifndef NO_MP3SURROUND + if (!saDecHandle || saMode == SA_DEC_OFF || saMode == SA_DEC_BYPASS) + return decoderDelay; + else /* bcc adds 576 delay */ + return decoderDelay+576; +#else + return decoderDelay; +#endif +} + +static void Decimate(const float *input, void *output, size_t numSamples, size_t *outputWritten, int bitsPerSample, bool useFloat, float gain) +{ + if (!useFloat) + { + // TODO seen a few crashes reported where 'output' is 0 + nsutil_pcm_FloatToInt_Interleaved_Gain(output, input, bitsPerSample, numSamples, gain); + } + else if (gain != 1.f) + { + float *data = (float *)output; + for (size_t i=0;i<numSamples;i++) + data[i]*=gain; + //data[i]=input[i]*gain; + } + + *outputWritten = numSamples * (bitsPerSample / 8); +} +/* +notes for mp3 surround implementations +need to check the first two frames for ancillary data +store first valid in temp +store second valid frame in delay line +decimate first valid into output buffer +ancillary data is stored one frame behind, so PCM data decoded from mp3 frame n combines with anc data from frame n+1 +*/ +int ADTS_MP2::Sync(ifc_mpeg_stream_reader *_file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate) +{ + SSC ssc; +CGioFile *file = (CGioFile *)_file; + unsigned char ancBytes[8192] = {0}; + int numAncBytes = 0; + + unsigned int delay=0, totalLength=0; + + float floatTemp[1152*2] = {0}; + float *flData=useFloat?(float *)output:floatTemp; + ssc = decoder->DecodeFrame(flData, sizeof(floatTemp), &outputFrameSize, +<<<<<<< HEAD:in_mp3/adts_mp2.cpp + ancBytes, &numAncBytes, 1, &delay, &totalLength); + + // TODO: benski> we should really have CGioFile try to read this stuff + if (delay && !file->prepad) + { + // validate + if (delay >= 529) + { + decoderDelay = delay; + endcut = 1152 - ((totalLength + delay) % 1152); // how many 0 samples had to be added? + endcut += decoderDelay; // also need to cut out the encoder+decoder delay + file->m_vbr_samples = totalLength; + } + } +======= + ancBytes, &numAncBytes); +>>>>>>> fd5c493... have CGioFile read the OFL (from newer fraunhofer encoders):mp3/adts_mp2.cpp + + switch (ssc) + { + case SSC_OK: + { + channels = decoder->GetStreamInfo()->GetEffectiveChannels(); + sampleRate = decoder->GetStreamInfo()->GetEffectiveSFreq(); +#ifndef NO_MP3SURROUND + if (!numAncBytes && saDecHandle) + { + ssc = decoder->DecodeFrame(delayline, sizeof(delayline), &delaylineSize, + ancBytes, &numAncBytes); + + if (SSC_SUCCESS(ssc)) + { + lineFilled=true; + } + else if (ssc == SSC_W_MPGA_SYNCEOF) + return ENDOFFILE; + else + return NEEDMOREDATA; + } + + if (saDecHandle) + { + SA_DEC_ERROR sa_error = IIS_SADec_DecodeAncData(saDecHandle, ancBytes, numAncBytes, 0, 0); + if (sa_error == SA_DEC_NO_ERROR) + { + IIS_SADec_InitInfo(saDecHandle, sampleRate, channels); + SA_DEC_INFO saInfo = IIS_SADec_GetInfo(saDecHandle); + sampleRate = saInfo.SampleRate; + channels = saInfo.nChannelsOut; + saMode = saInfo.configuredMode; + } + else if (saMode == SA_DEC_OFF) + { + IIS_SADec_Free(&saDecHandle); + saDecHandle=0; + } + else + { + lineFilled=false; + return NEEDMOREDATA; + } + } + + if (saDecHandle) + { + float surroundFloatTemp[1152*6] = {0}; + int outputSamples = 0; + /*SA_DEC_ERROR sa_error = */IIS_SADec_DecodeFrame(saDecHandle, + flData, outputFrameSize/sizeof(float), + (char *)ancBytes, numAncBytes, + surroundFloatTemp, sizeof(surroundFloatTemp), + &outputSamples, saMode, 0, 0, + 0, 0); + if (useFloat) + memcpy(output, surroundFloatTemp, sizeof(surroundFloatTemp)); + Decimate(surroundFloatTemp, output, outputSamples, outputWritten, bitsPerSample, useFloat, (float)gain); + outputFrameSize = *outputWritten; + } + else +#endif + { + Decimate(floatTemp, output, outputFrameSize / sizeof(float), outputWritten, bitsPerSample, useFloat, (float)gain); + outputFrameSize = *outputWritten; + } + } + return SSC_OK; + case SSC_W_MPGA_SYNCSEARCHED: + return NEEDMOREDATA; + case SSC_W_MPGA_SYNCEOF: + return ENDOFFILE; + case SSC_E_MPGA_WRONGLAYER: + decoder->m_Mbs.Seek(1); + default: + return NEEDMOREDATA; + + } +} + +int ADTS_MP2::Decode(ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate, size_t *endCut) +{ + if (endcut) + { + *endCut = endcut; + endcut=0; + } +#ifndef NO_MP3SURROUND + if (!saDecHandle && lineFilled) // if we don't have surround info, go ahead and flush out the delayline buffer + { + Decimate(delayline, output, delaylineSize / sizeof(float), outputWritten, bitsPerSample, useFloat, (float)gain); + *bitrate = decoder->GetStreamInfo()->GetBitrate() / 1000; + lineFilled=false; + return adts::SUCCESS; + } + + if (saDecHandle && !lineFilled && !decoder->IsEof()) // have surround info, but don't have a previously decoded frame + { + // resync + int ret = Sync(file, output, outputSize, outputWritten, bitrate); + if (ret == SSC_OK && saDecHandle) + { + *bitrate = decoder->GetStreamInfo()->GetBitrate() / 1000; + return adts::SUCCESS; + } + else if (saDecHandle) + return ret; + else + return adts::FAILURE; + } +#endif + + unsigned char ancBytes[8192] = {0}; + int numAncBytes = 0; + + int newl; + + float floatTemp[1152*2] = {0}; + float *flData=useFloat?(float *)output:floatTemp; + SSC ssc = decoder->DecodeFrame(flData, sizeof(floatTemp), &newl, ancBytes, &numAncBytes); + + if (SSC_SUCCESS(ssc)) + { +#ifndef NO_MP3SURROUND + if (saDecHandle && lineFilled) + { + float surroundFloatTemp[1152*6] = {0}; + int outputSamples; + /*SA_DEC_ERROR sa_error = */IIS_SADec_DecodeFrame(saDecHandle, + delayline, delaylineSize/sizeof(float), + (char *)ancBytes, numAncBytes, + surroundFloatTemp, sizeof(surroundFloatTemp), + &outputSamples, saMode, 0, 0, + 0, 0); + + if (useFloat) + memcpy(output, surroundFloatTemp, sizeof(surroundFloatTemp)); + Decimate(surroundFloatTemp, output, outputSamples, outputWritten, bitsPerSample, useFloat, (float)gain); + memcpy(delayline, flData, delaylineSize); + } + else +#endif + { + Decimate(floatTemp, output, newl / sizeof(float), outputWritten, bitsPerSample, useFloat, (float)gain); + } + *bitrate = decoder->GetStreamInfo()->GetBitrate() / 1000; + return adts::SUCCESS; + } + else if (decoder->IsEof()) + { + #ifndef NO_MP3SURROUND + /* In case of SA processing one ancillary data + package and maybe fill samples left in the dynamic + buffer of the mp3 decoder. Take care, that the + ancillary data buffer is greater then the dynamic buffer of + the mp3 decoder. */ + if (saDecHandle && lineFilled) + { + decoder->GetLastAncData(ancBytes, &numAncBytes); + float surroundFloatTemp[1152*6]; + int outputSamples = 0; + /*SA_DEC_ERROR sa_error = */IIS_SADec_DecodeFrame(saDecHandle, + delayline, delaylineSize/sizeof(float), + (char *)ancBytes, numAncBytes, + surroundFloatTemp, sizeof(surroundFloatTemp), + &outputSamples, saMode, 0, 0, + 0, 0); + + if (useFloat) + memcpy(output, surroundFloatTemp, sizeof(surroundFloatTemp)); + Decimate(surroundFloatTemp, output, outputSamples, outputWritten, bitsPerSample, useFloat, (float)gain); + lineFilled=false; + *bitrate = decoder->GetStreamInfo()->GetBitrate() / 1000; + return adts::SUCCESS; + } + else +#endif + return adts::ENDOFFILE; + } + else if (ssc == SSC_W_MPGA_SYNCNEEDDATA) + { + *bitrate = decoder->GetStreamInfo()->GetBitrate() / 1000; + return adts::NEEDMOREDATA; + } + else if (ssc==SSC_W_MPGA_SYNCLOST || ssc==SSC_W_MPGA_SYNCSEARCHED) + { + *bitrate = decoder->GetStreamInfo()->GetBitrate() / 1000; + return adts::NEEDSYNC; + } + else + { + if (ssc == SSC_E_MPGA_WRONGLAYER) + decoder->m_Mbs.Seek(1); + + return adts::FAILURE; + } + +} + +int ADTS_MP2::GetLayer() +{ + if (decoder) + return decoder->GetStreamInfo()->GetLayer(); + else + return 0; +} + +void ADTS_MP2::Release() +{ + delete this; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/adts_mp2.h b/Src/Plugins/Input/in_mp3/adts_mp2.h new file mode 100644 index 00000000..9678c72a --- /dev/null +++ b/Src/Plugins/Input/in_mp3/adts_mp2.h @@ -0,0 +1,58 @@ +#ifndef NULLSOFT_IN_MP3_ADTS_MP2_H +#define NULLSOFT_IN_MP3_ADTS_MP2_H + +#include "adts.h" +#include "api.h" +#include "config.h" +#ifndef NO_MP3SURROUND +#include "../mp3/bccDecLinklib/include/bccDecLink.h" // Binaural Cue Coding (aka mp3 surround) +#endif + +class ADTS_MP2 : public adts +{ +public: + ADTS_MP2(); + int Initialize(bool forceMono, bool reverse_stereo, bool allowSurround, int maxBits, bool allowRG, bool _useFloat, bool _useCRC); + bool Open(ifc_mpeg_stream_reader *file); + void Close(); + void GetOutputParameters(size_t *numBits, int *numChannels, int *sampleRate); + void CalculateFrameSize(int *frameSize); + void Flush(ifc_mpeg_stream_reader *file); + size_t GetCurrentBitrate(); + size_t GetDecoderDelay(); + int Sync(ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate); + int Decode(ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate, size_t *endCut); + int GetLayer(); + void Release(); + void SetDecoderHooks(void *layer3_vis, void *layer2_eq, void *layer3_eq); +<<<<<<< HEAD:in_mp3/adts_mp2.h + +======= +>>>>>>> 5058463... fix old-school vis/eq mp3 stuff:mp3/adts_mp2.h +private: + DecoderHooks hooks; + CMpgaDecoder *decoder; + int outputFrameSize; + size_t bitsPerSample; + double gain; + bool allowRG; + bool useFloat; + + int channels; + int sampleRate; + unsigned int decoderDelay; + unsigned int endcut; + +#ifndef NO_MP3SURROUND + float delayline[1152*2]; + int delaylineSize; + bool lineFilled; + SADEC_HANDLE saDecHandle; + SA_DEC_MODE saMode; +#endif + + +}; + + +#endif diff --git a/Src/Plugins/Input/in_mp3/adts_vlb.cpp b/Src/Plugins/Input/in_mp3/adts_vlb.cpp new file mode 100644 index 00000000..c8121381 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/adts_vlb.cpp @@ -0,0 +1,153 @@ +#include "adts_vlb.h" +#include "giofile.h" +#include "in2.h" +extern In_Module mod; + +ADTS_VLB::ADTS_VLB() : decoder(0), needsync(1) +{} + +int ADTS_VLB::Initialize(bool forceMono, bool reverseStereo, bool allowSurround, int maxBits, bool allowRG, bool _useFloat, bool _useCRC) +{ + return 0; +} + +bool ADTS_VLB::Open(ifc_mpeg_stream_reader *file) +{ + waServiceFactory *factory = mod.service->service_getServiceByGuid(obj_vlbDecoderGUID); + if (factory) + decoder = (obj_vlbDecoder *)factory->getInterface(); + + if (decoder) + { + int status = decoder->Open((DataIOControl *)(CGioFile *)file); + if (status == 0) + return true; + } + + return false; +} + +void ADTS_VLB::Close() +{ + if (decoder) + { + waServiceFactory *factory = mod.service->service_getServiceByGuid(obj_vlbDecoderGUID); + if (factory) + factory->releaseInterface(decoder); + } + + decoder = 0; +} + +void ADTS_VLB::GetOutputParameters(size_t *numBits, int *numChannels, int *sampleRate) +{ + *sampleRate = params.sampling_frequency; + *numChannels = params.num_channels; + *numBits = 16; +} + +void ADTS_VLB::CalculateFrameSize( int *frameSize ) +{ + *frameSize = 576 * 2 * params.num_channels; + if ( *frameSize > 576 * 2 * 2 ) + *frameSize = 576 * 2 * 2; +} + +void ADTS_VLB::Flush(ifc_mpeg_stream_reader *file) +{ + decoder->Flush(); + decoder->Close(); + decoder->Open((DataIOControl *)(CGioFile *)file); + + needsync = 1; +} + +size_t ADTS_VLB::GetCurrentBitrate() +{ + return params.bitrate / 1000; +} + +size_t ADTS_VLB::GetDecoderDelay() +{ + return 0; // not really sure +} + +int ADTS_VLB::Sync(ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate) +{ + int status = decoder->Synchronize(¶ms); + if (!status) + { + needsync = 0; + return SUCCESS; + } + + if (file->MPEGStream_EOF()) + return ENDOFFILE; + + return NEEDMOREDATA; +} + +int ADTS_VLB::Decode(ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate, size_t *endCut) +{ + if (*outputWritten = decoder->Read(output, outputSize)) + { + // TODO: benski> verify that params is valid here + *bitrate = params.bitrate / 1000; + + return adts::SUCCESS; + } + + if (needsync) + { + int status = decoder->Synchronize(¶ms); + if (!status) + { + needsync = 0; + } + else if (file->MPEGStream_EOF()) + { + return adts::ENDOFFILE; + } + } + + if (!needsync) + { + int status = decoder->DecodeFrame(¶ms); + + if (status > ERR_NO_ERROR && status != ERR_END_OF_FILE) + { + needsync = 1; + return adts::FAILURE; + } + else + { + if (status == ERR_END_OF_FILE) + { + if (file->MPEGStream_EOF()) + { + return adts::ENDOFFILE; + } + else + { + *bitrate = params.bitrate / 1000; + return adts::NEEDMOREDATA; + } + } + + *bitrate = params.bitrate / 1000; + return adts::SUCCESS; + } + } + + return adts::SUCCESS; +} + +int ADTS_VLB::GetLayer() +{ + return 4; +} + +void ADTS_VLB::Release() +{ + delete this; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/adts_vlb.h b/Src/Plugins/Input/in_mp3/adts_vlb.h new file mode 100644 index 00000000..7b6d3a9d --- /dev/null +++ b/Src/Plugins/Input/in_mp3/adts_vlb.h @@ -0,0 +1,37 @@ +#ifndef NULLSOFT_IN_MP3_ADTS_VLB_H +#define NULLSOFT_IN_MP3_ADTS_VLB_H +#include "adts.h" + +#include "../vlb/obj_vlbDecoder.h" +#include "api__in_mp3.h" +#include <api/service/waServiceFactory.h> + +class ADTS_VLB : public adts +{ +public: + ADTS_VLB(); + int Initialize( bool forceMono, bool reverseStereo, bool allowSurround, int maxBits, bool allowRG, bool _useFloat, bool _useCRC ); + + bool Open( ifc_mpeg_stream_reader *file ); + void Close(); + + void GetOutputParameters( size_t *numBits, int *numChannels, int *sampleRate ); + void CalculateFrameSize( int *frameSize ); + + void Flush( ifc_mpeg_stream_reader *file ); + + size_t GetCurrentBitrate(); + size_t GetDecoderDelay(); + + int Sync( ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate ); + int Decode( ifc_mpeg_stream_reader *file, unsigned __int8 *output, size_t outputSize, size_t *outputWritten, size_t *bitrate, size_t *endCut ); + + int GetLayer(); + void Release(); + +private: + obj_vlbDecoder *decoder; + int needsync; + AACStreamParameters params; +}; +#endif diff --git a/Src/Plugins/Input/in_mp3/apev2.cpp b/Src/Plugins/Input/in_mp3/apev2.cpp new file mode 100644 index 00000000..a4ae0d95 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/apev2.cpp @@ -0,0 +1,169 @@ +#include "apev2.h" + +APE::APE() +{ + hasData=false; + dirty=false; +} + +int APE::Decode(const void *data, size_t len) +{ + if (APEv2::Tag::Parse(data, len) == APEv2::APEV2_SUCCESS) + { + hasData=true; + return 1; + } + + return 0; +} + +// return -1 for empty, 1 for OK, 0 for "don't understand metadata name" +int APE::GetString(const char *metadata, wchar_t *data, int dataLen) +{ + if (!hasData) + return 0; + + if (!_stricmp(metadata, "replaygain_track_gain") + || !_stricmp(metadata, "replaygain_track_peak") + || !_stricmp(metadata, "replaygain_album_gain") + || !_stricmp(metadata, "replaygain_album_peak")) + { + if (APEv2::Tag::GetString(metadata, data, dataLen) == APEv2::APEV2_SUCCESS) + return 1; + return -1; + } + else + { + const char *ape_key = MapWinampKeyToApeKey(metadata); + if (ape_key) + { + if (APEv2::Tag::GetString(ape_key, data, dataLen) == APEv2::APEV2_SUCCESS) + return 1; + return -1; + } + } + + return 0; +} + +int APE::SetString(const char *metadata, const wchar_t *data) +{ + if (!_stricmp(metadata, "replaygain_track_gain") + || !_stricmp(metadata, "replaygain_track_peak") + || !_stricmp(metadata, "replaygain_album_gain") + || !_stricmp(metadata, "replaygain_album_peak")) + { + APEv2::Tag::SetString(metadata, data); + dirty=true; + hasData=true; + return 1; + } + else + { + const char *ape_key = MapWinampKeyToApeKey(metadata); + if (ape_key) + { + APEv2::Tag::SetString(ape_key, data); + dirty=true; + hasData=true; + return 1; + } + } + return 0; +} + +void APE::Clear() +{ + APEv2::Tag::Clear(); + dirty=true; + hasData=false; +} + +void APE::MarkClear() +{ + dirty=true; + hasData=false; +} + +int APE::SetKeyValueByIndex(size_t index, const char *key, const wchar_t *data) +{ + dirty=true; + return APEv2::Tag::SetKeyValueByIndex(index, key, data); +} + +int APE::RemoveItem(size_t index) +{ + dirty=true; + return APEv2::Tag::RemoveItem(index); +} + +int APE::AddItem() +{ + dirty=true; + hasData=true; + APEv2::Tag::AddItem(); + return APEv2::APEV2_SUCCESS; +} + +struct ApeKeyMapping +{ + const char *ape_key; + const char *winamp_key; + const wchar_t *winamp_keyW; +}; + +static ApeKeyMapping apeKeyMapping[] = +{ + { "Track", "track", L"track" }, + {"Album", "album", L"album" }, + {"Artist", "artist", L"artist" }, + {"Comment", "comment", L"comment" }, + {"Year", "year", L"year" }, + {"Genre", "genre", L"genre" }, + {"Title", "title", L"title"}, + {"Composer", "composer", L"composer"}, + {"Performer", "performer", L"performer"}, + {"Album artist", "albumartist", L"albumartist"}, +}; + +const wchar_t *APE::MapApeKeyToWinampKeyW(const char *ape_key) +{ + size_t num_mappings = sizeof(apeKeyMapping)/sizeof(apeKeyMapping[0]); + for (size_t i=0;i!=num_mappings;i++) + { + if (!_stricmp(ape_key, apeKeyMapping[i].ape_key)) + return apeKeyMapping[i].winamp_keyW; + } + return NULL; +} + +const char *APE::MapApeKeyToWinampKey(const char *ape_key) +{ + size_t num_mappings = sizeof(apeKeyMapping)/sizeof(apeKeyMapping[0]); + for (size_t i=0;i!=num_mappings;i++) + { + if (!_stricmp(ape_key, apeKeyMapping[i].ape_key)) + return apeKeyMapping[i].winamp_key; + } + return NULL; +} + +const char *APE::MapWinampKeyToApeKey(const char *winamp_key) +{ + size_t num_mappings = sizeof(apeKeyMapping)/sizeof(apeKeyMapping[0]); + for (size_t i=0;i!=num_mappings;i++) + { + if (!_stricmp(winamp_key, apeKeyMapping[i].winamp_key)) + return apeKeyMapping[i].ape_key; + } + return NULL; +} + +int APE::AddKeyValue(const char *key, const wchar_t *data) +{ + dirty=true; + hasData=true; + APEv2::Item *newItem = APEv2::Tag::AddItem(); + newItem->SetKey(key); + return APEv2::Tag::SetItemData(newItem, data); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/apev2.h b/Src/Plugins/Input/in_mp3/apev2.h new file mode 100644 index 00000000..76ca319c --- /dev/null +++ b/Src/Plugins/Input/in_mp3/apev2.h @@ -0,0 +1,46 @@ +#ifndef NULLSOFT_IN_MP3_APEV2_H +#define NULLSOFT_IN_MP3_APEV2_H + +#include "../apev2/tag.h" + +class APE : private APEv2::Tag +{ +public: + APE(); + bool HasData() { return hasData; } + bool IsDirty() { return dirty; } + void ResetDirty() { dirty=0; hasData = true; } + void Clear(); + void MarkClear(); + int Decode(const void *data, size_t len); + + // return -1 for empty, 1 for OK, 0 for "don't understand tag name" + int GetString(const char *tag, wchar_t *data, int dataLen); + int SetString(const char *tag, const wchar_t *data); + + int AddKeyValue(const char *key, const wchar_t *data); + int SetKeyValueByIndex(size_t index, const char *key, const wchar_t *data); + int RemoveItem(size_t index); + + int AddItem(); + + + static const char *MapApeKeyToWinampKey(const char *ape_key); + static const wchar_t *MapApeKeyToWinampKeyW(const char *ape_key); + static const char *MapWinampKeyToApeKey(const char *winamp_key); + + using APEv2::Tag::EnumValue; + using APEv2::Tag::EncodeSize; + using APEv2::Tag::Encode; + using APEv2::Tag::FindItemByKey; + using APEv2::Tag::GetNumItems; + using APEv2::Tag::IsItemReadOnly; + using APEv2::Tag::SetFlags; + +private: + bool hasData; + bool dirty; +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/api__in_mp3.h b/Src/Plugins/Input/in_mp3/api__in_mp3.h new file mode 100644 index 00000000..832db5bc --- /dev/null +++ b/Src/Plugins/Input/in_mp3/api__in_mp3.h @@ -0,0 +1,16 @@ +#ifndef NULLSOFT_IN_MP3_API_H +#define NULLSOFT_IN_MP3_API_H + +#include "../Agave/Config/api_config.h" + +#include "api/memmgr/api_memmgr.h" +extern api_memmgr *memmgr; +#define WASABI_API_MEMMGR memmgr + +#include "../Agave/Language/api_language.h" + +#include "api/application/api_application.h" +extern api_application *applicationApi; +#define WASABI_API_APP applicationApi + +#endif // !NULLSOFT_IN_MP3_API_H
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/config.cpp b/Src/Plugins/Input/in_mp3/config.cpp new file mode 100644 index 00000000..fcff41e8 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/config.cpp @@ -0,0 +1,685 @@ +#include "main.h" +#include <shlobj.h> +#include <commctrl.h> +#include <windows.h> +#include "../winamp/wa_ipc.h" +#include "config.h" +#include "api__in_mp3.h" +#include "resource.h" + +char g_http_tmp[MAX_PATH] = {0}; + +int config_write_mode = WRITE_UTF16; +int config_read_mode = READ_LOCAL; + +int config_parse_apev2 = 1; +int config_parse_lyrics3 = 1; +int config_parse_id3v1 = 1; +int config_parse_id3v2 = 1; + +int config_write_apev2 = 1; +int config_write_id3v1 = 1; +int config_write_id3v2 = 1; + +int config_create_id3v1 = 1; +int config_create_id3v2 = 1; +int config_create_apev2 = 0; + +int config_apev2_header = RETAIN_HEADER; +int config_lp = 0; + +BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam) +{ + wchar_t cl[32] = {0}; + GetClassNameW(hwnd, cl, ARRAYSIZE(cl)); + if (!lstrcmpiW(cl, WC_TREEVIEW)) + { + PostMessage(hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection(hwnd)); + return FALSE; + } + + return TRUE; +} + +static int CALLBACK BrowseCallbackProc( HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) +{ + switch (uMsg) + { + case BFFM_INITIALIZED: + { + SetWindowText(hwnd, WASABI_API_LNGSTRINGW(IDS_SELECT_DIRECTORY_TO_SAVE_TO)); + if (g_http_tmp[0]) SendMessage(hwnd, BFFM_SETSELECTIONA, 1, (LPARAM)g_http_tmp); + + // this is not nice but it fixes the selection not working correctly on all OSes + EnumChildWindows(hwnd, browseEnumProc, 0); + } + } + return 0; +} + +static char app_name[] = "Nullsoft MPEG Decoder"; + +char *get_inifile() { return INI_FILE; } + +int _r_i(char *name, int def) +{ + if (!_strnicmp(name, "config_", 7)) name += 7; + return GetPrivateProfileIntA(app_name, name, def, INI_FILE); +} + +#define RI(x) (( x ) = _r_i(#x,( x ))) +void _w_i(char *name, int d) +{ + char str[120] = {0}; + wsprintfA(str, "%d", d); + if (!_strnicmp(name, "config_", 7)) name += 7; + WritePrivateProfileStringA(app_name, name, str, INI_FILE); +} +#define WI(x) _w_i(#x,( x )) + +void _r_s(char *name, char *data, int mlen) +{ + char buf[2048] = {0}; + lstrcpynA(buf, data, 2048); + if (!_strnicmp(name, "config_", 7)) name += 7; + GetPrivateProfileStringA(app_name, name, buf, data, mlen, INI_FILE); +} +#define RS(x) (_r_s(#x,x,sizeof(x))) + +void _w_s(char *name, char *data) +{ + if (!_strnicmp(name, "config_", 7)) name += 7; + WritePrivateProfileStringA(app_name, name, data, INI_FILE); +} +#define WS(x) (_w_s(#x,x)) + +static void config_init() +{ + char *p; + if (mod.hMainWindow && + (p = (char *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETINIFILE)) + && p != (char *)1) + { + strncpy(INI_FILE, p, MAX_PATH); + } + else + { + GetModuleFileNameA(NULL, INI_FILE, sizeof(INI_FILE)); + p = INI_FILE + strlen(INI_FILE); + while (p >= INI_FILE && *p != '.') p--; + strcpy(++p, "ini"); + } +} + +#ifdef AAC_SUPPORT +#define DEF_EXT_LIST "MP3;MP2;MP1;AAC;VLB" +#else +#define DEF_EXT_LIST "MP3;MP2;MP1" +#endif + +#define __STR2WSTR(str) L##str +#define WIDEN(str) __STR2WSTR(str) +#define DEF_EXT_LISTW WIDEN(DEF_EXT_LIST) + +#ifdef AAC_SUPPORT +char config_extlist_aac[129] = DEF_EXT_LIST; +#else +char config_extlist[129] = DEF_EXT_LIST; +#endif + +char config_rating_email[255] = {0}; + +void config_read() +{ + config_init(); + RI(allow_scartwork); + RI(allow_sctitles); + RI(sctitle_format); + RI(config_http_buffersize); + RI(config_http_prebuffer); + RI(config_http_prebuffer_underrun); + RI(config_downmix); + RI(config_downsample); + RI(config_max_bufsize_k); + RI(config_eqmode); + RI(config_gapless); + + if(FAILED(SHGetFolderPathA(NULL, CSIDL_MYMUSIC, NULL, SHGFP_TYPE_CURRENT, config_http_save_dir))) + { + if(FAILED(SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, config_http_save_dir))) + { + lstrcpynA(config_http_save_dir, "C:\\", MAX_PATH); + } + } + + RS(config_http_save_dir); + RI(config_miscopts); + RI(config_fastvis); + +#ifdef AAC_SUPPORT + RS(config_extlist_aac); +#else + RS(config_extlist); +#endif + + RI(config_write_mode); + RI(config_read_mode); + + RI(config_parse_apev2); + RI(config_parse_lyrics3); + RI(config_parse_id3v1); + RI(config_parse_id3v2); + + RI(config_write_apev2); + RI(config_write_id3v1); + RI(config_write_id3v2); + + RI(config_create_apev2); + RI(config_create_id3v1); + RI(config_create_id3v2); + + RI(config_apev2_header); + + RI(config_lp); + + RS(config_rating_email); +} + +void config_write() +{ + WI(allow_scartwork); + WI(config_fastvis); + WI(config_miscopts); + WI(allow_sctitles); + WI(sctitle_format); + WI(config_http_buffersize); + WI(config_http_buffersize); + WI(config_http_prebuffer); + WI(config_http_prebuffer_underrun); + WI(config_downmix); + WI(config_downsample); + WI(config_max_bufsize_k); + WI(config_eqmode); + WS(config_http_save_dir); +#ifdef AAC_SUPPORT + WS(config_extlist_aac); +#else + WS(config_extlist); +#endif + + WI(config_write_mode); + WI(config_read_mode); + + WI(config_parse_apev2); + WI(config_parse_lyrics3); + WI(config_parse_id3v1); + WI(config_parse_id3v2); + + WI(config_write_apev2); + WI(config_write_id3v1); + WI(config_write_id3v2); + + WI(config_create_apev2); + WI(config_create_id3v1); + WI(config_create_id3v2); + + WI(config_apev2_header); + + WI(config_lp); + + WS(config_rating_email); +} + +static INT_PTR CALLBACK prefsProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +static INT_PTR CALLBACK id3Proc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +static INT_PTR CALLBACK advancedTaggingProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +static INT_PTR CALLBACK httpProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +static INT_PTR CALLBACK outputProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); + +#define ISSEP(x) ((x) == ' ' || (x) == ';' || (x) == ',' || (x) == ':' || (x) == '.') +char *getfileextensions() +{ + static char list[512]; + char *op = list; + // char *g_fileassos="MP3;MP2;MP1\0MPEG Audio Files (*.MP3;*.MP2;*.MP1)\0"; + + char *p = config_extlist; + int s = 0; + while (p && *p) + { + while (ISSEP(*p)) p++; + if (!p || !*p) break; + if (s) *op++ = ';'; + s = 1; + while (p && *p && !ISSEP(*p)) *op++ = *p++; + } + *op++ = 0; + strcpy(op, WASABI_API_LNGSTRING(IDS_MPEG_AUDIO_FILES)); + while (op && *op) op++; + p = config_extlist; + s = 0; + while (p && *p) + { + while (ISSEP(*p)) p++; + if (!p || !*p) break; + if (s) *op++ = ';'; + s = 1; + *op++ = '*'; + *op++ = '.'; + while (p && *p && !ISSEP(*p)) *op++ = *p++; + } + *op++ = ')'; + *op++ = 0; + *op++ = 0; + return list; +} + +void config(HWND hwndParent) +{ + wchar_t title[128] = {0}; + int x; + PROPSHEETHEADER pshead; + PROPSHEETPAGE pspage[5]; + ZeroMemory(&pshead, sizeof(PROPSHEETHEADER)); + pshead.dwSize = sizeof(PROPSHEETHEADER); + pshead.hwndParent = hwndParent; + pshead.dwFlags = PSH_PROPSHEETPAGE | PSH_NOAPPLYNOW | PSH_NOCONTEXTHELP; + pshead.hInstance = WASABI_API_LNG_HINST; + pshead.pszCaption = WASABI_API_LNGSTRINGW_BUF(IDS_MPEG_AUDIO_DECODER_SETTINGS,title,128);//"MPEG Audio Decoder Settings"; + pshead.nPages = sizeof(pspage) / sizeof(pspage[0]); + pshead.nStartPage = config_lp; + pshead.ppsp = pspage; + + ZeroMemory(pspage, sizeof(pspage)); + for ( x = 0; x < sizeof(pspage) / sizeof(pspage[0]); x ++) + pspage[x].dwSize = sizeof(PROPSHEETPAGE); + for ( x = 0; x < sizeof(pspage) / sizeof(pspage[0]); x ++) + pspage[x].hInstance = WASABI_API_LNG_HINST; + pspage[0].pszTemplate = MAKEINTRESOURCE(IDD_PREFS); + pspage[1].pszTemplate = MAKEINTRESOURCE(IDD_TAGOPTS); + pspage[2].pszTemplate = MAKEINTRESOURCE(IDD_ADVANCED_TAGGING); + pspage[3].pszTemplate = MAKEINTRESOURCE(IDD_OUTPUT); + pspage[4].pszTemplate = MAKEINTRESOURCE(IDD_HTTP); + pspage[0].pfnDlgProc = prefsProc; + pspage[1].pfnDlgProc = id3Proc; + pspage[2].pfnDlgProc = advancedTaggingProc; + pspage[3].pfnDlgProc = outputProc; + pspage[4].pfnDlgProc = httpProc; + PropertySheet((PROPSHEETHEADER*)&pshead); + config_write(); + extern char *g_fileassos; + mod.FileExtensions = getfileextensions(); +} + +static INT_PTR CALLBACK id3Proc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + if (config_parse_id3v1) CheckDlgButton(hwndDlg, IDC_READ_ID3V1, BST_CHECKED); + if (config_parse_id3v2) CheckDlgButton(hwndDlg, IDC_READ_ID3V2, BST_CHECKED); + + if (config_write_id3v1) CheckDlgButton(hwndDlg, IDC_WRITE_ID3V1, BST_CHECKED); + if (config_write_id3v2) CheckDlgButton(hwndDlg, IDC_WRITE_ID3V2, BST_CHECKED); + + if (config_create_id3v1) CheckDlgButton(hwndDlg, IDC_CREATE_ID3V1, BST_CHECKED); + if (config_create_id3v2) CheckDlgButton(hwndDlg, IDC_CREATE_ID3V2, BST_CHECKED); + + SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_LATIN_1)); + SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_SYSTEM_LANGUAGE)); + SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_SETCURSEL,(config_read_mode == READ_LOCAL),0); + + SendDlgItemMessage(hwndDlg,IDC_COMBO2,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_UNICODE_UTF_16)); + SendDlgItemMessage(hwndDlg,IDC_COMBO2,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_LATIN_1)); + SendDlgItemMessage(hwndDlg,IDC_COMBO2,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_SYSTEM_LANGUAGE)); + SendDlgItemMessage(hwndDlg,IDC_COMBO2,CB_SETCURSEL,config_write_mode,0); + + SetDlgItemTextA(hwndDlg,IDC_RATING_EMAIL,(config_rating_email[0] ? config_rating_email : "rating@winamp.com\0")); + + return FALSE; + case WM_NOTIFY: + { + LPNMHDR pnmh = (LPNMHDR) lParam; + if (pnmh->code == PSN_SETACTIVE) + { + config_lp = 1; + } + if (pnmh->code == PSN_APPLY) + { + config_parse_id3v1 = IsDlgButtonChecked(hwndDlg, IDC_READ_ID3V1); + config_parse_id3v2 = IsDlgButtonChecked(hwndDlg, IDC_READ_ID3V2); + + config_write_id3v1 = IsDlgButtonChecked(hwndDlg, IDC_WRITE_ID3V1); + config_write_id3v2 = IsDlgButtonChecked(hwndDlg, IDC_WRITE_ID3V2); + + config_create_id3v1 = IsDlgButtonChecked(hwndDlg, IDC_CREATE_ID3V1); + config_create_id3v2 = IsDlgButtonChecked(hwndDlg, IDC_CREATE_ID3V2); + + GetDlgItemTextA(hwndDlg,IDC_RATING_EMAIL,config_rating_email,sizeof(config_rating_email)); + if (!stricmp(config_rating_email, "rating@winamp.com\0")) config_rating_email[0] = 0; + + return TRUE; + } + } + return FALSE; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_COMBO1: + if(HIWORD(wParam) == CBN_SELCHANGE) + { + int cur = (int)SendMessage((HWND)lParam,CB_GETCURSEL,0,0); + if(!cur) config_read_mode = READ_LATIN; + else if(cur == 1) config_read_mode = READ_LOCAL; + } + break; + case IDC_COMBO2: + if(HIWORD(wParam) == CBN_SELCHANGE) + { + int cur = (int)SendMessage((HWND)lParam,CB_GETCURSEL,0,0); + if(!cur) config_write_mode = WRITE_UTF16; + else if(cur == 1) config_write_mode = WRITE_LATIN; + else if(cur == 2) config_write_mode = WRITE_LOCAL; + } + break; + case IDC_RATING_EMAIL_RESET: + if(HIWORD(wParam) == BN_CLICKED) + { + config_rating_email[0] = 0; + SetDlgItemTextA(hwndDlg,IDC_RATING_EMAIL,(config_rating_email[0] ? config_rating_email : "rating@winamp.com\0")); + } + } + return FALSE; + } + return FALSE; +} + +static INT_PTR CALLBACK advancedTaggingProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + + if (config_parse_apev2) CheckDlgButton(hwndDlg, IDC_READ_APEV2, BST_CHECKED); + if (config_write_apev2) CheckDlgButton(hwndDlg, IDC_WRITE_APEV2, BST_CHECKED); + if (config_create_apev2) CheckDlgButton(hwndDlg, IDC_CREATE_APEV2, BST_CHECKED); + + if (config_parse_lyrics3) CheckDlgButton(hwndDlg, IDC_READ_LYRICS3, BST_CHECKED); + + SendDlgItemMessage(hwndDlg,IDC_APEV2_HEADER_OPTIONS,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_APEV2_RETAIN_HEADER)); + SendDlgItemMessage(hwndDlg,IDC_APEV2_HEADER_OPTIONS,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_APEV2_ADD_HEADER)); + SendDlgItemMessage(hwndDlg,IDC_APEV2_HEADER_OPTIONS,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_APEV2_REMOVE_HEADER)); + SendDlgItemMessage(hwndDlg,IDC_APEV2_HEADER_OPTIONS,CB_SETCURSEL,config_apev2_header, 0); + + return FALSE; + case WM_NOTIFY: + { + LPNMHDR pnmh = (LPNMHDR) lParam; + if (pnmh->code == PSN_SETACTIVE) + { + config_lp = 2; + } + if (pnmh->code == PSN_APPLY) + { + config_parse_apev2 = IsDlgButtonChecked(hwndDlg, IDC_READ_APEV2); + config_write_apev2 = IsDlgButtonChecked(hwndDlg, IDC_WRITE_APEV2); + config_create_apev2 = IsDlgButtonChecked(hwndDlg, IDC_CREATE_APEV2); + + config_parse_lyrics3 = IsDlgButtonChecked(hwndDlg, IDC_READ_LYRICS3); + + return TRUE; + } + } + return FALSE; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_APEV2_HEADER_OPTIONS: + if(HIWORD(wParam) == CBN_SELCHANGE) + { + int cur = (int)SendMessage((HWND)lParam,CB_GETCURSEL,0,0); + if(!cur) config_apev2_header = RETAIN_HEADER; + else if(cur == 1) config_apev2_header = ADD_HEADER; + else if(cur == 2) config_apev2_header = REMOVE_HEADER; + } + break; + } + return FALSE; + } + return FALSE; +} + +static INT_PTR CALLBACK prefsProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + SetDlgItemTextA(hwndDlg, IDC_EDIT1, config_extlist); + SendDlgItemMessage(hwndDlg, IDC_EDIT1, EM_LIMITTEXT, 128, 0); + { + wchar_t str[10] = L""; + wsprintf(str, L"%d", config_max_bufsize_k); + SetDlgItemText(hwndDlg, IDC_BUFMAX, str); + SendMessage(GetDlgItem(hwndDlg, IDC_BUFMAX), EM_LIMITTEXT, 5, 0); + } + + return FALSE; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_BUTTON1: + SetDlgItemText(hwndDlg, IDC_EDIT1, DEF_EXT_LISTW); + break; + } + return FALSE; + case WM_NOTIFY: + { + LPNMHDR pnmh = (LPNMHDR) lParam; + if (pnmh->code == PSN_SETACTIVE) + { + config_lp = 0; + } + if (pnmh->code == PSN_APPLY) + { + config_max_bufsize_k = GetDlgItemInt(hwndDlg, IDC_BUFMAX, NULL, 0); + GetDlgItemTextA(hwndDlg, IDC_EDIT1, config_extlist, 128); + return TRUE; + } + } + return FALSE; + } + return FALSE; +} + +static INT_PTR CALLBACK outputProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + if (config_eqmode&1) CheckDlgButton(hwndDlg, IDC_RADIO2, 1); + else CheckDlgButton(hwndDlg, IDC_RADIO1, 1); + + if (!(config_eqmode&4)) CheckDlgButton(hwndDlg, IDC_FASTL3EQ, 1); + if (config_eqmode&8) CheckDlgButton(hwndDlg, IDC_FASTL12EQ, 1); + if (config_miscopts&1) CheckDlgButton(hwndDlg, IDC_CHECK1, BST_CHECKED); + if (config_miscopts&2) CheckDlgButton(hwndDlg, IDC_CHECK2, BST_CHECKED); + if (config_downmix == 2) CheckDlgButton(hwndDlg, IDC_REVSTEREO, BST_CHECKED); + if (config_downsample == 1) + CheckDlgButton(hwndDlg, IDC_HALFRATE, BST_CHECKED); + else if (config_downsample == 2) + CheckDlgButton(hwndDlg, IDC_QRATE, BST_CHECKED); + else + CheckDlgButton(hwndDlg, IDC_FULLRATE, BST_CHECKED); + return FALSE; + case WM_NOTIFY: + { + LPNMHDR pnmh = (LPNMHDR) lParam; + if (pnmh->code == PSN_SETACTIVE) + { + config_lp = 3; + } + + if (pnmh->code == PSN_APPLY) + { + config_miscopts &= ~3; + config_miscopts |= IsDlgButtonChecked(hwndDlg, IDC_CHECK1) ? 1 : 0; + config_miscopts |= IsDlgButtonChecked(hwndDlg, IDC_CHECK2) ? 2 : 0; + + config_eqmode = IsDlgButtonChecked(hwndDlg, IDC_RADIO1) ? 0 : 1; + config_eqmode |= IsDlgButtonChecked(hwndDlg, IDC_FASTL3EQ) ? 0 : 4; + config_eqmode |= IsDlgButtonChecked(hwndDlg, IDC_FASTL12EQ) ? 8 : 0; + + config_downmix = IsDlgButtonChecked(hwndDlg, IDC_REVSTEREO) ? 2 : 0; + + config_downsample = IsDlgButtonChecked(hwndDlg, IDC_HALFRATE) ? 1 : 0; + config_downsample = IsDlgButtonChecked(hwndDlg, IDC_QRATE) ? 2 : config_downsample; + + return TRUE; + } + } + return FALSE; + } + return FALSE; +} + +void SetHTTPSaveButtonText(HWND hwndDlg, char* path) +{ + HWND control = GetDlgItem(hwndDlg, IDC_BUTTON2); + HDC hdc = GetDC(control); + RECT r = {0}; + char temp[MAX_PATH] = {0}; + + lstrcpynA(temp, path, MAX_PATH); + SelectObject(hdc, (HFONT)SendMessage(control, WM_GETFONT, 0, 0)); + GetClientRect(control, &r); + r.left += 5; + r.right -= 5; + DrawTextA(hdc, temp, -1, &r, DT_PATH_ELLIPSIS|DT_WORD_ELLIPSIS|DT_MODIFYSTRING); + SetWindowTextA(control, temp); + ReleaseDC(control, hdc); +} + +static INT_PTR CALLBACK httpProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_CHECK2: + EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), IsDlgButtonChecked(hwndDlg, IDC_CHECK2)); + break; + case IDC_BUTTON2: + { + BROWSEINFO bi = {0}; + wchar_t name[MAX_PATH] = {0}; + bi.hwndOwner = hwndDlg; + bi.pszDisplayName = name; + bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; + bi.lpfn = BrowseCallbackProc; + LPITEMIDLIST idlist = SHBrowseForFolder(&bi); + if (idlist) + { + SHGetPathFromIDListA(idlist, g_http_tmp); + IMalloc *m = 0; + SHGetMalloc(&m); + m->Free(idlist); + SetHTTPSaveButtonText(hwndDlg, g_http_tmp); + } + } + + return 0; + } + return 0; + case WM_INITDIALOG: + SetDlgItemInt(hwndDlg, IDC_BUFFERS_NUMBUFS, config_http_buffersize, 0); + SendMessage(GetDlgItem(hwndDlg, IDC_PREBUFSLIDER), TBM_SETRANGEMAX, 0, 50); + SendMessage(GetDlgItem(hwndDlg, IDC_PREBUFSLIDER), TBM_SETRANGEMIN, 0, 0); + SendMessage(GetDlgItem(hwndDlg, IDC_PREBUFSLIDER), TBM_SETPOS, 1, config_http_prebuffer / 2); + SendMessage(GetDlgItem(hwndDlg, IDC_PREBUFSLIDER2), TBM_SETRANGEMAX, 0, 50); + SendMessage(GetDlgItem(hwndDlg, IDC_PREBUFSLIDER2), TBM_SETRANGEMIN, 0, 0); + SendMessage(GetDlgItem(hwndDlg, IDC_PREBUFSLIDER2), TBM_SETPOS, 1, config_http_prebuffer_underrun / 2); + CheckDlgButton(hwndDlg, IDC_CHECK1, allow_sctitles); + CheckDlgButton(hwndDlg, IDC_SC_ARTWORK, allow_scartwork); + CheckDlgButton(hwndDlg, IDC_CHECK3, sctitle_format); + + if (config_miscopts&16) + { + CheckDlgButton(hwndDlg, IDC_CHECK2, BST_CHECKED); + } + EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), (config_miscopts&16)); + SetHTTPSaveButtonText(hwndDlg, config_http_save_dir); + lstrcpynA(g_http_tmp, config_http_save_dir, MAX_PATH); + + return FALSE; + case WM_NOTIFY: + { + LPNMHDR pnmh = (LPNMHDR) lParam; + if (pnmh->code == PSN_SETACTIVE) + { + config_lp = 4; + } + + if (pnmh->code == PSN_APPLY) + { + sctitle_format = !!IsDlgButtonChecked(hwndDlg, IDC_CHECK3); + allow_sctitles = !!IsDlgButtonChecked(hwndDlg, IDC_CHECK1); + allow_scartwork = !!IsDlgButtonChecked(hwndDlg, IDC_SC_ARTWORK); + { + int s; + int t; + t = GetDlgItemInt(hwndDlg, IDC_BUFFERS_NUMBUFS, &s, 0); + if (s) config_http_buffersize = t; + if (config_http_buffersize < 16) config_http_buffersize = 16; + } + config_http_prebuffer = (int)SendMessage(GetDlgItem(hwndDlg, IDC_PREBUFSLIDER), TBM_GETPOS, 0, 0) * 2; + config_http_prebuffer_underrun = (int)SendMessage(GetDlgItem(hwndDlg, IDC_PREBUFSLIDER2), TBM_GETPOS, 0, 0) * 2; + lstrcpynA(config_http_save_dir, g_http_tmp, MAX_PATH); + if (IsDlgButtonChecked(hwndDlg, IDC_CHECK2)) + { + config_miscopts |= 16; + } + else + { + config_miscopts &= ~16; + } + + return TRUE; + } + } + return FALSE; + } + + const int controls[] = + { + IDC_PREBUFSLIDER, + IDC_PREBUFSLIDER2, + }; + if (FALSE != WASABI_API_APP->DirectMouseWheel_ProcessDialogMessage(hwndDlg, uMsg, wParam, lParam, controls, ARRAYSIZE(controls))) + { + return TRUE; + } + + return FALSE; +} + +int DoAboutMessageBox(HWND parent, wchar_t* title, wchar_t* message) +{ + MSGBOXPARAMSW msgbx = {sizeof(MSGBOXPARAMSW),0}; + msgbx.lpszText = message; + msgbx.lpszCaption = title; + msgbx.lpszIcon = MAKEINTRESOURCE(102); + msgbx.hInstance = GetModuleHandle(0); + msgbx.dwStyle = MB_USERICON; + msgbx.hwndOwner = parent; + return MessageBoxIndirectW(&msgbx); +} + +void about(HWND hwndParent) +{ + wchar_t message[1024] = {0}, text[1024] = {0}; + WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_MPEG_AUDIO_DECODER_OLD,text,1024); + wsprintfW(message, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), + mod.description, __DATE__); + DoAboutMessageBox(hwndParent,text,message); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/config.h b/Src/Plugins/Input/in_mp3/config.h new file mode 100644 index 00000000..a3ca37c3 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/config.h @@ -0,0 +1,51 @@ +#ifndef NULLSOFT_IN_MP3_CONFIG_H +#define NULLSOFT_IN_MP3_CONFIG_H + +#include <bfc/platform/guid.h> +enum +{ + WRITE_UTF16 = 0, + WRITE_LATIN = 1, + WRITE_LOCAL = 2, + WRITE_UTF8 = 3, +}; +extern int config_write_mode; + +enum +{ + READ_LATIN = 0, + READ_LOCAL = 1, +}; + +extern int config_read_mode; + +extern int config_parse_apev2; +extern int config_parse_lyrics3; +extern int config_parse_id3v1; +extern int config_parse_id3v2; + +extern int config_write_apev2; +extern int config_write_id3v1; +extern int config_write_id3v2; + +extern int config_create_id3v1; +extern int config_create_id3v2; +extern int config_create_apev2; + +enum +{ + RETAIN_HEADER = 0, + ADD_HEADER = 1, + REMOVE_HEADER = 2, +}; +extern int config_apev2_header; + +extern int config_id3v2_version; +extern int config_lp; +extern char config_rating_email[255]; + +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = +{ 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } }; + +#endif diff --git a/Src/Plugins/Input/in_mp3/giofile.cpp b/Src/Plugins/Input/in_mp3/giofile.cpp new file mode 100644 index 00000000..4949c268 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/giofile.cpp @@ -0,0 +1,2475 @@ +/***************************************************************************\ +* +* (C) copyright Fraunhofer - IIS (1998) +* All Rights Reserved +* +* filename: giofile.cpp +* project : MPEG Decoder +* author : Martin Sieler +* date : 1998-02-11 +* contents/description: file I/O class for MPEG Decoder +* +* +\***************************************************************************/ + +/* ------------------------ includes --------------------------------------*/ + +#include "main.h" +#include "api__in_mp3.h" +#include <time.h> +#include <locale.h> +#include "../Winamp/wa_ipc.h" + +#include "LAMEinfo.h" +#include "OFL.h" +#include "../nu/AutoChar.h" +#include "../nu/AutoWide.h" + +#include <api/service/waservicefactory.h> +#include <shlwapi.h> +#include "MP3Info.h" +#include "config.h" +#include <math.h> +#include "id3.h" +#include "../apev2/header.h" +#include "uvox_3902.h" +#include <foundation/error.h> +#include <strsafe.h> + +#define MAX_REDIRECTS 10 + +#define SAFE_MALLOC_MATH(orig, x) (((orig)<x)?x:orig) +// seems like a reasonable limit, but we should check with tag first +#define UVOX_MAXMSG_CAP 1048576 + +static jnl_connection_t CreateConnection(const char *url, jnl_dns_t dns, size_t sendbufsize, size_t recvbufsize) +{ + if (!_strnicmp(url, "https:", strlen("https:"))) + return jnl_sslconnection_create(dns, sendbufsize, recvbufsize); + else + return jnl_connection_create(dns, sendbufsize, recvbufsize); +} + +static int64_t Seek64(HANDLE hf, int64_t 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; +} + +HWND GetDialogBoxParent() +{ + HWND parent = (HWND)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETDIALOGBOXPARENT); + if (!parent || parent == (HWND)1) + return mod.hMainWindow; + return parent; +} + +/*-------------------------- defines --------------------------------------*/ + +/*-------------------------------------------------------------------------*/ + +//-------------------------------------------------------------------------* +// +// CGioFile +// +//-------------------------------------------------------------------------* + +//-------------------------------------------------------------------------* +// constructor +//-------------------------------------------------------------------------* + + +static char ultravoxUserAgent[128] = ""; +char *GetUltravoxUserAgent() +{ + if (!ultravoxUserAgent[0]) + { + StringCchPrintfA(ultravoxUserAgent, 128, "User-Agent: WinampMPEG/%01x.%02x, Ultravox/2.1\r\n" + "Ultravox-transport-type: TCP\r\n", + WINAMP_VERSION_MAJOR(winampVersion), + WINAMP_VERSION_MINOR(winampVersion)); + } + return ultravoxUserAgent; +} + +static char userAgent[128] = ""; +char *GetUserAgent() +{ + if (!userAgent[0]) + { + + StringCchPrintfA(userAgent, 128, "User-Agent: WinampMPEG/%01x.%02x\r\n", + WINAMP_VERSION_MAJOR(winampVersion), + WINAMP_VERSION_MINOR(winampVersion)); + } + return userAgent; +} + +extern int lastfn_status_err; + +CGioFile::CGioFile() +{ + req=0; + proxy_host=0; + host=0; + request=0; + proxy_lp=0; + lpinfo=0; + mpeg_length=0; + file_position=0; + mpeg_position=0; + no_peek_hack=false; + encodingMethod=0; + uvox_3901=0; + uvox_3902=0; + stream_url[0]=0; + stream_genre[0]=0; + stream_current_title[0]=0; + stream_name[0]=0; + ZeroMemory(&last_write_time, sizeof(last_write_time)); + ZeroMemory(id3v1_data, sizeof(id3v1_data)); + lengthVerified=false; + m_vbr_bytes = 0; + uvox_last_message = 0; + prepad = 0; + postpad = 0; + m_vbr_ms = 0; + m_vbr_hdr = 0; + stream_id3v2_buf = 0; + lyrics3_size=0; + lyrics3_data=0; + apev2_data=0; + m_content_type = 0; + ZeroMemory(uvox_meta, sizeof(uvox_meta)); + is_uvox = 0; + uvox_sid = uvox_maxbr = uvox_avgbr = 0; + uvox_message_cnt = uvox_desyncs = 0; + uvox_stream_data = 0; + uvox_stream_data_len = 0; + ZeroMemory(&uvox_artwork, sizeof(uvox_artwork)); + uvox_maxmsg = 65535; + force_lpinfo[0] = 0; + is_stream_seek = 0; + last_full_url[0] = 0; + m_is_stream = 0; + m_redircnt = 0; + m_auth_tries = 0; + m_full_buffer = NULL; + fEof = false; + m_connection = NULL; + m_dns = NULL; + hFile = INVALID_HANDLE_VALUE; + m_http_response = 0; + m_seek_reset = false; + m_is_stream_seek = false; + m_is_stream_seekable = false; + initTitleList(); + m_vbr_frames = 0; + ZeroMemory(&m_vbr_toc, sizeof(m_vbr_toc)); + m_vbr_frame_len = 0; + m_vbr_flag = 0; + m_id3v2_len = 0; + m_id3v1_len = 0; + port = 80; + constate = 0; + ZeroMemory(&save_filename, sizeof(save_filename)); + ZeroMemory(&server_name, sizeof(server_name)); + recvbuffersize = 32768; + + timeout_start = GetTickCount(); + meta_interval = 0; + meta_pos = 0; + ZeroMemory(&stream_title_save, sizeof(stream_title_save)); + ZeroMemory(&last_title_sent, sizeof(last_title_sent)); + ZeroMemory(&dlg_realm, sizeof(dlg_realm)); + m_useaproxy = 0; +} + +//-------------------------------------------------------------------------* +// destructor +//-------------------------------------------------------------------------* + +CGioFile::~CGioFile() +{ + int x, y; + for (x = 0; x < 2; x ++) + for (y = 0; y < 32; y ++) + free(uvox_meta[x][y]); + free(m_content_type); + free(uvox_stream_data); + free(stream_id3v2_buf); + free(lyrics3_data); + free(apev2_data); + free(uvox_3901); + free(uvox_3902); + + Close(); + delete m_vbr_hdr; + m_vbr_hdr = 0; + clearTitleList(); + free(lpinfo); + free(proxy_lp); + free(request); + free(host); + free(req); + free(proxy_host); +} + +static bool GetRG(const char *key, ID3v2 &info, APEv2::Tag &apev2, wchar_t *val, int len) +{ + val[0]=0; + if (info.GetString(key, val, len) == 1 && val[0]) + return true; + if (apev2.GetString(key, val, len) == APEv2::APEV2_SUCCESS && val[0]) + return true; + return false; +} + +float CGioFile::GetGain() +{ + if (AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false)) + { + // if (info.HasData()) + { + float dB = 0, peak = 1.0f; + wchar_t gain[128] = L"", peakVal[128] = L""; + _locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale(); + + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_source", 0)) + { + case 0: // track + if ((GetRG("replaygain_track_gain", info, apev2, gain, 128) == false || !gain[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + GetRG("replaygain_album_gain", info, apev2, gain, 128); + + if ((GetRG("replaygain_track_peak", info, apev2, peakVal, 128) == false || !peakVal[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + GetRG("replaygain_album_peak", info, apev2, peakVal, 128); + break; + case 1: + if ((GetRG("replaygain_album_gain", info, apev2, gain, 128) == false || !gain[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + GetRG("replaygain_track_gain", info, apev2, gain, 128); + + if ((GetRG("replaygain_album_peak", info, apev2, peakVal, 128) == false || !peakVal[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + GetRG("replaygain_track_peak", info, apev2, peakVal, 128); + break; + } + + if (gain[0]) + { + if (gain[0] == L'+') + dB = (float)_wtof_l(&gain[1], C_locale); + else + dB = (float)_wtof_l(gain, C_locale); + } + else + { + dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0); + return (float)pow(10.0f, dB / 20.0f); + } + + if (peakVal[0]) + { + peak = (float)_wtof_l(peakVal, C_locale); + } + + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_mode", 1)) + { + case 0: // apply gain + return (float)pow(10.0f, dB / 20.0f); + case 1: // apply gain, but don't clip + return min((float)pow(10.0f, dB / 20.0f), 1.0f / peak); + case 2: // normalize + return 1.0f / peak; + case 3: // prevent clipping + if (peak > 1.0f) + return 1.0f / peak; + else + return 1.0f; + } + } + /* else + { + float dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0); + return pow(10.0f, dB / 20.0f); + }*/ + } + + return 1.0f; // no gain +} +//-------------------------------------------------------------------------* +// Open +//-------------------------------------------------------------------------* + +static char *jnl_strndup(const char *str, size_t n) +{ + char *o = (char *)calloc(n+1, sizeof(char)); + if (!o) + return 0; + + strncpy(o, str, n); + o[n]=0; + return o; +} + +static int parse_url(const char *url, char **prot, char **host, unsigned short *port, char **req, char **lp) +{ + free(*prot); *prot=0; + free(*host); *host = 0; + free(*req); *req = 0; + free(*lp); *lp = 0; + *port = 0; + + const char *p; + const char *protocol = strstr(url, "://"); + if (protocol) + { + *prot = jnl_strndup(url, protocol-url); + p = protocol + 3; + + } + else + { + p = url; + } + + while (p && *p == '/') p++; // skip extra / + + size_t end = strcspn(p, "@/"); + + // check for username + if (p[end] == '@') + { + *lp = jnl_strndup(p, end); + p = p+end+1; + end = strcspn(p, "[:/"); + } + + if (p[0] == '[') // IPv6 style address + { + p++; + const char *ipv6_end = strchr(p, ']'); + if (!ipv6_end) + return 1; + + *host = jnl_strndup(p, ipv6_end-p); + p = ipv6_end+1; + } + else + { + end = strcspn(p, ":/"); + *host = jnl_strndup(p, end); + p += end; + } + + // is there a port number? + if (p[0] == ':') + { + char *new_end; + *port = (unsigned short)strtoul(p+1, &new_end, 10); + p = new_end; + } + + if (p[0]) + { + *req = _strdup(p); + } + + return 0; +} + +static void parseURL(const char *url, char **host, unsigned short *port, char **req, char **lp) +{ + char *prot=0; + + parse_url(url, &prot, host, port, req, lp); + if (!*port) + { + if (prot) + { + if (!stricmp(prot, "https")) + *port = 443; + else + *port = 80; + } + else + *port=80; + } + + free(prot); + + if (!*req) + *req = _strdup("/"); +} + +static void encodeMimeStr(char *in, char *out) +{ + char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + int shift = 0; + int accum = 0; + + while (in && *in) + { + if (*in) + { + accum <<= 8; + shift += 8; + accum |= *in++; + } + while (shift >= 6) + { + shift -= 6; + *out++ = alphabet[(accum >> shift) & 0x3F]; + } + } + if (shift == 4) + { + *out++ = alphabet[(accum & 0xF) << 2]; + *out++ = '='; + } + else if (shift == 2) + { + *out++ = alphabet[(accum & 0x3) << 4]; + *out++ = '='; + *out++ = '='; + } + + *out++ = 0; +} + +int CGioFile::doConnect(const char *str, int start_offset) +{ + char *http_ver_str = " HTTP/1.0\r\n"; + + unsigned short proxy_port = 80; + char str2[1024]={0}; + + if (!str) + str = last_full_url; + else + lstrcpynA( last_full_url, str, sizeof( last_full_url ) ); + + if (start_offset > 0) + { + http_ver_str = " HTTP/1.1\r\n"; + } + + lstrcpynA(str2, str, 1024); + + is_stream_seek = start_offset || !str; + + lstrcpynA(g_stream_title, str, 256); + + meta_interval = meta_pos = 0; + server_name[0] = 0; + last_title_sent[0] = 0; + stream_bytes_read = start_offset; + stream_metabytes_read = 0; + m_is_stream = 1; + constate = 0; + parseURL(str2, &host, &port, &request, &lpinfo); + if (port == 80 || !GetPrivateProfileIntA("Winamp", "proxyonly80", 0, INI_FILE)) + { + const char *p; + const char *winamp_proxy = (const char *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GET_PROXY_STRING); + if (!winamp_proxy || winamp_proxy == (char *)1) + { + char temp[256] = {0}; + GetPrivateProfileStringA("Winamp", "proxy", "", temp, sizeof(temp), INI_FILE); + p = temp; + } + else + p = winamp_proxy; + + while (p && (*p == ' ' || *p == '\t')) p++; + char config_proxy[512] = "http://"; + StringCchCatA(config_proxy, 512, p); + parseURL(config_proxy, &proxy_host, &proxy_port, &req, &proxy_lp); + } + + if (!host || !host[0]) + return 1; + char *p = request + strlen(request); + while (p >= request && *p != '/') p--; + if (p[1]) + lstrcpynA(g_stream_title, ++p, 256); + lstrcpynA(stream_title_save, g_stream_title, 580); + lstrcpynA(stream_name, g_stream_title, 256); + g_stream_title[255] = 0; + fEof = false; + timeout_start = GetTickCount(); + EnterCriticalSection(&g_lfnscs); + WASABI_API_LNGSTRING_BUF(IDS_CONNECTING,lastfn_status,256); + LeaveCriticalSection(&g_lfnscs); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + if (force_lpinfo[0]) + { + free(lpinfo); + lpinfo = _strdup(force_lpinfo); + } + + if (!m_dns) jnl_dns_create(&m_dns); + m_connection = CreateConnection(str, m_dns, 16384, recvbuffersize = max(32768, config_http_buffersize * 1024)); + if (!m_connection) + return 1; + + if (!proxy_host || !proxy_host[0]) + { + jnl_connection_connect(m_connection, host, port); + jnl_connection_send_string(m_connection, "GET "); + jnl_connection_send_string(m_connection, request); + jnl_connection_send_string(m_connection, http_ver_str); + } + else + { + char s[32]={0}; + jnl_connection_connect(m_connection, proxy_host, proxy_port); + jnl_connection_send_string(m_connection, "GET http://"); + if (lpinfo && lpinfo[0]) + { + jnl_connection_send_string(m_connection, lpinfo); + jnl_connection_send_string(m_connection, "@"); + } + jnl_connection_send_string(m_connection, host); + StringCchPrintfA(s, 32, ":%d", port); + jnl_connection_send_string(m_connection, s); + jnl_connection_send_string(m_connection, request); + jnl_connection_send_string(m_connection, http_ver_str); + if (proxy_lp && proxy_lp[0]) + { + char temp[1024]={0}; + jnl_connection_send_string(m_connection, "Proxy-Authorization: Basic "); + encodeMimeStr(proxy_lp, temp); + jnl_connection_send_string(m_connection, temp); + jnl_connection_send_string(m_connection, "\r\n"); + } + } + + jnl_connection_send_string(m_connection, "Host: "); + jnl_connection_send_string(m_connection, host); + jnl_connection_send_string(m_connection, "\r\n"); + + if (!start_offset) + { + jnl_connection_send_string(m_connection, GetUltravoxUserAgent()); + } + else + jnl_connection_send_string(m_connection, GetUserAgent()); + + jnl_connection_send_string(m_connection, "Accept: */*\r\n"); + + if (allow_sctitles && !start_offset) jnl_connection_send_string(m_connection, "Icy-MetaData:1\r\n"); + + if (lpinfo && lpinfo[0]) + { + char str[512] = {0}; + encodeMimeStr(lpinfo, str); + jnl_connection_send_string(m_connection, "Authorization: Basic "); + jnl_connection_send_string(m_connection, str); + jnl_connection_send_string(m_connection, "\r\n"); + } + if (start_offset > 0) + { + char str[512] = {0}; + StringCchPrintfA(str, 512, "Range: bytes=%d-\r\n", start_offset); + jnl_connection_send_string(m_connection, str); + } + jnl_connection_send_string(m_connection, "Connection: close\r\n"); + jnl_connection_send_string(m_connection, "\r\n"); + + return 0; +} + +int CGioFile::Open(const wchar_t *pszName, int maxbufsizek) +{ + peekBuffer.reserve(16384); + + m_vbr_flag = 0; + m_vbr_frames = 0; + m_vbr_samples = 0; + m_vbr_ms = 0; + m_vbr_frame_len = 0; + + m_id3v2_len = 0; + m_id3v1_len = 0; + m_apev2_len = 0; + + lyrics3_size = 0; + + m_is_stream = 0; + + mpeg_length = 0; + mpeg_position = 0; + file_position = 0; + + if ( !_wcsnicmp( pszName, L"file://", 7 ) ) + pszName += 7; + + if ( PathIsURL( pszName ) ) + { + wchar_t str[ 8192 ] = { 0 }; + wchar_t *p; + hFile = INVALID_HANDLE_VALUE; + lstrcpyn( str, pszName, 8192 ); + save_filename[ 0 ] = 0; + if ( p = wcsstr( str, L">save.file:" ) ) + { + *p = 0; + p += 11; + if ( !wcsstr( p, L".." ) && !wcsstr( p, L"\\" ) && !wcsstr( p, L"/" ) ) + { + lstrcpynA( save_filename, AutoChar( p ), 256 ); + } + } + + if ( doConnect( AutoChar( str ), 0 ) ) + return NErr_ConnectionFailed; + } + else + { + hFile = CreateFile(pszName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0); + + if (hFile != INVALID_HANDLE_VALUE) + { + GetFileTime( hFile, 0, 0, &last_write_time ); + mpeg_length = Seek64( hFile, 0, FILE_END ); + + uint64_t startOffset = 0; + unsigned char buf[ 1448 ] = { 0 }; + + DWORD len = 0; + while ( 1 ) // read all tags (sometimes programs get stupid and make multiple tags) + { + len = 0; + Seek64( hFile, startOffset, FILE_BEGIN ); + ReadFile( hFile, buf, 10, &len, NULL ); + + if ( len >= 10 && + buf[ 0 ] == 'I' && + buf[ 1 ] == 'D' && + buf[ 2 ] == '3' && + buf[ 3 ] != 255 && + buf[ 4 ] != 255 && + buf[ 6 ] < 0x80 && + buf[ 7 ] < 0x80 && + buf[ 8 ] < 0x80 && + buf[ 9 ] < 0x80 ) + { + DWORD thisLen = 10; + if ( buf[ 5 ] & 0x10 ) // check for footer flag + thisLen += 10; + + thisLen += ( (int)buf[ 6 ] ) << 21; + thisLen += ( (int)buf[ 7 ] ) << 14; + thisLen += ( (int)buf[ 8 ] ) << 7; + thisLen += ( (int)buf[ 9 ] ); + + SetFilePointer( hFile, -10, NULL, FILE_CURRENT ); + + if ( stream_id3v2_buf ) // already read a tag? + { + startOffset += thisLen; + m_id3v2_len += thisLen; + mpeg_length -= thisLen; + mpeg_position += thisLen; + + SetFilePointer( hFile, thisLen, NULL, FILE_CURRENT ); + + continue; + } + + stream_id3v2_buf = (char *)malloc( thisLen ); + if ( stream_id3v2_buf ) + { + memcpy( stream_id3v2_buf, buf, 10 ); + + DWORD dummy = 0; + if ( !ReadFile( hFile, stream_id3v2_buf, thisLen, &dummy, 0 ) || dummy < thisLen ) + { + free( stream_id3v2_buf ); + stream_id3v2_buf = NULL; + + thisLen = 0; + SetFilePointer( hFile, 0, NULL, FILE_END ); + + break; + } + else + { + mpeg_position += thisLen; + stream_id3v2_read = thisLen; + mpeg_length -= thisLen; + startOffset += thisLen; + + info.Decode( stream_id3v2_buf, thisLen ); + } + } + else + { + /* memory allocation failed, let's assume the ID3v2 tag was valid ... */ + mpeg_position += thisLen; + stream_id3v2_read = thisLen; + mpeg_length -= thisLen; + startOffset += thisLen; + } + + m_id3v2_len += thisLen; + } + else + { + // benski> unnecessary because we call SetFilePointer immediately after the loop: + // CUT: SetFilePointer(hFile, -10, NULL, FILE_CURRENT); + break; + } + } + + /* Read ID3v1 Tag */ + if ( mpeg_length >= 128 ) + { + SetFilePointer( hFile, -128, NULL, FILE_END ); + + len = 0; + if ( ReadFile( hFile, id3v1_data, 128, &len, NULL ) && len == 128 ) + { + if ( id3v1_data[ 0 ] == 'T' && id3v1_data[ 1 ] == 'A' && id3v1_data[ 2 ] == 'G' ) + { + m_id3v1_len = 128; + mpeg_length -= m_id3v1_len; + } + } + } + + /* read appended ID3v2.4 tag */ + if ( mpeg_length >= 10 && Seek64( hFile, mpeg_position + mpeg_length - 10, FILE_BEGIN ) != -1 ) + { + len = 0; + ReadFile( hFile, buf, 10, &len, NULL ); + if ( len >= 10 && + buf[ 0 ] == '3' && + buf[ 1 ] == 'D' && + buf[ 2 ] == 'I' && + buf[ 3 ] != 255 && + buf[ 4 ] != 255 && + buf[ 6 ] < 0x80 && + buf[ 7 ] < 0x80 && + buf[ 8 ] < 0x80 && + buf[ 9 ] < 0x80 ) + { + DWORD thisLen = 10; + if ( buf[ 5 ] & 0x10 ) // check for header flag + thisLen += 10; + + thisLen += ( (int)buf[ 6 ] ) << 21; + thisLen += ( (int)buf[ 7 ] ) << 14; + thisLen += ( (int)buf[ 8 ] ) << 7; + thisLen += ( (int)buf[ 9 ] ); + + mpeg_length -= thisLen; + } + } + + /* Read Lyrics3 Tag */ + if ( mpeg_length >= 15 ) + { + free( lyrics3_data ); + lyrics3_data = NULL; + + lyrics3_size = 0; + char lyrics3_end_signature[ 15 ] = { 0 }; + + Seek64( hFile, ( mpeg_position + mpeg_length - 15 ), FILE_BEGIN ); + + len = 0; + if ( ReadFile( hFile, lyrics3_end_signature, 15, &len, NULL ) && len == 15 ) + { + if ( !memcmp( lyrics3_end_signature + 6, "LYRICS200", 9 ) ) + { + lyrics3_size = strtoul( lyrics3_end_signature, 0, 10 ); + if ( lyrics3_size ) + { + lyrics3_data = (char *)malloc( lyrics3_size ); + if ( lyrics3_data ) + { + SetFilePointer( hFile, -(LONG)( 15 + lyrics3_size ), NULL, FILE_CURRENT ); + + len = 0; + ReadFile( hFile, lyrics3_data, lyrics3_size, &len, 0 ); + if ( len != lyrics3_size || memcmp( lyrics3_data, "LYRICSBEGIN", 11 ) ) + { + free( lyrics3_data ); + lyrics3_data = NULL; + + lyrics3_size = 0; + } + else + { + mpeg_length -= lyrics3_size + 15; + } + } + } + } + } + } + + if ( mpeg_length >= 32 ) + { + /* Read APEv2 Tag */ + free( apev2_data ); + apev2_data = NULL; + + char ape[ 32 ] = { 0 }; + Seek64( hFile, ( mpeg_position + mpeg_length - 32 ), FILE_BEGIN ); + + len = 0; + if ( ReadFile( hFile, ape, 32, &len, NULL ) && len == 32 ) + { + APEv2::Header footer( ape ); + if ( footer.Valid() ) + { + m_apev2_len = footer.TagSize(); + if ( mpeg_length >= m_apev2_len ) + { + Seek64( hFile, -(int64_t)( m_apev2_len ), FILE_CURRENT ); + + apev2_data = (char *)malloc( m_apev2_len ); + if ( apev2_data ) + { + len = 0; + ReadFile( hFile, apev2_data, m_apev2_len, &len, NULL ); + if ( len != m_apev2_len || apev2.Parse( apev2_data, m_apev2_len ) != APEv2::APEV2_SUCCESS ) + { + free( apev2_data ); + apev2_data = NULL; + + m_apev2_len = 0; + } + } + + mpeg_length -= m_apev2_len; + } + } + } + } + + { + Seek64( hFile, mpeg_position, FILE_BEGIN ); + + len = 0; + ReadFile( hFile, buf, sizeof( buf ), &len, NULL ); + + delete m_vbr_hdr; + m_vbr_hdr = NULL; + + LAMEinfo lame; + lame.toc = m_vbr_toc; + + m_vbr_frame_len = ReadLAMEinfo( buf, &lame ); + if ( m_vbr_frame_len ) + { + lengthVerified = false; + prepad = lame.encoderDelay; + postpad = lame.padding; + + encodingMethod = lame.encodingMethod; + if ( !encodingMethod && lame.cbr ) + encodingMethod = ENCODING_METHOD_CBR; + + if ( lame.flags & TOC_FLAG ) + { + int x; + for ( x = 0; x < 100; x++ ) + if ( m_vbr_toc[ x ] ) break; + + if ( x != 100 ) + m_vbr_flag = 1; + } + + if ( lame.flags & BYTES_FLAG ) + { + // some programs are stupid and count the id3v2 length in the lame header + if ( mpeg_length + m_id3v2_len == lame.bytes || mpeg_length + m_id3v2_len + m_id3v1_len == lame.bytes ) + { + m_vbr_bytes = mpeg_length; + lengthVerified = true; + } + else if ( abs( (int)mpeg_length - lame.bytes ) < MAX_ACCEPTABLE_DEVIANCE ) + { + m_vbr_bytes = lame.bytes; + lengthVerified = true; + } + } + + if ( lame.flags & FRAMES_FLAG + && m_vbr_bytes && lengthVerified ) // only use the length if we're sure it's unmodified + { + m_vbr_frames = lame.frames; + m_vbr_samples = Int32x32To64( lame.frames, lame.h_id ? 1152 : 576 ); + m_vbr_samples -= ( prepad + postpad ); + m_vbr_ms = MulDiv( (int)m_vbr_samples, 1000, lame.samprate ); + } + + if ( !m_vbr_frames || encodingMethod == ENCODING_METHOD_CBR ) + m_vbr_flag = 0; + + mpeg_length -= m_vbr_frame_len; + mpeg_position += m_vbr_frame_len; + } + else + { + m_vbr_hdr = new CVbriHeader; + + m_vbr_frame_len = m_vbr_hdr->readVbriHeader(buf); + if (m_vbr_frame_len) + { + m_vbr_bytes = m_vbr_hdr->getBytes(); + m_vbr_frames = m_vbr_hdr->getNumFrames(); + m_vbr_ms = m_vbr_hdr->getNumMS(); + + lengthVerified = true; + + mpeg_length -= m_vbr_frame_len; + mpeg_position += m_vbr_frame_len; + } + else + { + delete m_vbr_hdr; + m_vbr_hdr = NULL; + } + } + } + + // read OFL + { + Seek64( hFile, mpeg_position, FILE_BEGIN ); + len = 0; + ReadFile( hFile, buf, sizeof( buf ), &len, NULL ); + MPEGFrame frame; + frame.ReadBuffer( buf ); + OFL ofl; + if ( ofl.Read( frame, &buf[ 4 ], len - 4 ) == NErr_Success ) + { + m_vbr_ms = (int)ofl.GetLengthSeconds() * 1000; + m_vbr_samples = ofl.GetSamples(); + + m_vbr_frames = ofl.GetFrames(); + size_t pre, post; + if ( ofl.GetGaps( &pre, &post ) == NErr_Success ) + { + prepad = (int)pre - 529; + postpad = (int)post + 529; + } + } + } + + ReadiTunesGaps(); + + Seek64( hFile, mpeg_position, FILE_BEGIN ); + + if ( maxbufsizek * 1024 >= mpeg_length ) + { + DWORD m_full_buffer_len = 0; + m_full_buffer = new unsigned char[ (unsigned int)mpeg_length ]; + if ( !ReadFile( hFile, m_full_buffer, (DWORD)mpeg_length, &m_full_buffer_len, NULL ) ) + { + CloseHandle( hFile ); + hFile = INVALID_HANDLE_VALUE; + delete[] m_full_buffer; + m_full_buffer = NULL; + return NErr_Error; + } + CloseHandle( hFile ); + hFile = INVALID_HANDLE_VALUE; + m_full_buffer_pos = 0; + } + else + { + m_full_buffer = NULL; + } + + fEof = false; + return NErr_Success; + } + else + { + //DWORD dwError = GetLastError(); + return NErr_Error; + } + } + + return NErr_Success; +} + +//-------------------------------------------------------------------------* +// Close +//-------------------------------------------------------------------------* + +int CGioFile::Close() +{ + int dwReturn = NErr_Success; + + if (m_is_stream) + { + jnl_connection_release(m_connection); + if (m_dns) jnl_dns_release(m_dns); + m_dns = 0; + if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); + hFile = INVALID_HANDLE_VALUE; + m_is_stream = 0; + } + else + { + delete [] m_full_buffer; + m_full_buffer = NULL; + + if (hFile != INVALID_HANDLE_VALUE) + { + dwReturn = CloseHandle(hFile) ? NErr_Success : NErr_Error; + hFile = INVALID_HANDLE_VALUE; + } + } + + return dwReturn; +} + +void CGioFile::processMetaData(char *data, int len, int msgId) +{ + if (len && *data) + { + char *ld = NULL; + int x; + if (len > 4096) return ; + for (x = 0; x < len; x ++) + if (!data[x]) break; + if (x == len) return ; + while ((ld = strstr(data, "='"))) + { + char * n = data; + ld[0] = 0; + ld += 2; + data = strstr(ld, "';"); + if (data) + { + data[0] = 0; + data += 2; + if (!_stricmp(n, "StreamTitle")) + { + lstrcpynA(g_stream_title, ld, sizeof(g_stream_title)); + lstrcpynA(last_title_sent, g_stream_title, sizeof(last_title_sent)); + lstrcpynA(stream_current_title, g_stream_title, sizeof(stream_current_title)); + if (sctitle_format) + { + StringCchCatA(g_stream_title, 256, *stream_title_save ? *g_stream_title ? " (" : "(" : ""); + StringCchCatA(g_stream_title, 256, stream_title_save); + StringCchCatA(g_stream_title, 256, *stream_title_save ? ")" : ""); + } + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + } + else if (!_stricmp(n, "StreamUrl")) + { + lstrcpynA(stream_url, ld, sizeof(stream_url)); + DWORD_PTR dw; + if (stream_url[0]) SendMessageTimeout(mod.hMainWindow, WM_USER, (WPARAM)stream_url, IPC_MBOPEN, SMTO_NORMAL, 500, &dw); + } + } + else break; + } + } +} + +INT_PTR CALLBACK CGioFile::httpDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + CGioFile *_this; + switch (uMsg) + { + case WM_INITDIALOG: + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); + _this = (CGioFile *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if (_this->force_lpinfo[0]) + SetDlgItemTextA(hwndDlg, IDC_EDIT1, _this->force_lpinfo); + else SetDlgItemTextA(hwndDlg, IDC_EDIT1, _this->lpinfo?_this->lpinfo:""); + SetDlgItemTextA(hwndDlg, IDC_REALM, _this->dlg_realm); + return 1; + case WM_COMMAND: + _this = (CGioFile *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if (LOWORD(wParam) == IDOK) + { + GetDlgItemTextA(hwndDlg, IDC_EDIT1, _this->force_lpinfo, sizeof(_this->force_lpinfo)); + EndDialog(hwndDlg, 1); + } + else if (LOWORD(wParam) == IDCANCEL) + { + EndDialog(hwndDlg, 0); + } + break; + } + return 0; +} + +class GioFileFiller : public Filler +{ +public: + GioFileFiller(CGioFile *_file) + { + ret=NErr_Success; + file=_file; + } + + size_t Read(void *dest, size_t len) + { + int bytesRead=0; + ret = file->Read(dest, (int)len, &bytesRead); + return bytesRead; + } + + CGioFile *file; + int ret; +}; + +int CGioFile::Peek(void *pBuffer, int cbToRead, int *pcbRead) +{ + GioFileFiller filler(this); + + // do we need to fill up? + if (cbToRead > (int)peekBuffer.size()) + { + no_peek_hack = true;// benski> HACK ALERT! we have to set this or else Read() will try to use the peek buffer + peekBuffer.fill(&filler, cbToRead - peekBuffer.size()); + no_peek_hack=false; + } + + *pcbRead = (int)peekBuffer.peek(pBuffer, cbToRead); + return filler.ret; +} + +//-------------------------------------------------------------------------* +// Read +//-------------------------------------------------------------------------* + +int CGioFile::Read(void *pBuffer, int cbToRead, int *pcbRead) +{ + TITLELISTTYPE *mylist = TitleLinkedList; + // these are used for SHOUTCAST2 metadata and artwork since it can provide + // multi-packet metadata chunks which are out of order or are received in + // a way which would otherwise cause these to be cleared incorrectly + static int title_parts = 0, stream_art_parts = 0, stream_art_parts_total_len = 0, + playing_art_parts = 0, playing_art_parts_total_len = 0; + static char **title_blocks = 0, **stream_art_blocks = 0, **playing_art_blocks = 0; + + if (mylist != &TitleListTerminator && mod.outMod) + { + while (mylist->Next && mylist != &TitleListTerminator) + { + TITLELISTTYPE *next = (TITLELISTTYPE *)mylist->Next; + long now = mod.outMod->GetOutputTime(); + + if (mylist->title[0] || mylist->part_len) + { + if (!mylist->timer || now >= mylist->timer) + { + switch(mylist->style) + { + case UVOX_METADATA_STYLE_AOLRADIO: + { + EnterCriticalSection(&streamInfoLock); + free(uvox_3901); + uvox_3901 = _strdup(mylist->title); + LeaveCriticalSection(&streamInfoLock); + if (g_playing_file) + { + PostMessage(mod.hMainWindow, WM_USER, (WPARAM) "0x3901", IPC_METADATA_CHANGED); + PostMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE); + } + } + break; + + case UVOX_METADATA_STYLE_SHOUTCAST: + { + processMetaData(mylist->title, (int)strlen(mylist->title) + 1); + } + break; + + case UVOX_METADATA_STYLE_SHOUTCAST2: + { + EnterCriticalSection(&streamInfoLock); + // so we can recombine multi-packets we'll store things and + // when all of the packets have been read then we form them + if(!title_blocks) + title_blocks = (char **)malloc(sizeof(char*)*(mylist->total_parts)); + + title_blocks[mylist->part-1] = _strdup(mylist->title); + title_parts++; + + // sanity check so we only try to get the metadata if all parts were processed + if (title_parts == mylist->total_parts) + { + free(uvox_3902); + uvox_3902 = (char *)malloc(16377 * mylist->total_parts); + uvox_3902[0] = 0; + title_parts = 0; + + for(int i = 0; i < mylist->total_parts; i++) + { + StringCchCatA(uvox_3902, mylist->total_parts * 16384, title_blocks[i]); + free(title_blocks[i]); + } + free(title_blocks); + title_blocks = 0; + + // attempt to form a title as 'artist - album - title' as sc_serv2 feeds + // changed for 5.61 to be just 'artist - title' to match v1 and v2 tools + Ultravox3902 uvox_metadata; + if (uvox_metadata.Parse(uvox_3902) != API_XML_FAILURE) + { + wchar_t stream_title[256] = {0}; + char* fields[] = {"artist", "title"}; + for(int i = 0; i < sizeof(fields)/sizeof(fields[0]); i++) + { + wchar_t temp[256] = {0}; + int ret = uvox_metadata.GetExtendedData(fields[i], temp, 256); + if(ret && temp[0]) + { + if(stream_title[0]) StringCchCatW(stream_title, 256, L" - "); + StringCchCatW(stream_title, 256, temp); + } + } + + lstrcpynA(g_stream_title, AutoChar(stream_title, CP_UTF8), sizeof(g_stream_title)); + lstrcpynA(last_title_sent, g_stream_title, sizeof(last_title_sent)); + lstrcpynA(stream_current_title, g_stream_title, sizeof(stream_current_title)); + } + } + else + { + g_stream_title[0] = 0; + last_title_sent[0] = 0; + } + + if (sctitle_format) + { + StringCchCatA(g_stream_title, 256, *stream_title_save ? *g_stream_title ? " (" : "(" : ""); + StringCchCatA(g_stream_title, 256, stream_title_save); + StringCchCatA(g_stream_title, 256, *stream_title_save ? ")" : ""); + } + LeaveCriticalSection(&streamInfoLock); + + if (g_playing_file) + { + PostMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE); + } + } + break; + + case UVOX_METADATA_STYLE_SHOUTCAST2_ARTWORK: + { + EnterCriticalSection(&streamInfoLock); + // so we can recombine multi-packets we'll store things and + // when all of the packets have been read then we form them + if(!stream_art_blocks) + { + stream_art_parts_total_len = 0; + stream_art_blocks = (char **)malloc(sizeof(char*)*(mylist->total_parts)); + } + + stream_art_blocks[mylist->part-1] = (char *)malloc(mylist->part_len+1); + memcpy(stream_art_blocks[mylist->part-1], mylist->title, mylist->part_len); + stream_art_parts++; + stream_art_parts_total_len += mylist->part_len; + + // sanity check so we only try to get the metadata if all parts were processed + if (stream_art_parts == mylist->total_parts) + { + free(uvox_artwork.uvox_stream_artwork); + if (stream_art_parts_total_len <= 0) break; + uvox_artwork.uvox_stream_artwork = (char *)malloc(stream_art_parts_total_len); + uvox_artwork.uvox_stream_artwork[0] = 0; + uvox_artwork.uvox_stream_artwork_len = stream_art_parts_total_len; + uvox_artwork.uvox_stream_artwork_type = mylist->type; + stream_art_parts = 0; + + char *art = uvox_artwork.uvox_stream_artwork; + for(int i = 0; i < mylist->total_parts; i++) + { + int size = min(stream_art_parts_total_len, 16371); + if (size > 0 && size <= 16371) + { + memcpy(art, stream_art_blocks[i], size); + stream_art_parts_total_len -= size; + art += size; + } + free(stream_art_blocks[i]); + } + free(stream_art_blocks); + stream_art_blocks = 0; + } + LeaveCriticalSection(&streamInfoLock); + + if (g_playing_file) + { + PostMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE); + } + } + break; + + case UVOX_METADATA_STYLE_SHOUTCAST2_ARTWORK_PLAYING: + { + EnterCriticalSection(&streamInfoLock); + // so we can recombine multi-packets we'll store things and + // when all of the packets have been read then we form them + if(!playing_art_blocks) + { + playing_art_parts_total_len = 0; + playing_art_blocks = (char **)malloc(sizeof(char*)*(mylist->total_parts)); + } + + playing_art_blocks[mylist->part-1] = (char *)malloc(mylist->part_len+1); + memcpy(playing_art_blocks[mylist->part-1], mylist->title, mylist->part_len); + playing_art_parts++; + playing_art_parts_total_len += mylist->part_len; + + // sanity check so we only try to get the metadata if all parts were processed + if (playing_art_parts == mylist->total_parts) + { + free(uvox_artwork.uvox_playing_artwork); + if (playing_art_parts_total_len <= 0) break; + uvox_artwork.uvox_playing_artwork = (char *)malloc(playing_art_parts_total_len); + uvox_artwork.uvox_playing_artwork[0] = 0; + uvox_artwork.uvox_playing_artwork_len = playing_art_parts_total_len; + uvox_artwork.uvox_playing_artwork_type = mylist->type; + + playing_art_parts = 0; + + char *art = uvox_artwork.uvox_playing_artwork; + for(int i = 0; i < mylist->total_parts; i++) + { + int size = min(playing_art_parts_total_len, 16371); + if (size > 0 && size <= 16371) + { + memcpy(art, playing_art_blocks[i], size); + playing_art_parts_total_len -= size; + art += size; + } + free(playing_art_blocks[i]); + } + free(playing_art_blocks); + playing_art_blocks = 0; + } + LeaveCriticalSection(&streamInfoLock); + + if (g_playing_file) + { + PostMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE); + } + } + break; + } + removeTitleListEntry(mylist); + } + } + mylist = next; + } + } + + if (!no_peek_hack && peekBuffer.size()) + { + *pcbRead = (int)peekBuffer.read(pBuffer, cbToRead); + return NErr_Success; + } + + BOOL bSuccess; + if (m_is_stream) + { + if (m_connection) + { + char str[4096]={0}; + if (pcbRead) *pcbRead = 0; + jnl_connection_run(m_connection, -1, -1, 0, 0); + if (constate == 0) + { + if ( jnl_connection_receive_lines_available( m_connection ) > 0 ) + { + char *p = str; + jnl_connection_receive_line( m_connection, str, 4096 ); + + // check http version and type of data, partial or full + if ( strlen( str ) >= 13 && !_strnicmp( str, "HTTP/1.1", 8 ) ) + { + char *p = str + 8; while ( p && *p == ' ' ) p++; + m_http_response = ( p ? atoi( p ) : 0 ); + if ( m_http_response == 200 ) + m_seek_reset = true; + } + + EnterCriticalSection( &g_lfnscs ); + lstrcpynA( lastfn_status, str, 256 ); + LeaveCriticalSection( &g_lfnscs ); + PostMessage( mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE ); + while ( p && *p && *p != ' ' ) p++; + if ( p && *p ) p++; + + if ( p[ 0 ] == '2' ) constate = 1; + else if ( strstr( p, "301" ) == p || strstr( p, "302" ) == p || + strstr( p, "303" ) == p || strstr( p, "307" ) == p ) + { + constate = 69; + } + else if ( strstr( p, "401" ) == p && m_auth_tries++ < 3 ) + { + constate = 75; + } + else if ( strstr( p, "403" ) == p ) + { + EnterCriticalSection( &g_lfnscs ); + lstrcpynA( lastfn_status, "access denied", 256 ); + lastfn_status_err = 1; + LeaveCriticalSection( &g_lfnscs ); + jnl_connection_close( m_connection, 1 ); + } + else if ( strstr( p, "503" ) == p ) + { + EnterCriticalSection( &g_lfnscs ); + lstrcpynA( lastfn_status, "server full", 256 ); + lastfn_status_err = 1; + LeaveCriticalSection( &g_lfnscs ); + jnl_connection_close( m_connection, 1 ); + } + else + { + lastfn_status_err = 1; + jnl_connection_close( m_connection, 1 ); + } + } + else if ( jnl_connection_get_state( m_connection ) == JNL_CONNECTION_STATE_CLOSED || jnl_connection_get_state( m_connection ) == JNL_CONNECTION_STATE_ERROR || jnl_connection_get_state( m_connection ) == JNL_CONNECTION_STATE_NOCONNECTION ) + { + const char *t = jnl_connection_get_error( m_connection ); + if ( t && *t ) + { + EnterCriticalSection( &g_lfnscs ); + lstrcpynA( lastfn_status, t, 256 ); + lastfn_status_err = 1; + LeaveCriticalSection( &g_lfnscs ); + } + PostMessage( mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE ); + } + } + if (constate == 75) // authorization required + { + while (jnl_connection_receive_lines_available(m_connection) > 0) + { + char *wwwa = "WWW-Authenticate:"; + jnl_connection_receive_line(m_connection, str, 4096); + if (!str[0]) + { + lastfn_status_err = 1; jnl_connection_close(m_connection, 1); break; + } + if (!_strnicmp(str, wwwa, strlen(wwwa))) + { + int has = 0; + char *s2 = "Basic realm=\""; + char *p = str + strlen(wwwa); while (p && *p == ' ') p++; + if (!_strnicmp(p, s2, strlen(s2))) + { + p += strlen(s2); + if (strstr(p, "\"")) + { + if (p && *p) + { + strstr(p, "\"")[0] = 0; + extern char *get_inifile(); + if (!force_lpinfo[0]) GetPrivateProfileStringA("HTTP-AUTH", p, "", force_lpinfo, sizeof(force_lpinfo), get_inifile()); + if (!force_lpinfo[0] || (lpinfo && lpinfo[0])) + { + lstrcpynA(dlg_realm, p, sizeof(dlg_realm)); + if (!WASABI_API_DIALOGBOXPARAM(IDD_HTTPAUTH, GetDialogBoxParent(), httpDlgProc, (LPARAM)this)) + { + force_lpinfo[0] = 0; + } + else + { + WritePrivateProfileStringA("HTTP-AUTH", p, force_lpinfo, get_inifile()); + } + } + if (force_lpinfo[0]) + { + jnl_connection_release(m_connection); + m_connection = NULL; + doConnect(NULL, 0); + has = 1; + } + } + } + } + if (!has) + { + lastfn_status_err = 1; + jnl_connection_close(m_connection, 1); + } + break; + } + } + } + if (constate == 69) // redirect city + { + while (jnl_connection_receive_lines_available(m_connection) > 0) + { + jnl_connection_receive_line(m_connection, str, 4096); + if (!str[0]) + { + jnl_connection_close(m_connection, 1); break; + } + if (!_strnicmp(str, "Location:", 9)) + { + char *p = str + 9; while (p && *p == ' ') p++; + if (p && *p) + { + if (m_redircnt++ < MAX_REDIRECTS) + { + jnl_connection_release(m_connection); + m_connection = NULL; + doConnect(p, 0); + } + else + { + EnterCriticalSection(&g_lfnscs); + WASABI_API_LNGSTRING_BUF(IDS_REDIRECT_LIMIT_EXCEEDED,lastfn_status,256); + lastfn_status_err = 1; + LeaveCriticalSection(&g_lfnscs); + jnl_connection_close(m_connection, 1); + } + break; + } + } + } + } + if (constate == 1) + { + while (jnl_connection_receive_lines_available(m_connection) > 0) + { + jnl_connection_receive_line(m_connection, str, 4096); + + if (!str[0]) + { + if (config_http_save_dir[0] && (config_miscopts&16)) + { + if (!save_filename[0] && m_is_stream == 1 && + strlen(stream_title_save) > 4 && + !strstr(stream_title_save, "..") && + !strstr(stream_title_save, "\\") && + !strstr(stream_title_save, "/")) + { + lstrcpynA(save_filename, stream_title_save, 251); + char *p = strstr(save_filename, ".mp"); + if (!p) p = strstr(save_filename, ".MP"); + if (!p) p = strstr(save_filename, ".mP"); + if (!p) p = strstr(save_filename, ".Mp"); + if (!p) + { + StringCchCatA(save_filename, 256, ".mp3"); + } + else + { + while (p && *p && *p != ' ') p++; + if (p) *p = 0; + } + } + + if (save_filename[0]) + { + char buf[4096] = {0}; + StringCchPrintfA(buf, 4096, "%s%s%s", config_http_save_dir, config_http_save_dir[strlen(config_http_save_dir) - 1] == '\\' ? "" : "\\", save_filename); + hFile = CreateFileA(buf, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + } + } + else save_filename[0] = 0; + constate = meta_interval ? 4 : 2; + break; + } + + // check if stream is seekable + if (strlen(str) >= 14 && !_strnicmp(str, "Accept-Ranges:", 14)) + { + m_is_stream_seekable = true; + } + + if (!_strnicmp(str, "Content-Length:", 15)) + { + char *p = str + 15; while (p && *p == ' ') p++; + if (!is_stream_seek) + mpeg_length = atoi(p); + m_is_stream_seekable = true; + } + + if (!_strnicmp(str, "content-type:", 13)) + { + char *p = str + 13; while (p && *p == ' ') p++; + free(m_content_type); + m_content_type = _strdup(p); + if (!_strnicmp(m_content_type, "misc/ultravox",13)) + { + stream_title_save[0] = 0; + stream_name[0]=0; + g_stream_title[0] = 0; + is_uvox = 1; + // TODO get this to id as SC2 stream if possible... + m_is_stream = 3; + } + } + + if (!_strnicmp(str, "Server:", 7)) + { + char *p = str + 7; while (p && *p == ' ') p++; + lstrcpynA(server_name, p, sizeof(server_name)); + } + + if (!_strnicmp(str, "ultravox-max-msg:", 17)) + { + char *p = str + 17; while (p && *p == ' ') p++; + uvox_maxmsg = (p ? atoi(p) : 0); + if (uvox_maxmsg > UVOX_MAXMSG_CAP) // benski> security vulnerability fix, too high of value was causing an integer overflow (because we malloc uvox_maxmsg*2 + uvox_maxmsg = UVOX_MAXMSG_CAP; + m_is_stream = 3; + + } + else if (!_strnicmp(str, "Ultravox-SID:", 13)) + { + char *p = str + 13; while (p && *p == ' ') p++; + uvox_sid = (p ? atoi(p) : 0); + m_is_stream = 3; + } + if (!_strnicmp(str, "Ultravox-Avg-Bitrate:", 21)) + { + char *p = str + 21; while (p && *p == ' ') p++; + uvox_avgbr = (p ? atoi(p) : 0); + m_is_stream = 3; + } + if (!_strnicmp(str, "Ultravox-Max-Bitrate:", 21)) + { + char *p = str + 21; while (p && *p == ' ') p++; + uvox_maxbr = (p ? atoi(p) : 0); + m_is_stream = 3; + } + if (!_strnicmp(str, "Ultravox-Bitrate:", 17)) + { + char *p = str + 17; while (p && *p == ' ') p++; + uvox_avgbr = uvox_maxbr = (p ? atoi(p) : 0); + m_is_stream = 3; + } + if (!_strnicmp(str, "Ultravox-Title:", 15)) + { + char *p = str + 15; while (p && *p == ' ') p++; + lstrcpynA(stream_title_save, p, 580); + m_is_stream = 3; + } + if (!_strnicmp(str, "Ultravox-Genre:", 15)) + { + char *p = str + 15; while (p && *p == ' ') p++; + lstrcpynA(stream_genre, p, sizeof(stream_genre)); + m_is_stream = 3; + } + if (!_strnicmp(str, "Ultravox-URL:", 13)/* && !strstr(str, "shoutcast.com")*/) + { + char *p = str + 13; while (p && *p == ' ') p++; + lstrcpynA(stream_url, p, sizeof(stream_url)); + DWORD_PTR dw = 0; + if (stream_url[0]) SendMessageTimeout(mod.hMainWindow, WM_USER, (WPARAM)stream_url, IPC_MBOPEN, SMTO_NORMAL, 500, &dw); + m_is_stream = 3; + } + if (!_strnicmp(str, "Ultravox-Class-Type:", 19)) + { + // id our stream for 'streamtype' + // added for 5.63 as is a better way to check for + // a SC2/UVOX2.1 stream when no metadata is sent + m_is_stream = 5; + } + + if (!_strnicmp(str, "icy-notice2:", 12)) + { + char *p = str + 12; while (p && *p == ' ') p++; + char *p2 = (p ? strstr(p, "<BR>") : 0); + if (p2) + { + *p2 = 0; + lstrcpynA(server_name, p, sizeof(server_name)); + } + m_is_stream=2; + } + + if (!_strnicmp(str, "content-disposition:", 20)) + { + if (strstr(str, "filename=")) + lstrcpynA(g_stream_title, strstr(str, "filename=") + 9, sizeof(g_stream_title)); + lstrcpynA(stream_title_save, g_stream_title, 580); + } + + if (!_strnicmp(str, "icy-name:", 9)) + { + char *p = str + 9; while (p && *p == ' ') p++; + lstrcpynA(g_stream_title, p, sizeof(g_stream_title)); + lstrcpynA(stream_title_save, g_stream_title, 580); + lstrcpynA(stream_name, g_stream_title, 256); + // m_is_stream = 2; // benski> cut: some things use icy-name as a hack to show a title - not a reliable indicator of a SHOUTcast stream + } + + if (!_strnicmp(str, "icy-metaint:", 12)) + { + char *p = str + 12; while (p && *p == ' ') p++; + meta_interval = (p ? atoi(p) : 0); + m_is_stream = 2; + } + if (!_strnicmp(str, "icy-genre:", 10)) + { + char *p = str + 10; while (p && *p == ' ') p++; + lstrcpynA(stream_genre, p, sizeof(stream_genre)); + m_is_stream = 2; + } + if (!_strnicmp(str, "icy-url:", 8) && !strstr(str, "shoutcast.com")) + { + char *p = str + 8; while (p && *p == ' ') p++; + lstrcpynA(stream_url, p, sizeof(stream_url)); + //if (!strncmp(stream_url,"hTtP",4)) + //{ + // DWORD dw; + // SendMessageTimeout(mod.hMainWindow,WM_USER,(WPARAM)0,241,SMTO_NORMAL,500,&dw); + //} // Removed by Tag, Annoying. + DWORD_PTR dw = 0; + if (stream_url[0]) SendMessageTimeout(mod.hMainWindow, WM_USER, (WPARAM)stream_url, IPC_MBOPEN, SMTO_NORMAL, 500, &dw); + m_is_stream = 2; + } + } + } + + if (constate == 2) // check for id3v2 + { + if (jnl_connection_receive_bytes_available(m_connection) >= 10) + { + char buf[10]={0}; + constate = 4; + jnl_connection_peek(m_connection, buf, 10); + if (buf[0] == 'I' + && buf[1] == 'D' + && buf[2] == '3' + && buf[3] != 255 + && buf[4] != 255 + && buf[6] < 0x80 + && buf[7] < 0x80 + && buf[8] < 0x80 + && buf[9] < 0x80) + { + jnl_connection_receive(m_connection, buf, 10); + m_id3v2_len = 10; + if (buf[5] & 0x10) // check for footer flag + m_id3v2_len += 10; + m_id3v2_len += ((int)buf[6]) << 21; + m_id3v2_len += ((int)buf[7]) << 14; + m_id3v2_len += ((int)buf[8]) << 7; + m_id3v2_len += ((int)buf[9]); + if (m_id3v2_len < 0) m_id3v2_len = 0; + if (mpeg_length && m_id3v2_len > mpeg_length) + { + m_id3v2_len = 0; + } + if (m_id3v2_len) + { + constate = 3; + stream_id3v2_read = 0; + if (m_id3v2_len < 16*1024*1024) + { + stream_id3v2_buf = (char*)malloc(10 + m_id3v2_len); + stream_id3v2_read = 10; + memcpy(stream_id3v2_buf, buf, 10); + } + EnterCriticalSection(&g_lfnscs); + WASABI_API_LNGSTRING_BUF(IDS_READING_ID3,lastfn_status,256); + LeaveCriticalSection(&g_lfnscs); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + } + } + } + } + + if (constate == 3) // id3v2 found + { + while ((int)stream_id3v2_read < m_id3v2_len) + { + char buf[1024]={0}; + int btoread = m_id3v2_len - stream_id3v2_read; + if (btoread > sizeof(buf)) btoread = sizeof(buf); + + int avail = (int)jnl_connection_receive_bytes_available(m_connection); + if (btoread > avail) btoread = avail; + if (!btoread) break; + + if (stream_id3v2_buf) + stream_id3v2_read += (uint32_t)jnl_connection_receive(m_connection, stream_id3v2_buf + stream_id3v2_read, btoread); + else + stream_id3v2_read += (uint32_t)jnl_connection_receive(m_connection, buf, btoread); + //stream_id3v2_read+=m_connection->ReceiveBytes(0,btoread); + } + + if ((int)stream_id3v2_read >= m_id3v2_len) + { + if (stream_id3v2_buf) + { + if (m_is_stream_seekable /*m_is_stream != 2*/) /* don't want to do id3v2 on an internet stream */ + { + EnterCriticalSection(&streamInfoLock); + info.Decode(stream_id3v2_buf, stream_id3v2_read); + // TODO: streamInfo = info; + LeaveCriticalSection(&streamInfoLock); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + } + } + constate = 4; + } + } + + if (constate == 4) // check for xing header + { + if (GetContentLength() < 1 || is_stream_seek) constate = 5; + else + { + int avail = (int)jnl_connection_receive_bytes_available(m_connection); + if (avail > 4096 || jnl_connection_get_state(m_connection) == JNL_CONNECTION_STATE_CLOSED) + { + char buf[4096]={0}; + jnl_connection_peek(m_connection, buf, sizeof(buf)); + constate++; + { + delete m_vbr_hdr; + m_vbr_hdr = 0; + LAMEinfo lame; + lame.toc = m_vbr_toc; + m_vbr_frame_len = ReadLAMEinfo((unsigned char *)buf, &lame); + if (m_vbr_frame_len) + { + prepad = lame.encoderDelay; + postpad = lame.padding; + if (lame.flags&TOC_FLAG) + { + int x; + for (x = 0; x < 100; x++) + if (m_vbr_toc[x]) break; + if (x != 100) + m_vbr_flag = 1; + } + if (lame.flags&FRAMES_FLAG) + { + m_vbr_frames = lame.frames; + m_vbr_samples = Int32x32To64(lame.frames, lame.h_id ? 1152 : 576); + m_vbr_samples -= (prepad + postpad); + m_vbr_ms = MulDiv((int)m_vbr_samples, 1000, lame.samprate); + } + if (!m_vbr_frames) m_vbr_flag = 0; + jnl_connection_receive(m_connection, buf, m_vbr_frame_len); + } + else + { + m_vbr_hdr = new CVbriHeader; + m_vbr_frame_len = m_vbr_hdr->readVbriHeader((unsigned char *)buf); + if (m_vbr_frame_len) + { + m_vbr_bytes = m_vbr_hdr->getBytes(); + m_vbr_frames = m_vbr_hdr->getNumFrames(); + m_vbr_ms = m_vbr_hdr->getNumMS(); + } + else + { + delete m_vbr_hdr; + m_vbr_hdr = 0; + } + } + } + // TODO OFL + } + } + } + + if (constate == 5) // time to stream + { + while (1) + { + // Process any timed titles + + int len = (int)jnl_connection_receive_bytes_available(m_connection); + if (meta_interval && meta_pos >= meta_interval) + { + unsigned char b; + if (len > 0 && jnl_connection_peek(m_connection, (char*)&b, 1) && len > (b << 4)) + { + char metabuf[4096]={0}; + jnl_connection_receive(m_connection, metabuf, 1); + jnl_connection_receive(m_connection, metabuf, b << 4); + processMetaData(metabuf, b << 4); + stream_metabytes_read += (b << 4) + 1; + meta_pos = 0; + } + else break; + } + else if (is_uvox) + { + if (!uvox_stream_data) + { + /* benski> this was a security vulnerability. + don't blindly multiply by 2 and pass to malloc + if uvox_maxmsg happens to be 2^31, multiplying by 2 turns it into 0! + CUT!!! uvox_stream_data = (unsigned char*)malloc(uvox_maxmsg * 2 + 4096); + */ + uvox_stream_data = (unsigned char*)malloc(SAFE_MALLOC_MATH(uvox_maxmsg * 2 + 4096, uvox_maxmsg)); + } + if (uvox_stream_data_len < 1) + { +again: + if (len < 6) break; + jnl_connection_peek(m_connection, (char*)uvox_stream_data, 6); + + int uvox_qos = uvox_stream_data[1]; + int classtype = (uvox_stream_data[2] << 8) | uvox_stream_data[3]; + int uvox_len = (uvox_stream_data[4] << 8) | uvox_stream_data[5]; + + int uvox_class = (classtype >> 12) & 0xF; + int uvox_type = classtype & 0xFFF; + + if (uvox_stream_data[0] != 0x5A || uvox_len > uvox_maxmsg) + { + jnl_connection_receive(m_connection, NULL, 1); + len--; + uvox_desyncs++; + + goto again; + } + + if (uvox_len + 7 > len) break; + // process uvox chunk + + jnl_connection_peek(m_connection, (char*)uvox_stream_data, 6 + uvox_len + 1); + if (uvox_stream_data[6 + uvox_len]) + { + jnl_connection_receive(m_connection, NULL, 1); + uvox_desyncs++; + len--; + goto again; + } + else if (uvox_class == 0x2 && uvox_type == 0x003) + { + // failover gayness + } + else if (uvox_class == 0x2 && uvox_type == 0x002) + { + EnterCriticalSection(&g_lfnscs); + WASABI_API_LNGSTRING_BUF(IDS_STREAM_TERMINATED,lastfn_status,256); + LeaveCriticalSection(&g_lfnscs); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + + jnl_connection_close(m_connection, 0); + } + else if (uvox_class == 0x2 && uvox_type == 0x001) + { + EnterCriticalSection(&g_lfnscs); + WASABI_API_LNGSTRING_BUF(IDS_STREAM_TEMPORARILY_INTERRUPTED,lastfn_status,256); + LeaveCriticalSection(&g_lfnscs); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + } + + else if (uvox_class == 0x3 && (uvox_type == 0x902 || uvox_type == 0x901)) // SHOUTcast 2 metadata + { + // id our stream for 'streamtype' + m_is_stream = 5; + + // this will allow us to cope with multi-packet metadata messages + // (used to be needed when the old SC2 spec used an APIC tag to + // to send the artwork in the metadata message - now it's sent in + // in the uvox_class == 0x4 messages for stream and playing cases) + if (!uvox_stream_data[8]) + { + char *mbuf = (char*)uvox_stream_data + 12; + if (mbuf) + { + unsigned long delay = 0; + + if (uvox_avgbr) + { + long byterate = (uvox_avgbr / 8); + long bytes = 1024 * 11; + + float localdelay = (float)bytes / (float)byterate; + delay = (long)localdelay * 1000; + } + + // make sure that we've got a packet which is within the specs + // as id can only be 1-32, total parts can only be 1-32, + // id cannot be more than total parts and if not then skip it. + if(uvox_stream_data[11] < 1 || uvox_stream_data[11] > 32 || + uvox_stream_data[11] > uvox_stream_data[9] || + uvox_stream_data[9] < 1 || uvox_stream_data[9] > 32) + { + break; + } + + TITLELISTTYPE *newtitle = newTitleListEntry(); + newtitle->style = (uvox_type == 0x902 ? UVOX_METADATA_STYLE_SHOUTCAST2 : UVOX_METADATA_STYLE_AOLRADIO); + // we make sure to only copy up to the maximum metadata payload size + newtitle->part_len = min((uvox_len - 6), 16371); + memcpy(newtitle->title, mbuf, newtitle->part_len); + newtitle->part = uvox_stream_data[11]; + newtitle->total_parts = uvox_stream_data[9]; + newtitle->timer = (stream_bytes_read && mod.outMod ? delay + mod.outMod->GetOutputTime() : 0); + } + } + } + + else if (uvox_class == 0x4) // SHOUTcast 2 albumart + { + if (allow_scartwork && !uvox_stream_data[8]) + { + char *mbuf = (char*)uvox_stream_data + 12; + if (mbuf) + { + // [0x4] [0|1] [00|01|02|03] + unsigned long delay = 0; + + if (uvox_avgbr) + { + long byterate = (uvox_avgbr / 8); + long bytes = 1024 * 11; + + float localdelay = (float)bytes / (float)byterate; + delay = (long)localdelay * 1000; + } + + // make sure that we've got a packet which is within the specs + // as id can only be 1-32, total parts can only be 1-32, + // id cannot be more than total parts and if not then skip it. + if(uvox_stream_data[11] < 1 || uvox_stream_data[11] > 32 || + uvox_stream_data[11] > uvox_stream_data[9] || + uvox_stream_data[9] < 1 || uvox_stream_data[9] > 32) + { + break; + } + + TITLELISTTYPE *newtitle = newTitleListEntry(); + newtitle->style = (uvox_type & 0x100 ? UVOX_METADATA_STYLE_SHOUTCAST2_ARTWORK_PLAYING : UVOX_METADATA_STYLE_SHOUTCAST2_ARTWORK); + // we make sure to only copy up to the maximum metadata payload size + newtitle->part_len = min((uvox_len - 6), 16371); + memcpy(newtitle->title, mbuf, newtitle->part_len); + newtitle->part = uvox_stream_data[11]; + newtitle->total_parts = uvox_stream_data[9]; + newtitle->type = (uvox_type & 0x00FF); + newtitle->timer = (stream_bytes_read && mod.outMod ? delay + mod.outMod->GetOutputTime() : 0); + } + } + } + + else if (uvox_class == 0x3 && (uvox_type == 0x001 || uvox_type == 0x002)) + { + int w = uvox_type - 1; // should be ID? + int n = (uvox_stream_data[8] << 8) | uvox_stream_data[9]; + if (n && n < 33) + { + int t = (uvox_stream_data[10] << 8) | uvox_stream_data[11]; + if (t && t <= n) + { + int l = 0; + int a; + char *p = (char*)uvox_stream_data + 12; // this almost works + free(uvox_meta[w][t - 1]); + uvox_meta[w][t - 1] = _strdup(p); + for (a = 0;a < n;a++) + { + if (!uvox_meta[w][a]) break; + l += (int)strlen(uvox_meta[w][a]); + } + if (a == n) + { + char *outtext = (char*)malloc(l + 1); + p = outtext; + for (a = 0;a < n;a++) + { + lstrcpynA(p, uvox_meta[w][a], l + 1); + free(uvox_meta[w][a]); + uvox_meta[w][a] = 0; + p += strlen(p); + } + processMetaData(outtext, l + 1); + free(outtext); + } + } + } + } + + else if ((uvox_class == 0x7 && (uvox_type == 0x0 || uvox_type == 0x1)) + || (uvox_class == 0x8 && (uvox_type == 0x0 || uvox_type == 0x1 || uvox_type == 0x3))) + { + memcpy(uvox_stream_data, uvox_stream_data + 6, uvox_len); + uvox_stream_data_len = uvox_len; + uvox_last_message = uvox_class << 12 | uvox_type; + } + jnl_connection_receive(m_connection, NULL, 6 + uvox_len + 1); + + uvox_message_cnt++; + } + if (uvox_stream_data_len > 0) + { + len = min(cbToRead, uvox_stream_data_len); + + memcpy(pBuffer, uvox_stream_data, len); + if (pcbRead) *pcbRead = len; + stream_bytes_read += len; + if (len < uvox_stream_data_len) + { + memcpy(uvox_stream_data, uvox_stream_data + len, uvox_stream_data_len - len); + } + uvox_stream_data_len -= len; + break; + } + } + else + { + len = min(cbToRead, len); + if (meta_interval) len = min(meta_interval - meta_pos, len); + if (len > 0) + { + DWORD dw = 0; + len = (int)jnl_connection_receive(m_connection, (char*)pBuffer, len); + if (hFile != INVALID_HANDLE_VALUE) WriteFile(hFile, pBuffer, len, &dw, NULL); + if (pcbRead) *pcbRead = len; + meta_pos += len; + stream_bytes_read += len; + } + else if (pcbRead) *pcbRead = 0; + break; + } + } + } + + int state = m_connection ? jnl_connection_get_state(m_connection) : JNL_CONNECTION_STATE_CLOSED; + if (state == JNL_CONNECTION_STATE_ERROR || state == JNL_CONNECTION_STATE_CLOSED) + { + if ((!pcbRead || !*pcbRead) && (!m_connection || jnl_connection_receive_bytes_available(m_connection) < 1)) + { + fEof = 1; + return NErr_Error; + } + } + else if (constate != 5 && constate != 4 && constate != 3 && timeout_start + 15000 < GetTickCount()) // 15 second net connect timeout + { + EnterCriticalSection(&g_lfnscs); + WASABI_API_LNGSTRING_BUF(IDS_TIMED_OUT,lastfn_status,256); + lastfn_status_err = 1; + LeaveCriticalSection(&g_lfnscs); + PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + fEof = 1; + + return NErr_Error; + } + return NErr_Success; + } + return NErr_Error; + } + else if (m_full_buffer) + { + int len = cbToRead; + if ((uint64_t)len > (mpeg_length - m_full_buffer_pos)) + { + len = (int)(mpeg_length - m_full_buffer_pos); + } + if (pcbRead) *pcbRead = len; + if (len) + { + memcpy(pBuffer, m_full_buffer + m_full_buffer_pos, len); + m_full_buffer_pos += len; + } + else + { + fEof = true; + return NErr_EndOfFile; + } + return NErr_Success; + } + else + { + if (hFile != INVALID_HANDLE_VALUE) + { + if ((uint64_t)cbToRead >= (mpeg_length - file_position)) + { + cbToRead = (int)(mpeg_length - file_position); + fEof = true; + } + + if (cbToRead == 0) + { + if (pcbRead) + *pcbRead = 0; + return NErr_Success; + } + + DWORD dwRead = 0; + bSuccess = ReadFile(hFile, pBuffer, cbToRead, &dwRead, NULL); + + if (bSuccess) + { + file_position += dwRead; + // update pcbRead + if (pcbRead) + *pcbRead = dwRead; + + // check for EOF + if (dwRead == 0) + fEof = true; + + return NErr_Success; + } + else + { + // error reading from file + return NErr_Error; + } + } + else + { + // no valid file handle + return NErr_Error; + } + } +} + +//-------------------------------------------------------------------------* +// IsEof +//-------------------------------------------------------------------------* + +bool CGioFile::IsEof() const +{ + return fEof; +} + +//-------------------------------------------------------------------------* +// GetContentLength +//-------------------------------------------------------------------------* + +DWORD CGioFile::GetContentLength(void) const +{ + DWORD dwSize = 0 ; + + dwSize = (DWORD)mpeg_length; + + return dwSize ; +} + +//-------------------------------------------------------------------------* +// GetCurrentPosition +//-------------------------------------------------------------------------* + +DWORD CGioFile::GetCurrentPosition(void) const +{ + DWORD dwPos = 0; + + if (m_is_stream) + { + dwPos = (DWORD)stream_bytes_read; + } + else if (m_full_buffer) + { + dwPos = (DWORD)m_full_buffer_pos; + } + else if (hFile != INVALID_HANDLE_VALUE) + { + dwPos = SetFilePointer(hFile, 0, NULL, FILE_CURRENT); + dwPos -= (DWORD)(mpeg_position + peekBuffer.size()); + } + + return dwPos ; +} + +//-------------------------------------------------------------------------* +// SetCurrentPosition +//-------------------------------------------------------------------------* + +void CGioFile::SetCurrentPosition(long dwPos, int How) // GIO_FILE_BEGIN only +{ + fEof = false; + if (m_full_buffer) + { + m_full_buffer_pos = dwPos; + if (m_full_buffer_pos < 0) m_full_buffer_pos = 0; + if (m_full_buffer_pos > mpeg_length) m_full_buffer_pos = mpeg_length; + } + else if (hFile != INVALID_HANDLE_VALUE) + { + Seek64(hFile, mpeg_position + dwPos, FILE_BEGIN); + file_position = dwPos; + peekBuffer.clear(); + } +} + +int CGioFile::GetHeaderOffset() +{ + return (int)mpeg_position; +} +/*-------------------------------------------------------------------------*/ + +void CGioFile::Seek(int posms, int br) +{ + int offs = 0; + if (m_vbr_hdr) + { + offs = m_vbr_hdr->seekPointByTime((float)posms); + } + else if (!m_vbr_flag) + offs = MulDiv(posms, br, 8); + else + { + int ms = 0; + int fl = GetContentLength(); + if (!m_vbr_frames) + { + ms = MulDiv(fl, 8 * 1000, br); + } + else ms = m_vbr_ms; + offs = SeekPoint(m_vbr_toc, fl, (float)posms / ((float)ms / 100.0f)); + } + if (m_is_stream) + { + if (GetContentLength() > 0) + { + if (m_connection && m_is_stream_seekable) + { + jnl_connection_release(m_connection); + m_connection = NULL; + m_is_stream_seek = true; + m_seek_reset = false; + doConnect(NULL, offs); + } + } + } + else + { + SetCurrentPosition(offs, GIO_FILE_BEGIN); + } +} + +bool CGioFile::IsSeekable() +{ + return !m_is_stream || GetContentLength() > 0; +} + +void CGioFile::GetStreamInfo(wchar_t *obuf, size_t len) +{ + if (m_is_stream) + { + wchar_t langbuf[2048]={0}; + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_NETWORK_RECEIVED_X_BYTES, langbuf, 2048), stream_bytes_read + stream_metabytes_read); + if (server_name[0]) + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_SERVER, langbuf, 2048), AutoWide(server_name,CP_UTF8)); + if (m_content_type && m_content_type[0]) + { + if(is_uvox) + { + // report the actual content type instead of just misc/ultravox to make it easier to see what the stream type is (helps debugging) + static const int MP3_DATA = 0x7000; + static const int VLB_DATA = 0x8000; + static const int AAC_LC_DATA = 0x8001; + static const int AACP_DATA = 0x8003; + static const int OGG_DATA = 0x8004; + switch(uvox_last_message) + { + case MP3_DATA: + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), L"audio/mpeg"); + break; + + case VLB_DATA: + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), L"audio/vlb"); + break; + + case AAC_LC_DATA: + case AACP_DATA: + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), L"audio/aacp"); + break; + + case OGG_DATA: + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), L"audio/ogg"); + break; + + default: + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), AutoWide(m_content_type,CP_UTF8)); + break; + } + } + else + { + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), AutoWide(m_content_type,CP_UTF8)); + } + } + + if (is_uvox) + { + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_ULTRAVOX_SYNC, langbuf, 2048), uvox_message_cnt, uvox_desyncs); + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_ULTRAVOX_DATA_MESSAGE, langbuf, 2048), uvox_last_message); + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_ULTRAVOX_SID_AVGBR_MAXBR, langbuf, 2048), uvox_sid, uvox_avgbr, uvox_maxbr); + } + + if (stream_metabytes_read) + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_METADATA_RECEIVED, langbuf, 2048), stream_metabytes_read); + if (meta_interval) + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_METADATA_INTERVAL, langbuf, 2048), meta_interval); + if (m_id3v2_len) + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_ID3v2_TAG, langbuf, 2048), m_id3v2_len); + if (m_vbr_frame_len) + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_VBR_LEADING_FRAME, langbuf, 2048), m_vbr_frame_len); + if (stream_title_save[0]) + { + wchar_t name[580]={0}; + ConvertTryUTF8(stream_title_save, name, 580); + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_STREAM_NAME, langbuf, 2048), name/*AutoWide(stream_title_save,CP_UTF8)*/); + } + if (last_title_sent[0]) + { + wchar_t title[256]={0}; + ConvertTryUTF8(last_title_sent, title, 256); + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CURRENT_TITLE, langbuf, 2048), title); + } + + if (mpeg_length) + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_LENGTH, langbuf, 2048), mpeg_length); + if (save_filename[0] && hFile != INVALID_HANDLE_VALUE) + StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_SAVING_TO, langbuf, 2048), AutoWide(save_filename,CP_UTF8)); + } +} + +static inline const wchar_t *IncSafe(const wchar_t *val, int x) +{ + while (x--) + { + if (val && *val) + val++; + } + return val; +} + +void CGioFile::ReadiTunesGaps() +{ + if (info.HasData() && !prepad && !postpad) + { + wchar_t str[128] = {0}; + if (info.GetString("pregap", str, 128) == 1) + prepad = _wtoi(str); + + str[0]=0; + if (info.GetString("postgap", str, 128) == 1) + postpad = _wtoi(str); + } +} + +#define CBCLASS CGioFile +START_DISPATCH; +CB( MPEGSTREAM_PEEK, Peek ) +CB( MPEGSTREAM_READ, Read ) +CB( MPEGSTREAM_EOF, EndOf ) +CB( MPEGSTREAM_GAIN, GetGain ) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/giofile.h b/Src/Plugins/Input/in_mp3/giofile.h new file mode 100644 index 00000000..121d77c9 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/giofile.h @@ -0,0 +1,306 @@ + +/***************************************************************************\ + * + * (C) copyright Fraunhofer - IIS (1998) + * All Rights Reserved + * + * filename: giofile.h + * project : MPEG Decoder + * author : Martin Sieler + * date : 1998-02-11 + * contents/description: HEADER - file I/O class for MPEG Decoder + * + * +\***************************************************************************/ + +#ifndef _GIOFILE_H +#define _GIOFILE_H + +/* ------------------------ includes --------------------------------------*/ +#include <windows.h> + +#include "CVbriHeader.h" +#include "jnetlib/jnetlib.h" + +#include "../vlb/dataio.h" +#include "ID3v2.h" +#include "LAMEInfo.h" +#include "../nu/RingBuffer.h" +#include "../apev2/tag.h" +#include "ifc_mpeg_stream_reader.h" + +/*-------------------------- defines --------------------------------------*/ + +/*-------------------------------------------------------------------------*/ + +class CGioFile : public DataIOControl, public ifc_mpeg_stream_reader +{ +public: + CGioFile(); + virtual ~CGioFile(); + + int Open(const wchar_t *pszName, int maxbufsizek); + int Close(); + int Read(void *pBuffer, int cbToRead, int *pcbRead); + int Peek(void *pBuffer, int cbToRead, int *pcbRead); + + //dataiocontrol interface + int IO(void *buf, int size, int count) + { + int l=0; + Read(buf,count,&l); + return l; + } + int Seek(long offset, int origin) + { + return 0; + } + //int Close() { return 0; } + int EndOf(void) + { + return IsEof(); + } + int DICGetLastError() + { + return DATA_IO_ERROR_NONE; + } + int DICGetDirection() + { + return DATA_IO_READ; + } + + unsigned int GetAvgVBRBitrate(void) + { + if (m_vbr_ms && m_vbr_frames) + { + if (m_vbr_bytes && encodingMethod != ENCODING_METHOD_CBR) + return (unsigned int)(m_vbr_bytes * 8 / m_vbr_ms); + else + return (unsigned int)(mpeg_length * 8 / m_vbr_ms); + } + return 0; + } + bool IsEof() const; + bool lengthVerified; + bool isSeekReset() + { + + if (m_is_stream && m_seek_reset && m_is_stream_seek) + { + m_seek_reset = false; + m_is_stream_seek = false; + return true; + } + else + return false; + } + + bool IsStreamSeekable() + { + return m_is_stream_seekable; + } + + int GetHeaderOffset(); + + int PercentAvailable() + { + if (!m_is_stream) return 0; + if (!recvbuffersize) return 0; + if (!m_connection) return 0; + if (constate != 5) return 0; + uint64_t bytes_100 = jnl_connection_receive_bytes_available(m_connection)*100; + bytes_100 /= recvbuffersize; + return (int)bytes_100; + } + + int RunStream() + { + if (m_is_stream && m_connection) + { + if (constate != 5) Read(NULL,0,NULL); + else jnl_connection_run(m_connection, -1, -1, NULL, NULL); + int p=jnl_connection_get_state(m_connection); + if (p==JNL_CONNECTION_STATE_ERROR|| + p==JNL_CONNECTION_STATE_CLOSED) + return 2; + return 1; + } + return 0; + } + int IsStream() + { + return m_is_stream; + } + void GetStreamInfo(wchar_t *, size_t len); + + DWORD GetContentLength(void) const; + DWORD GetCurrentPosition(void) const; + void SetCurrentPosition(long dwPos, int How); + + enum { GIO_FILE_BEGIN, GIO_FILE_CURRENT, GIO_FILE_END }; + + uint64_t mpeg_length; /* length of valid audio data */ + uint64_t mpeg_position; /* starting position of first valid decodable non-header MPEG frame */ + uint64_t file_position; /* position within the MPEG data that we've read so far */ + + uint64_t m_vbr_bytes; + int m_vbr_frames, m_vbr_ms; + int encodingMethod; + uint64_t m_vbr_samples; + int prepad, postpad; + void Seek(int posms, int br); + bool IsSeekable(); + + unsigned char id3v1_data[128]; + + char stream_url[256]; + char stream_name[256]; + char stream_genre[256]; + char stream_current_title[256]; + + char *m_content_type; + int uvox_last_message; + char *uvox_3901; + char *uvox_3902; + + typedef struct { + char *uvox_stream_artwork; + int uvox_stream_artwork_len; + int uvox_stream_artwork_type; + char *uvox_playing_artwork; + int uvox_playing_artwork_len; + int uvox_playing_artwork_type; + } UVOX_ARTWORK; + UVOX_ARTWORK uvox_artwork; + + ID3v2 info; + float GetGain(); + unsigned char m_vbr_toc[100]; + int m_vbr_frame_len; + int m_vbr_flag; + CVbriHeader *m_vbr_hdr; + + FILETIME last_write_time; + + void *GetID3v1() + { + if (m_id3v1_len == 128) + return id3v1_data; + else + return 0; + } + + void *GetID3v2(uint32_t *len) + { + if (stream_id3v2_buf) + { + *len = stream_id3v2_read; + return stream_id3v2_buf; + } + else + return 0; + } + + void *GetLyrics3(uint32_t *len) + { + if (lyrics3_data) + { + *len = lyrics3_size; + return lyrics3_data; + } + else + return 0; + } + + void *GetAPEv2(uint32_t *len) + { + if (apev2_data) + { + *len = m_apev2_len; + return apev2_data; + } + return 0; + } + +protected: + void ReadiTunesGaps(); +private: + /* ID3v2 */ + int m_id3v1_len; + + /* ID3v2 */ + uint32_t stream_id3v2_read; + char *stream_id3v2_buf; + int m_id3v2_len; + + /* Lyrics3 */ + uint32_t lyrics3_size; + char *lyrics3_data; + + /* APEv2 */ + uint32_t m_apev2_len; + char *apev2_data; + APEv2::Tag apev2; + + int m_is_stream; + jnl_dns_t m_dns; + jnl_connection_t m_connection; + char last_full_url[4096]; + int is_stream_seek; + char *host; + char *proxy_host; + char *req; + unsigned short port; + char *lpinfo; + char *proxy_lp; + char *request; + int constate; + char save_filename[256]; + char server_name[128]; + int recvbuffersize; + + int64_t stream_bytes_read; + int64_t stream_metabytes_read; + unsigned int timeout_start; + char force_lpinfo[256]; + unsigned char *m_full_buffer; + int is_uvox; + int uvox_stream_data_len; + int uvox_message_cnt, uvox_desyncs; + int uvox_sid,uvox_maxbr, uvox_avgbr,uvox_maxmsg; + + unsigned char *uvox_stream_data; + + char *uvox_meta[2][32]; + int meta_interval,meta_pos; + uint64_t m_full_buffer_pos/*,m_full_buffer_len*/; + char stream_title_save[580]; + char last_title_sent[256]; + + RingBuffer peekBuffer; + //unsigned char m_peekbuf[8192]; + //int m_peekbuf_used; + bool no_peek_hack; + + int doConnect(const char *str, int start_offset); + void processMetaData(char *data, int lent, int msgId = 0); + + int m_redircnt; + int m_auth_tries; + + HANDLE hFile; + bool fEof; + + + char dlg_realm[256]; + static INT_PTR CALLBACK httpDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam); + int m_http_response; + bool m_seek_reset; + bool m_is_stream_seek; + bool m_is_stream_seekable; + bool m_useaproxy; + RECVS_DISPATCH; +}; + +/*-------------------------------------------------------------------------*/ +#endif diff --git a/Src/Plugins/Input/in_mp3/graphics/filterWater.cpp b/Src/Plugins/Input/in_mp3/graphics/filterWater.cpp new file mode 100644 index 00000000..d6128738 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/graphics/filterWater.cpp @@ -0,0 +1,452 @@ +#include ".\filterwater.h" +#include <math.h> + +#define random( min, max ) (( rand() % (int)((( max ) + 1 ) - ( min ))) + ( min )) + +MLImageFilterWater::MLImageFilterWater(void) +{ + hField1 = NULL; + hField2 = NULL; + hHandle = NULL; + + width = 0; + height = 0; + + drawWithLight = TRUE; + lightModifier = 1; + hPage = 0; + density = 5; + +} + +MLImageFilterWater::~MLImageFilterWater(void) +{ + ClearData(); +} + +void MLImageFilterWater::ClearData(void) +{ + if (hHandle) + { + if (hField1) HeapFree(hHandle, NULL, hField1); + if (hField2) HeapFree(hHandle, NULL, hField2); + HeapDestroy(hHandle); + hField1 = NULL; + hField2 = NULL; + hHandle = NULL; + } + +} + +BOOL MLImageFilterWater::CreateFor(const MLImage *image) +{ + ClearData(); + width = image->GetWidth(); + height = image->GetHeight(); + hPage = 0; + int len = height * width * sizeof(int); + hHandle = HeapCreate(NULL, 3*len, 3*len); + if (!hHandle) + { + width = 0; + height = 0; + return FALSE; + } + + hField1 = (int*)HeapAlloc(hHandle, HEAP_ZERO_MEMORY, len); + hField2 = (int*)HeapAlloc(hHandle, HEAP_ZERO_MEMORY, len); + return hField1 && hField2; +} + +void MLImageFilterWater::Render(MLImage* destination, const MLImage* source) +{ + if(!drawWithLight) DrawWaterNoLight(hPage, destination, source); + else DrawWaterWithLight(destination, source); + CalculateWater(hPage, density); +// CalcWaterBigFilter(hPage, density); + hPage ^= 1; +} + +void MLImageFilterWater::CalculateWater(int page, int density) +{ + int newh; + int count = width + 1; + int *newptr; + int *oldptr; + + if(page == 0) + { + newptr = hField1; + oldptr = hField2; + } + else + { + newptr = hField2; + oldptr = hField1; + } + + int x, y; + for (y = (height - 1) * width; count < y; count += 2) + { + for (x = count + width - 2; count < x; count++) + { + // This does the eight-pixel method. + + newh = ((oldptr[count + width] + + oldptr[count - width] + + oldptr[count + 1] + + oldptr[count - 1] + + oldptr[count - width - 1] + + oldptr[count - width + 1] + + oldptr[count + width - 1] + + oldptr[count + width + 1] + ) >> 2 ) + - newptr[count]; + newptr[count] = newh - (newh >> density); + /* + // This is the "sludge" method... + newh = (oldptr[count]<<2) + + oldptr[count-1-m_iWidth] + + oldptr[count+1-m_iWidth] + + oldptr[count-1+m_iWidth] + + oldptr[count+1+m_iWidth] + + ((oldptr[count-1] + + oldptr[count+1] + + oldptr[count-m_iWidth] + + oldptr[count+m_iWidth])<<1); + + newptr[count] = (newh-(newh>>6)) >> density; + */ + } + } +} + +void MLImageFilterWater::SmoothWater(int page) +{ + int newh; + int count = width + 1; + + int *newptr; + int *oldptr; + + if(page == 0) + { + newptr = hField1; + oldptr = hField2; + } + else + { + newptr = hField2; + oldptr = hField1; + } + + int x, y; + + for(y = 1; y < height; y++, count += 2) + { + for( x = 1; x < width; x++, count++) + { + // This does the eight-pixel method. + + newh = ((oldptr[count + width] + + oldptr[count - width] + + oldptr[count + 1] + + oldptr[count - 1] + + oldptr[count - width - 1] + + oldptr[count - width + 1] + + oldptr[count + width - 1] + + oldptr[count + width + 1] + ) >> 3 ) + + newptr[count]; + newptr[count] = newh>>1; + } + } +} +void MLImageFilterWater::FlattenWater(void) +{ + int len = width * height * sizeof(int); + SecureZeroMemory(hField1, len); + SecureZeroMemory(hField2, len); +} +void MLImageFilterWater::SineBlob(int x, int y, int radius, int height, int page) +{ + int cx, cy; + int left,top,right,bottom; + double square, dist; + double radsquare = radius * radius; + double length = double((1024.0/(double)radius)*(1024.0/(double)radius)); + int *newptr; + + if(page == 0) + { + newptr = hField1; + } + else + { + newptr = hField2; + } + + int t = (this->width - 2*radius - 1); + if (t == 0) t = 1; + if(x<0) x = 1 + radius + rand() % t; + t = (this->height - 2*radius - 1); + if (t == 0) t = 1; + if(y<0) y = 1 + radius + rand() % t; + + radsquare = (radius*radius); + + left = -radius; right = radius; + top = -radius; bottom = radius; + + // Perform edge clipping... + if(x - radius < 1) left -= (x-radius-1); + if(y - radius < 1) top -= (y-radius-1); + if(x + radius > this->width - 1) right -= (x + radius - this->width + 1); + if(y + radius > this->height - 1) bottom -= (y + radius - this->height + 1); + + for(cy = top; cy < bottom; cy++) + { + for(cx = left; cx < right; cx++) + { + square = cy*cy + cx*cx; + if(square < radsquare) + { + dist = sqrt(square*length); + newptr[this->width*(cy+y) + cx+x] += (int)((cos(dist)+0xffff)*(height)) >> 19; + } + } + } +} + +void MLImageFilterWater::WarpBlob(int x, int y, int radius, int height, int page) +{ + int cx, cy; + int left,top,right,bottom; + int square; + int radsquare = radius * radius; + int *newptr; + + if(page == 0) + { + newptr = hField1; + } + else + { + newptr = hField2; + } + + radsquare = (radius*radius); + + height /= 64; + + left=-radius; right = radius; + top=-radius; bottom = radius; + + // Perform edge clipping... + if(x - radius < 1) left -= (x-radius-1); + if(y - radius < 1) top -= (y-radius-1); + if(x + radius > this->width-1) right -= (x+ radius - this->width + 1); + if(y + radius > this->height-1) bottom-= (y + radius - this->height + 1); + + for(cy = top; cy < bottom; cy++) + { + for(cx = left; cx < right; cx++) + { + square = cy*cy + cx*cx; + if(square < radsquare) + { + newptr[this->width*(cy+y) + cx+x] += int((radius-sqrt((float)square))*(float)(height)); + } + } + } +} +void MLImageFilterWater::HeightBox (int x, int y, int radius, int height, int page) +{ + int cx, cy; + int left,top,right,bottom; + int *newptr; + + if(page == 0) + { + newptr = hField1; + } + else + { + newptr = hField2; + } + + int t = (this->width - 2*radius - 1); + if (t == 0) t = 1; + if(x<0) x = 1 + radius + rand() % t; + t = (this->height - 2*radius - 1); + if (t == 0) t = 1; + if(y<0) y = 1 + radius + rand() % t; + + left=-radius; right = radius; + top=-radius; bottom = radius; + + // Perform edge clipping... + if(x - radius < 1) left -= (x-radius-1); + if(y - radius < 1) top -= (y-radius-1); + if(x + radius > this->width-1) right -= (x+ radius - this->width + 1); + if(y + radius > this->height-1) bottom-= (y + radius - this->height + 1); + + for(cy = top; cy < bottom; cy++) + { + for(cx = left; cx < right; cx++) + { + newptr[this->width*(cy+y) + cx+x] = height; + } + } +} +void MLImageFilterWater::HeightBlob(int x, int y, int radius, int height, int page) +{ + int rquad; + int cx, cy; + int left, top, right, bottom; + + rquad = radius * radius; + + // Make a randomly-placed blob... + int t = (this->width - 2*radius - 1); + if (t == 0) t = 1; + if(x<0) x = 1 + radius + rand() % t; + t = (this->height - 2*radius - 1); + if (t == 0) t = 1; + if(y<0) y = 1 + radius + rand() % t; + + left = -radius; right = radius; + top = -radius; bottom = radius; + + // Perform edge clipping... + if(x - radius < 1) left -= (x-radius-1); + if(y - radius < 1) top -= (y-radius-1); + if(x + radius > this->width-1) right -= (x+ radius - this->width + 1); + if(y + radius > this->height-1) bottom-= (y + radius - this->height + 1); + + for(cy = top; cy < bottom; cy++) + { + int cyq = cy*cy; + for(cx = left; cx < right; cx++) + { + if(cx*cx + cyq < rquad) newptr[this->width * (cy+y) + (cx+x)] += height; + } + } +} + +void MLImageFilterWater::CalcWaterBigFilter(int page, int density) +{ + int newh; + int count = (2 * width) + 2; + + int *newptr; + int *oldptr; + + // Set up the pointers + if(page == 0) + { + newptr = hField1; + oldptr = hField2; + } + else + { + newptr = hField2; + oldptr = hField1; + } + + int x, y; + + for(y=2; y < height-2; y++, count += 4) + { + for(x=2; x < width-2; x++, count++) + { + // This does the 25-pixel method. It looks much okay. + + newh = ( + ( + ( + (oldptr[count + width] + + oldptr[count - width] + + oldptr[count + 1] + + oldptr[count - 1] + )<<1) + + ((oldptr[count - width - 1] + + oldptr[count - width + 1] + + oldptr[count + width - 1] + + oldptr[count + width + 1])) + + ( ( + oldptr[count - (width*2)] + + oldptr[count + (width*2)] + + oldptr[count - 2] + + oldptr[count + 2] + ) >> 1 ) + + ( ( + oldptr[count - (width*2) - 1] + + oldptr[count - (width*2) + 1] + + oldptr[count + (width*2) - 1] + + oldptr[count + (width*2) + 1] + + oldptr[count - 2 - width] + + oldptr[count - 2 + width] + + oldptr[count + 2 - width] + + oldptr[count + 2 + width] + ) >> 2 ) + ) + >> 3) + - (newptr[count]); + + + newptr[count] = newh - (newh >> density); + } + } +} + +void MLImageFilterWater::DrawWaterNoLight(int page, MLImage* destination, const MLImage* source) +{ + unsigned int brk = width * height; + + int *ptr = hField1; + + DWORD *dataS = (DWORD*)source->GetData(); + DWORD *dataD = (DWORD*)destination->GetData(); + + for (unsigned int offset = 0; offset < brk; offset++) + { + int dx = ptr[offset] - ptr[offset + 1]; + int dy = ptr[offset] - ptr[offset + width]; + unsigned int index = offset + width * (dy>>3) + (dx>>3); + dataD[offset] = (index < brk ) ? dataS[offset + width*(dy>>3) + (dx>>3)] : dataS[offset]; + } +} + +void MLImageFilterWater::DrawWaterWithLight(MLImage* destination, const MLImage* source) +{ + unsigned int brk = width * height; + + int *ptr = hField1; + + DWORD *dataS = (DWORD*)source->GetData(); + DWORD *dataD = (DWORD*)destination->GetData(); + + for (unsigned int offset = 0; offset < brk; offset++) + { + int dx = ptr[offset] - ptr[offset + 1]; + int dy = ptr[offset] - ptr[offset + width]; + unsigned int index = offset + width * (dy>>3) + (dx>>3); + dataD[offset] = (index < brk ) ? GetShiftedColor(dataS[index], dx) : dataS[offset]; + } +} +COLORREF MLImageFilterWater::GetShiftedColor(COLORREF color,int shift) +{ + int R,G, B; + int r, g, b; + + R = GetRValue(color)-shift; + G = GetGValue(color)-shift; + B = GetBValue(color)-shift; + + r = (R < 0) ? 0 : (R > 255) ? 255 : R; + g = (G < 0) ? 0 : (G > 255) ? 255 : G; + b = (B < 0) ? 0 : (B > 255) ? 255 : B; + + return RGB(r,g,b); +} diff --git a/Src/Plugins/Input/in_mp3/graphics/filterWater.h b/Src/Plugins/Input/in_mp3/graphics/filterWater.h new file mode 100644 index 00000000..21ed92c0 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/graphics/filterWater.h @@ -0,0 +1,47 @@ +#ifndef NULLSOFT_ML_IMAGE_FILTERWATER_HEADER +#define NULLSOFT_ML_IMAGE_FILTERWATER_HEADER + +#include <windows.h> +#include ".\image.h" + +class MLImageFilterWater +{ +public: + MLImageFilterWater(void); + ~MLImageFilterWater(void); + +public: + BOOL CreateFor(const MLImage *image); + void Render(MLImage* destination, const MLImage* source); + + void CalculateWater(int page, int density); + void SmoothWater(int page); + void FlattenWater(void); + + void SineBlob(int x, int y, int radius, int height, int page); + void WarpBlob(int x, int y, int radius, int height, int page); + void HeightBox (int x, int y, int radius, int height, int page); + void HeightBlob(int x, int y, int radius, int height, int page); + +protected: + void ClearData(void); + void CalcWaterBigFilter(int page, int density); + void DrawWaterNoLight(int page,MLImage* destination, const MLImage* source); + void DrawWaterWithLight(MLImage* destination, const MLImage* source); + COLORREF GetShiftedColor(COLORREF color,int shift); + +private: + HANDLE hHandle; + int height; + int width; + + BOOL drawWithLight; + int lightModifier; + int hPage; + int density; + + int* hField1; + int* hField2; +}; + +#endif //#define NULLSOFT_ML_IMAGE_FILTERWATER_HEADER
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/graphics/image.cpp b/Src/Plugins/Input/in_mp3/graphics/image.cpp new file mode 100644 index 00000000..2e3cda46 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/graphics/image.cpp @@ -0,0 +1,209 @@ +#include ".\image.h" + +MLImage::MLImage(void) +{ + loader = NULL; + loaderDelete = TRUE; + ResetData(); +} + +MLImage::MLImage(IMGLOADFUNC loader, BOOL deleteDone) +{ + ResetData(); + SetLoader(loader, deleteDone, FALSE); +} + +MLImage::MLImage(int width, int height) +{ + loader = NULL; + ResetData(); + Init(width,height); +} + +MLImage::~MLImage(void) +{ + ResetData(); +} + +INT_PTR MLImage::SetLoader(IMGLOADFUNC loader, BOOL deleteDone, BOOL forceLoad) +{ + this->loader = loader; + this->loaderDelete = deleteDone; + if (loader && forceLoad) Load(); + return (loader != NULL) ? (INT_PTR) this : FALSE; +} + +BOOL MLImage::Load(void) +{ + ResetData(); + + if (!loader) return FALSE; + HBITMAP hbmpLoaded = loader((INT_PTR)this); + if(hbmpLoaded == NULL) return FALSE; + + BITMAP bi; + if (GetObject(hbmpLoaded, sizeof(bi), &bi)) + { + hbmp = ConvertTo32BppDIB(hbmpLoaded, bi.bmWidth, bi.bmHeight, &info, &data); + } + + if (loaderDelete) DeleteObject(hbmpLoaded); + return (hbmp != NULL); +} + +void MLImage::ResetData(void) +{ + if (hbmp) DeleteObject(hbmp); + hbmp = NULL; + SecureZeroMemory(&info, sizeof(BITMAPINFO)); + data = NULL; +} + +BOOL MLImage::Draw(HDC hdcDest, int destX, int destY, int destWidth, int destHeight, int sourceX, int sourceY) +{ + if (!hbmp) return FALSE; + + int realheight = abs(info.bmiHeader.biHeight); + int rsX = min(sourceX, info.bmiHeader.biWidth); + int rsY = min(sourceY, info.bmiHeader.biWidth); + int height = min(destHeight, realheight - rsY); + + BOOL bResult = SetDIBitsToDevice( hdcDest, destX, destY, + min(destWidth, info.bmiHeader.biWidth - rsX), height, + rsX, realheight - height - rsY, + 0, height, + data, &info, DIB_RGB_COLORS); + return bResult; +} + +BOOL MLImage::Draw(HDC hdcDest, int destX, int destY) +{ + return (!hbmp) ? FALSE : SetDIBitsToDevice( hdcDest, destX, destY, + info.bmiHeader.biWidth, abs(info.bmiHeader.biHeight), + 0, 0, + 0, abs(info.bmiHeader.biHeight), + data, &info, DIB_RGB_COLORS); +} + +int MLImage::GetWidth(void) const +{ + return (hbmp) ? info.bmiHeader.biWidth : 0; +} + +int MLImage::GetHeight(void) const +{ + return (hbmp) ? abs(info.bmiHeader.biHeight) : 0; +} + +void* MLImage::GetData(void) const +{ + return data; +} + +HBITMAP MLImage::ConvertTo32BppDIB(HBITMAP bmpHandle, int bmpWidth, int bmpHeight, LPBITMAPINFO bmpInfo, LPVOID *bmpData) +{ + HBITMAP hbmpNew = NULL; + + HDC hdc = GetWindowDC(NULL); + HDC hdcTmp = CreateCompatibleDC(hdc); + HBITMAP hbmpTmp = CreateCompatibleBitmap(hdc, bmpWidth, bmpHeight); + HBITMAP hbmpOld = (HBITMAP) SelectObject(hdcTmp, hbmpTmp); + + // render original bitmap to the temp dc + HDC hdcBmp = CreateCompatibleDC(hdc); + SelectObject(hdcBmp, bmpHandle); + BitBlt(hdcTmp, 0, 0, bmpWidth, bmpHeight, hdcBmp, 0,0, SRCCOPY); + SelectObject(hdcBmp, NULL); + DeleteDC(hdcBmp); + + // Create a 32 bit bitmap + BITMAPINFO bih; + // create DIB Section + bih.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bih.bmiHeader.biWidth = bmpWidth; + bih.bmiHeader.biHeight = 0 - bmpHeight; + bih.bmiHeader.biPlanes = 1; + bih.bmiHeader.biBitCount = 32; + bih.bmiHeader.biCompression = BI_RGB; + bih.bmiHeader.biSizeImage = 0; + bih.bmiHeader.biXPelsPerMeter = 0; + bih.bmiHeader.biYPelsPerMeter = 0; + bih.bmiHeader.biClrUsed = 0; + bih.bmiHeader.biClrImportant = 0; + + // Create a DC which will be used to get DIB, then create DIBsection + hbmpNew = CreateDIBSection(hdc, (const BITMAPINFO*) &bih, DIB_RGB_COLORS, bmpData, NULL, 0); + + + DWORD* line = (DWORD*)(*bmpData); + // Copy the bits into our 32 bit dib.. + for(int i=0; i<bmpHeight; i++) + { + for(int j=0; j<bmpWidth; j++) + { + line[(i*bmpWidth) + j] = FIXCOLORREF(GetPixel(hdcTmp, j, i)); + } + } + + SelectObject(hdcTmp, hbmpOld); + ReleaseDC(NULL, hdc); + DeleteDC(hdcTmp); + + memcpy(bmpInfo, &bih, sizeof(BITMAPINFO)); + + return hbmpNew; +} + +MLImage* MLImage::Copy(MLImage* destination, const MLImage* original) +{ + if (!destination) return NULL; + + destination->ResetData(); + + destination->loader = original->loader; + destination->loaderDelete = original->loaderDelete; + destination->info = original->info; + HDC hdc = GetWindowDC(NULL); + destination->hbmp = CreateDIBSection(hdc, (const BITMAPINFO*) &destination->info, DIB_RGB_COLORS, &destination->data, NULL, 0); + + CopyMemory(destination->data, original->data, 4*destination->GetHeight() * destination->GetWidth()); + ReleaseDC(NULL, hdc); + return destination; +} + +MLImage* MLImage::Init(int width, int height) +{ + ResetData(); + + loader = NULL; + loaderDelete = TRUE; + + // create DIB Section + info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + info.bmiHeader.biWidth = width; + info.bmiHeader.biHeight = 0 - height; + info.bmiHeader.biPlanes = 1; + info.bmiHeader.biBitCount = 32; + info.bmiHeader.biCompression = BI_RGB; + info.bmiHeader.biSizeImage = 0; + info.bmiHeader.biXPelsPerMeter = 0; + info.bmiHeader.biYPelsPerMeter = 0; + info.bmiHeader.biClrUsed = 0; + info.bmiHeader.biClrImportant = 0; + + HDC hdc = GetWindowDC(NULL); + hbmp = CreateDIBSection(hdc, (const BITMAPINFO*) &info, DIB_RGB_COLORS, &data, NULL, 0); + ReleaseDC(NULL, hdc); + return this; +} + +MLImage* MLImage::Init(int width, int height, COLORREF color) +{ + Init(width, height); + + int rColor = FIXCOLORREF(color); + DWORD *line = (DWORD*)(data); + DWORD *end = line + GetHeight() * GetWidth(); + for(;line != end; line++) *line = rColor; + return this; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/graphics/image.h b/Src/Plugins/Input/in_mp3/graphics/image.h new file mode 100644 index 00000000..72a4aa5d --- /dev/null +++ b/Src/Plugins/Input/in_mp3/graphics/image.h @@ -0,0 +1,61 @@ +#ifndef NULLSOFT_ML_IMAGE_HEADER +#define NULLSOFT_ML_IMAGE_HEADER + +#include <windows.h> + +#define RGBA(r,g,b,a) ((COLORREF)(((BYTE)(r)|((WORD)(g)<<8))|(((DWORD)(BYTE)(b))<<16)|(((DWORD)(BYTE)(a))<<24))) +#define FIXCOLORREF(clr) RGBA(GetBValue(clr),GetGValue(clr), GetRValue(clr),((DWORD)(clr)) >> 24) + +// loader function will be called every time MLImage need to +// reload picture. Input parameter - handle to the calling object +// Output - loaded bitmap +typedef HBITMAP (*IMGLOADFUNC)(INT_PTR handle); + +class MLImage +{ +public: + MLImage(void); + MLImage(IMGLOADFUNC loader, BOOL deleteDone); + MLImage(int width, int height); + ~MLImage(void); + +public: + // sets the loader function and returns handle to the class or NULL if error + // loader - pointer to the loader function + // deleteDone - if TRUE MLImage will delete HBITMAP object from loader every time it is done loading + // forceLoad - forcing to load bitamp immedialty by calling Load() + INT_PTR SetLoader(IMGLOADFUNC loader, BOOL deleteDone, BOOL forceLoad); + BOOL Load(void); // load image + + MLImage* Init(int width, int height); // init image (allocates memory) + MLImage* Init(int width, int height, COLORREF color); // init image (allocates memory) and set + + BOOL Draw(HDC hdcDest, int destX, int destY, int destWidth, int destHeight, int sourceX, int sourceY); // draw image + BOOL Draw(HDC hdcDest, int destX, int destY); // draw image + +public: + int GetWidth(void) const; + int GetHeight(void) const; + void* GetData(void) const; + + +private: + void ResetData(void); + +public: + static MLImage* Copy(MLImage* destination, const MLImage* original);// copy all data from the original object (including image data) to the destination + +private: + static HBITMAP ConvertTo32BppDIB(HBITMAP bmpHandle, int bmpWidth, int bmpHeight, LPBITMAPINFO bmpInfo, LPVOID *bmpData); + +private: + IMGLOADFUNC loader; // pointer to the loader function + BOOL loaderDelete; // TRUE - delete HBITMAP from loader after load + + HBITMAP hbmp; // my bitmap + BITMAPINFO info; + void *data; +}; + +#endif // NULLSOFT_ML_IMAGE_HEADER + diff --git a/Src/Plugins/Input/in_mp3/graphics/imageFilters.cpp b/Src/Plugins/Input/in_mp3/graphics/imageFilters.cpp new file mode 100644 index 00000000..59d994ed --- /dev/null +++ b/Src/Plugins/Input/in_mp3/graphics/imageFilters.cpp @@ -0,0 +1,91 @@ +#include ".\imagefilters.h" + +void MLImageFilter_GrayScale(MLImage *image) +{ + DWORD *line = (DWORD*)(image->GetData()); + DWORD *end = line + image->GetHeight() * image->GetWidth(); + for(;line != end; line++) + { + BYTE y = (BYTE)(0.3f * GetBValue(*line) + 0.59f *GetGValue(*line) + 0.11f *GetRValue(*line)); + *line = RGB(y,y,y); + } +} + +void MLImageFilter_Invert(MLImage *image) +{ + DWORD *line = (DWORD*)(image->GetData()); + DWORD *end = line + image->GetHeight() * image->GetWidth(); + for(;line != end; line++) *line = ((~*line) & 0x00FFFFFF) | (*line & 0xFF000000); +} + +void MLImageFilter_SetToColor(MLImage *image, COLORREF color) +{ + COLORREF rColor = FIXCOLORREF(color); + DWORD *line = (DWORD*)(image->GetData()); + DWORD *end = line + image->GetHeight() * image->GetWidth(); + for(;line != end; line++) *line = rColor; +} + +void MLImageFilter_Fader1(MLImage *dest, const MLImage* source, COLORREF color) +{ + int len = dest->GetHeight() * dest->GetWidth(); + BYTE r = GetRValue(color), g = GetGValue(color), b = GetBValue(color); + + DWORD *dataS = (DWORD*)(source->GetData()); + DWORD *dataD = (DWORD*)(dest->GetData()); + DWORD *end = dataD + len; + for(;dataD != end; dataD++, dataS++) + { + *dataD = RGB( max(b, GetRValue(*dataS)), max(g, GetGValue(*dataS)), max(r, GetBValue(*dataS))) ; + } +} + +void MLImageFilter_Fader2(MLImage *dest, const MLImage* source, COLORREF color) +{ + int len = dest->GetHeight() * dest->GetWidth(); + BYTE r = GetRValue(color), g = GetGValue(color), b = GetBValue(color); + + DWORD *dataS = (DWORD*)(source->GetData()); + DWORD *dataD = (DWORD*)(dest->GetData()); + DWORD *end = dataD + len; + for(;dataD != end; dataD++, dataS++) + { + *dataD = RGB( min(b, GetRValue(*dataS)), min(g, GetGValue(*dataS)), min(r, GetBValue(*dataS))) ; + } +} + +void MLImageFilter_Fader3(MLImage *dest, const MLImage* source, int koeff) +{ + int len = dest->GetHeight() * dest->GetWidth(); + + DWORD *dataS = (DWORD*)(source->GetData()); + DWORD *dataD = (DWORD*)(dest->GetData()); + DWORD *end = dataD + len; + for(;dataD != end; dataD++, dataS++) + { + *dataD = RGB(min(255,GetRValue(*dataS) + koeff), min(255,GetGValue(*dataS) + koeff), min(255, GetBValue(*dataS) + koeff)); + } +} + +void MLImageFilter_Blend1(MLImage *dest, MLImage *src1, int destX, int destY, int width, int height, const MLImage* src2, int srcX, int srcY, COLORREF color) +{ + int widthS1 = src1->GetWidth(); + int widthS2 = src2->GetWidth(); + + DWORD *dataD = (DWORD*)(dest->GetData()) + destY * widthS1 + destX; + DWORD *dataS1 = (DWORD*)(src1->GetData()) + destY * widthS1 + destX; + DWORD *dataS2 = (DWORD*)(src2->GetData()) + srcY * widthS2 + srcX; + + DWORD *curS1; + + for (int y = 0; y < height; y++) + { + DWORD *curD = dataD + y * widthS1; + curS1 = dataS1 + y * widthS1; + DWORD *curS2 = dataS2 + y * widthS2; + for (DWORD *end = curS1 + width; end != curS1; curD++, curS1++, curS2++) + { + *curD = (*curS1 == color) ? *curS2 : *curS1; + } + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/graphics/imageFilters.h b/Src/Plugins/Input/in_mp3/graphics/imageFilters.h new file mode 100644 index 00000000..ebb1c752 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/graphics/imageFilters.h @@ -0,0 +1,17 @@ +#ifndef NULLSOFT_ML_IMAGE_FILTER_HEADER +#define NULLSOFT_ML_IMAGE_FILTER_HEADER + +#include <windows.h> +#include ".\image.h" +#include ".\filterwater.h" + +void MLImageFilter_GrayScale(MLImage *image); +void MLImageFilter_Invert(MLImage *image); +void MLImageFilter_SetToColor(MLImage *image, COLORREF color); +void MLImageFilter_Fader1(MLImage *dest, const MLImage* source, COLORREF color); +void MLImageFilter_Fader2(MLImage *dest, const MLImage* source, COLORREF color); +void MLImageFilter_Fader3(MLImage *dest, const MLImage* source, int koeff); +void MLImageFilter_Blend1(MLImage *dest, MLImage *src1, int destX, int destY, int width, int height, const MLImage* src2, int srcX, int srcY, COLORREF color); + + +#endif //NULLSOFT_ML_IMAGE_FILTER_HEADER diff --git a/Src/Plugins/Input/in_mp3/id3.cpp b/Src/Plugins/Input/in_mp3/id3.cpp new file mode 100644 index 00000000..2ab12c74 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/id3.cpp @@ -0,0 +1,556 @@ +#include "../id3v2/id3_tag.h" +#include "id3.h" +#include "config.h" +#include "../nu/ns_wc.h" +#include <strsafe.h> +#define _isdigit(x) (( x ) >= '0' && ( x ) <= '9') + +/* id3 helper functions */ + +void SetFrameEncoding(ID3_Frame *frame, int encoding) +{ + switch (encoding) + { + case ENCODING_AUTO: + if (config_write_mode == WRITE_UTF16) + frame->Field(ID3FN_TEXTENC).Set(ID3TE_UNICODE); + else + frame->Field(ID3FN_TEXTENC).Set(ID3TE_ASCII); + break; + case ENCODING_FORCE_ASCII: + frame->Field(ID3FN_TEXTENC).Set(ID3TE_ASCII); + break; + case ENCODING_FORCE_UNICODE: + frame->Field(ID3FN_TEXTENC).Set(ID3TE_UNICODE); + break; + } +} + +char *ID3_GetString(ID3_Frame *frame, ID3_FieldID fldName, size_t nIndex) +{ + char *text = NULL; + if (NULL != frame) + { + size_t nText = frame->Field(fldName).Size(); + text = (char *)calloc(nText + 1, sizeof(char)); + frame->Field(fldName).GetLocal(text, nText + 1, nIndex); + } + return text; +} + +wchar_t *ID3_GetUnicodeString(ID3_Frame *frame, ID3_FieldID fldName, size_t nIndex) +{ + wchar_t *text = NULL; + if (NULL != frame) + { + size_t nText = frame->Field(fldName).Size(); + text = (wchar_t *)calloc(sizeof(wchar_t) * (nText + 1), sizeof(wchar_t)); + frame->Field(fldName).GetUnicode(text, nText + 1, nIndex); + } + return text; +} + +wchar_t *ID3_FillUnicodeString(ID3_Frame *frame, ID3_FieldID fldName, wchar_t *dest, size_t destlen, size_t nIndex) +{ + memset(dest, 0, destlen * sizeof(wchar_t)); + if (NULL != frame) + { + frame->Field(fldName).GetUnicode(dest, destlen, nIndex); + return dest; + } + else + return NULL; +} + +wchar_t *ID3_GetTitle(ID3_Tag *tag) +{ + wchar_t*sTitle = NULL; + if (NULL == tag) + { + return sTitle; + } + ID3_Frame *frame = tag->Find(ID3FID_TITLE); + if (frame != NULL) + { + sTitle = ID3_GetUnicodeString(frame, ID3FN_TEXT); + } + return sTitle; +} + +wchar_t *ID3_GetArtist(ID3_Tag *tag) +{ + if (!tag) return 0; + wchar_t *sArtist = NULL; + ID3_Frame *frame = NULL; + if ((frame = tag->Find(ID3FID_LEADARTIST)) || (frame = tag->Find(ID3FID_BAND))) + { + sArtist = ID3_GetUnicodeString(frame, ID3FN_TEXT); + } + return sArtist; +} + +wchar_t *ID3_GetAlbum(ID3_Tag *tag) +{ + wchar_t *sAlbum = NULL; + if (NULL == tag) + { + return sAlbum; + } + ID3_Frame *frame = tag->Find(ID3FID_ALBUM); + if (frame != NULL) + { + sAlbum = ID3_GetUnicodeString(frame, ID3FN_TEXT); + } + return sAlbum; +} + +wchar_t *ID3_GetYear(ID3_Tag *tag) +{ + wchar_t *sYear = NULL; + if (NULL == tag) + { + return sYear; + } + ID3_Frame *frame = tag->Find(ID3FID_RECORDINGTIME); + if (frame != NULL) + sYear = ID3_GetUnicodeString(frame, ID3FN_TEXT); + + if (!sYear || !*sYear) + { + frame = tag->Find(ID3FID_YEAR); + if (frame != NULL) + sYear = ID3_GetUnicodeString(frame, ID3FN_TEXT); + } + + return sYear; +} + +void ID3_AddSetComment(ID3_Tag *tag, const wchar_t *comment) +{ + ID3_Frame *frame = tag->Find(ID3FID_COMMENT, ID3FN_DESCRIPTION, L""); + if (frame) + { + if (!comment || !comment[0]) + tag->RemoveFrame(frame); + else + { + SetFrameEncoding(frame); + frame->Field(ID3FN_TEXT).SetUnicode(comment); + unsigned char null3[3] = {0, 0, 0}; + frame->Field(ID3FN_LANGUAGE).Get(null3, 3); + if (!null3[0]) frame->Field(ID3FN_LANGUAGE).SetLatin("eng"); + } + } + else if (comment && comment[0]) + { + frame = new ID3_Frame(ID3FID_COMMENT); + SetFrameEncoding(frame); + frame->Field(ID3FN_LANGUAGE).SetLatin("eng"); + //frame->Field(ID3FN_LANGUAGE).Set(null3, 3); + frame->Field(ID3FN_DESCRIPTION).SetUnicode(L""); + frame->Field(ID3FN_TEXT).SetUnicode(comment); + tag->AddFrame(frame, TRUE); + } +} + +void ID3_AddSetRating(ID3_Tag *tag, const wchar_t *rating) +{ + luint rating_integer = 0; + if (rating) + rating_integer = _wtoi(rating); + + bool custom_frame = false, own_frame = false; + ID3_Frame* frame = NULL; + if (config_rating_email[0]) + { + frame = tag->Find(ID3FID_POPULARIMETER, ID3FN_EMAIL, config_rating_email); + if (!frame) custom_frame = true; + } + if (!frame) + { + frame = tag->Find(ID3FID_POPULARIMETER, ID3FN_EMAIL, "rating@winamp.com\0"); + if (frame) own_frame = true; + } + if (!frame) + { + frame = tag->Find(ID3FID_POPULARIMETER); + if (frame) own_frame = true; + } + // try to use a custom field if our own was present and the custom wasn't + if (custom_frame && own_frame) + { + frame->Clear(); + frame = NULL; + } + if (!frame) + { + frame = new ID3_Frame(ID3FID_POPULARIMETER); + if (!config_rating_email[0]) + frame->Field(ID3FN_EMAIL).Set((uchar *)"rating@winamp.com\0", 18); + else + { + frame->Field(ID3FN_EMAIL).Set((uchar *)config_rating_email, strlen(config_rating_email)+1); + } + tag->AddFrame(frame, TRUE); + } + if (frame) + { + switch(rating_integer) + { + case 2: + rating_integer=64; + break; + case 3: + rating_integer=128; + break; + case 4: + rating_integer=196; + break; + case 5: + rating_integer = 255; + break; + } + + if (!rating_integer) + tag->RemoveFrame(frame); + else + frame->Field(ID3FN_RATING).Set(rating_integer); + } +} + +wchar_t *ID3_GetComment(ID3_Tag *tag, wchar_t *dest, size_t destlen) +{ + wchar_t *comment = NULL; + if (NULL == tag) + { + return comment; + } + ID3_Frame* frame = tag->Find(ID3FID_COMMENT, ID3FN_DESCRIPTION, L""); + if (frame) + { + comment = ID3_FillUnicodeString(frame, ID3FN_TEXT, dest, destlen); + } + return comment; +} + +wchar_t *ID3_GetRating(ID3_Tag *tag, wchar_t *dest, size_t destlen) +{ + if (NULL == tag) + { + return NULL; + } + ID3_Frame* frame = NULL; + if (config_rating_email[0]) + frame = tag->Find(ID3FID_POPULARIMETER, ID3FN_EMAIL, config_rating_email); + if (!frame) + frame = tag->Find(ID3FID_POPULARIMETER, ID3FN_EMAIL, "rating@winamp.com\0"); + if (!frame) + frame = tag->Find(ID3FID_POPULARIMETER); + if (frame) + { + int rating = (int)frame->Field(ID3FN_RATING).Get(); + + if (rating >= 224 && rating <= 255) + rating = 5; + else if (rating >= 160 && rating <= 223) + rating = 4; + else if (rating >= 96 && rating <= 159) + rating = 3; + else if (rating >= 32 && rating <= 95) + rating = 2; + else if (rating >= 1 && rating <= 31) + rating = 1; + else + rating = 0; + + StringCchPrintfW(dest, destlen, L"%u", rating); + return dest; + } + return 0; +} + +wchar_t *ID3_GetComment(ID3_Tag *tag, const wchar_t *desc, wchar_t *dest, size_t destlen) +{ + wchar_t *comment = NULL; + if (NULL == tag) + { + return comment; + } + ID3_Frame* frame = tag->Find(ID3FID_COMMENT, ID3FN_DESCRIPTION, desc); + if (frame) + { + comment = ID3_FillUnicodeString(frame, ID3FN_TEXT, dest, destlen); + } + return comment; +} + +wchar_t *ID3_GetMusicbrainzRecordingID(ID3_Tag *tag, wchar_t *dest, size_t destlen) +{ + if (NULL == tag) + { + return 0; + } + ID3_Frame* frame = tag->Find(ID3FID_UNIQUEFILEID, ID3FN_OWNER, L"http://musicbrainz.org"); + if (frame) + { + uchar data[64] = {0}; + luint dataSize = frame->Field(ID3FN_DATA).Size(); + frame->Field(ID3FN_DATA).Get(data, 64); + int converted = MultiByteToWideCharSZ(CP_ACP, 0, (const char *)data, (int)dataSize, dest, (int)destlen); + dest[converted]=0; + return dest; + } + return 0; +} + +wchar_t *ID3_GetGracenoteTagID(ID3_Tag *tag) +{ + if (NULL == tag) + { + return 0; + } + ID3_Frame* frame = tag->Find(ID3FID_UNIQUEFILEID, ID3FN_OWNER, L"http://www.cddb.com/id3/taginfo1.html"); + if (frame) + { + uchar data[64] = {0}; + luint dataSize = frame->Field(ID3FN_DATA).Size(); + frame->Field(ID3FN_DATA).Get(data, 64); + int converted = MultiByteToWideChar(CP_ACP, 0, (const char *)data, (int)dataSize, 0, 0); + wchar_t *dest = (wchar_t *)calloc((converted+1), sizeof(wchar_t)); + converted = MultiByteToWideChar(CP_ACP, 0, (const char *)data, (int)dataSize, dest, converted); + dest[converted]=0; + return dest; + } + return 0; +} + +wchar_t *ID3_GetGracenoteTagID(ID3_Tag *tag, wchar_t *dest, size_t destlen) +{ + if (NULL == tag) + { + return 0; + } + ID3_Frame* frame = tag->Find(ID3FID_UNIQUEFILEID, ID3FN_OWNER, L"http://www.cddb.com/id3/taginfo1.html"); + if (frame) + { + uchar data[64] = {0}; + luint dataSize = frame->Field(ID3FN_DATA).Size(); + frame->Field(ID3FN_DATA).Get(data, 64); + int converted = MultiByteToWideCharSZ(CP_ACP, 0, (const char *)data, (int)dataSize, dest, (int)destlen); + dest[converted]=0; + return dest; + } + return 0; +} + +void ID3_AddSetGracenoteTagID(ID3_Tag *tag, const wchar_t *tagID) +{ + ID3_Frame *frame = tag->Find(ID3FID_UNIQUEFILEID, ID3FN_OWNER, L"http://www.cddb.com/id3/taginfo1.html"); + if (frame) + { + if (!tagID || !tagID[0]) + tag->RemoveFrame(frame); + else + { + size_t origLen = wcslen(tagID); // so we can not write the null terminator + uchar data[64] = {0}; + luint dataSize = WideCharToMultiByte(CP_ACP, 0, tagID, (int)origLen, (char *)data, 64, 0, 0); + frame->Field(ID3FN_DATA).Set(data, dataSize); + } + } + else if (tagID && tagID[0]) + { + frame = new ID3_Frame(ID3FID_UNIQUEFILEID); + SetFrameEncoding(frame, ENCODING_FORCE_ASCII); + frame->Field(ID3FN_OWNER).SetLatin("http://www.cddb.com/id3/taginfo1.html"); + size_t origLen = wcslen(tagID); // so we can not write the null terminator + uchar data[64] = {0}; + luint dataSize = WideCharToMultiByte(CP_ACP, 0, tagID, (int)origLen, (char *)data, 64, 0, 0); + frame->Field(ID3FN_DATA).Set(data, dataSize); + tag->AddFrame(frame, TRUE); + } +} + +#if 0 // benski> CUT +char *ID3_GetTUID(ID3_Tag *tag) +{ + char *tuid = NULL; + if (NULL == tag) + { + return tuid; + } + ID3_Frame* frame = NULL; + frame = tag->Find(ID3FID_UNIQUEFILEID); + if (frame) + { + char *tmp = ID3_GetString(frame, ID3FN_DATA); + if (tmp) + { + // verify first four characters are '3CD3' + if (!strncmp(tmp, "3CD3", 4)) + { + char m, n; + char *p = tmp + 4; + n = *p++; + m = 'P' - n; + p += m; + + n = *p++; + m = 'Z' - n; // length of TUID; + tuid = _strdup(p); + tuid[m] = 0; // null terminate + } + + free(tmp); + } + } + return tuid; +} +#endif + +char *ID3_GetGenre(ID3_Tag *tag) +{ + char *sGenre = NULL; + if (NULL == tag) + { + return sGenre; + } + ID3_Frame *frame = tag->Find(ID3FID_CONTENTTYPE); + if (frame != NULL) + { + sGenre = ID3_GetString(frame, ID3FN_TEXT); + } + return sGenre; +} + +void ID3_AddUserText(ID3_Tag *tag, wchar_t *desc, const wchar_t *value, int encoding) +{ + ID3_Frame *frame = tag->Find(ID3FID_USERTEXT, ID3FN_DESCRIPTION, desc); + if (frame) + { + if (!value || !value[0]) + tag->RemoveFrame(frame); + else + { + SetFrameEncoding(frame, encoding); + frame->Field(ID3FN_TEXT).SetUnicode(value); + } + } + else if (value && value[0]) + { + frame = new ID3_Frame(ID3FID_USERTEXT); + SetFrameEncoding(frame, encoding); + frame->Field(ID3FN_DESCRIPTION).SetUnicode(desc); + frame->Field(ID3FN_TEXT).SetUnicode(value); + tag->AddFrame(frame, TRUE); + } +} + +wchar_t *ID3_GetUserText(ID3_Tag *tag, wchar_t *desc) +{ + if (tag == NULL) + return NULL; + + ID3_Frame *frame = tag->Find(ID3FID_USERTEXT, ID3FN_DESCRIPTION, desc); + if (frame) + return ID3_GetUnicodeString(frame, ID3FN_TEXT); + else + return 0; +} + +wchar_t *ID3_GetUserText(ID3_Tag *tag, wchar_t *desc, wchar_t *dest, size_t destlen) +{ + if (tag == NULL) + return NULL; + + ID3_Frame *frame = tag->Find(ID3FID_USERTEXT, ID3FN_DESCRIPTION, desc); + if (frame) + return ID3_FillUnicodeString(frame, ID3FN_TEXT, dest, destlen); + else + return 0; +} + +wchar_t *ID3_GetTagText(ID3_Tag *tag, ID3_FrameID f) +{ + wchar_t *sComposer = NULL; + if (NULL == tag) + { + return sComposer; + } + ID3_Frame *frame = tag->Find(f); + if (frame != NULL) + { + sComposer = ID3_GetUnicodeString(frame, ID3FN_TEXT); + } + return sComposer; +} + +wchar_t *ID3_GetTagText(ID3_Tag *tag, ID3_FrameID f, wchar_t *dest, size_t destlen) +{ + wchar_t *sComposer = NULL; + if (NULL == tag) + { + return sComposer; + } + ID3_Frame *frame = tag->Find(f); + if (frame != NULL) + { + sComposer = ID3_FillUnicodeString(frame, ID3FN_TEXT, dest, destlen); + } + return sComposer; +} + +wchar_t *ID3_GetTagUrl(ID3_Tag *tag, ID3_FrameID f, wchar_t *dest, size_t destlen) +{ + wchar_t *sComposer = NULL; + if (NULL == tag) + { + return sComposer; + } + ID3_Frame *frame = tag->Find(f); + if (frame != NULL) + { + sComposer = ID3_FillUnicodeString(frame, ID3FN_URL, dest, destlen); + } + return sComposer; +} + +#if 0 +char *ID3_GetGenreDisplayable(ID3_Tag *tag) +{ + char *sGenre = ID3_GetGenre(tag); + if (!sGenre) return NULL; + + while (sGenre && *sGenre == ' ') sGenre++; + + if (sGenre[0] == '(' || _isdigit(sGenre[0])) + { + int isparam = !_isdigit(sGenre[0]); + char *pCur = &sGenre[isparam]; + int cnt = 0; + while (_isdigit(*pCur)) + { + cnt++; + pCur++; + } + while (pCur && *pCur == ' ') pCur++; + + if (cnt > 0 && (isparam && *pCur == ')') || (!isparam && !*pCur)) + { + // if the genre number is greater than 255, its invalid. + size_t ulGenre = atoi(&sGenre[isparam]); + if (ulGenre >= 0 && ulGenre < numberOfGenres) + { + char *tmp = (char*)malloc(strlen(genres[ulGenre]) + 1); + if (tmp) + { + memcpy(tmp, genres[ulGenre], strlen(genres[ulGenre]) + 1); + free(sGenre); + sGenre = tmp; + } + } + } + } + return sGenre; +} +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/id3.h b/Src/Plugins/Input/in_mp3/id3.h new file mode 100644 index 00000000..1bd40590 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/id3.h @@ -0,0 +1,43 @@ +#ifndef NULLSOFT_IN_MP3_IN_H +#define NULLSOFT_IN_MP3_IN_H + +extern char *genres[]; +extern size_t numberOfGenres; +enum +{ + ENCODING_AUTO=0, + ENCODING_FORCE_ASCII = 1, + ENCODING_FORCE_UNICODE = 2, +}; + +char *ID3_GetString(ID3_Frame *frame, ID3_FieldID fldName, size_t nIndex=1); +wchar_t *ID3_GetUnicodeString(ID3_Frame *frame, ID3_FieldID fldName, size_t nIndex=1); +wchar_t *ID3_FillUnicodeString(ID3_Frame *frame, ID3_FieldID fldName, wchar_t *dest, size_t destlen, size_t nIndex=1); + +wchar_t *ID3_GetTitle(ID3_Tag *tag); +wchar_t *ID3_GetArtist(ID3_Tag *tag); +//char *ID3_GetAlbumLocal(ID3_Tag *tag); +wchar_t *ID3_GetAlbum(ID3_Tag *tag); +wchar_t *ID3_GetYear(ID3_Tag *tag); +wchar_t *ID3_GetComment(ID3_Tag *tag, wchar_t *dest, size_t destlen); +wchar_t *ID3_GetComment(ID3_Tag *tag, const wchar_t *desc, wchar_t *dest, size_t destlen); +char *ID3_GetTUID(ID3_Tag *tag); +char *ID3_GetGenre(ID3_Tag *tag); +wchar_t *ID3_GetTagText(ID3_Tag *tag, ID3_FrameID f); +wchar_t *ID3_GetTagText(ID3_Tag *tag, ID3_FrameID f, wchar_t *dest, size_t destlen); +wchar_t *ID3_GetTagUrl(ID3_Tag *tag, ID3_FrameID f, wchar_t *dest, size_t destlen); +char *ID3_GetGenreDisplayable(ID3_Tag *tag); +wchar_t *ID3_GetUserText(ID3_Tag *tag, wchar_t *desc); +wchar_t *ID3_GetUserText(ID3_Tag *tag, wchar_t *desc, wchar_t *dest, size_t destlen); +void ID3_AddUserText(ID3_Tag *tag, wchar_t *desc, const wchar_t *value, int encoding=ENCODING_AUTO); +void ID3_AddSetComment(ID3_Tag *tag, const wchar_t *comment); +void ID3_AddSetRating(ID3_Tag *tag, const wchar_t *rating); +wchar_t *ID3_GetRating(ID3_Tag *tag, wchar_t *dest, size_t destlen); +wchar_t *ID3_GetMusicbrainzRecordingID(ID3_Tag *tag, wchar_t *dest, size_t destlen); +wchar_t *ID3_GetGracenoteTagID(ID3_Tag *tag); +wchar_t *ID3_GetGracenoteTagID(ID3_Tag *tag, wchar_t *dest, size_t destlen); +void ID3_AddSetGracenoteTagID(ID3_Tag *tag, const wchar_t *tagID); + +void SetFrameEncoding(ID3_Frame *frame, int encoding = ENCODING_AUTO); + +#endif diff --git a/Src/Plugins/Input/in_mp3/id3dlg.cpp b/Src/Plugins/Input/in_mp3/id3dlg.cpp new file mode 100644 index 00000000..85de0393 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/id3dlg.cpp @@ -0,0 +1,1071 @@ +#include "main.h" +#include "Metadata.h" +#include "../Winamp/wa_ipc.h" +// ID3v2 stuff +#include "../id3v2/id3_tag.h" +#include "FactoryHelper.h" +#include "id3.h" +#include "../nu/AutoWide.h" +#include "../nu/AutoChar.h" +#include "AACFrame.h" +#include "LAMEinfo.h" +#include <shlwapi.h> +#include "../nu/ns_wc.h" +#include "../nu/ListView.h" +#include "resource.h" +#include "Stopper.h" +#include "config.h" +#include <strsafe.h> + + +// TODO: benski> CUT!!! +char g_stream_title[256] = {0}; + +int fixAACCBRbitrate(int br) +{ + static short brs[] = + { + 8, 12, 16, 20, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 + }; + int x; + for (x = 0; x < sizeof(brs) / sizeof(brs[0]); x ++) + { + int delta = (brs[x] * 8) / 128; + if (delta < 2) delta = 2; + if (br < brs[x] - delta) break; + if (br < brs[x] + delta) return brs[x]; + } + return br; +} + +void ConvertTryUTF8(const char *in, wchar_t *out, size_t outlen) +{ + out[0]=0; + int x = MultiByteToWideCharSZ(CP_UTF8, MB_ERR_INVALID_CHARS, in, -1, out, (int)outlen); + if (!x) + { + if (GetLastError() == ERROR_NO_UNICODE_TRANSLATION) + MultiByteToWideCharSZ(CP_ACP, 0, in, -1, out, (int)outlen); + else + MultiByteToWideCharSZ(CP_UTF8, 0, in, -1, out, (int)outlen); + } +} + +void getfileinfo(const wchar_t *filename, wchar_t *title, int *length_in_ms) +{ + const wchar_t *fn; + if (length_in_ms) *length_in_ms = -1000; + if (filename && filename[0]) + fn = filename; + else + fn = lastfn; + if (!_wcsnicmp(fn, L"file://", 7)) fn += 7; + if (PathIsURL(fn)) + { + if (title) + { + if (fn != filename || !_wcsicmp(fn, lastfn)) + { + EnterCriticalSection(&g_lfnscs); + if (lastfn_status[0]) + { + char buf[4096] = {0}; + StringCchPrintfA(buf, 4096, "[%s] %s", lastfn_status, lastfn_data_ready ? g_stream_title : (char *)AutoChar(fn)); + ConvertTryUTF8(buf, title, 256); + } + else + { + if (!lastfn_data_ready) + lstrcpynW(title, fn, 256); + else + { + ConvertTryUTF8(g_stream_title, title, 256); + } + } + LeaveCriticalSection(&g_lfnscs); + if (length_in_ms) *length_in_ms = getlength(); + } + else + { + lstrcpynW(title, fn, 256); + } + } + return ; + } + else + { + Metadata info; + if (info.Open(fn) == METADATA_SUCCESS) + { + if (title) + { + wchar_t mp3artist[256] = L"", mp3title[256] = L""; + info.GetExtendedData("artist", mp3artist, 256); + info.GetExtendedData("title", mp3title, 256); + if (mp3artist[0] && mp3title[0]) + StringCchPrintfW(title, 256, L"%s - %s", mp3artist, mp3title); + else if (mp3title[0]) + lstrcpynW(title, mp3title, 256); + else + { + lstrcpynW(title, fn, MAX_PATH); + PathStripPathW(title); + PathRemoveExtensionW(title); + } + } + + if (fn == filename) + { + wchar_t ln[128]=L""; + info.GetExtendedData("length", ln, 128); + *length_in_ms = _wtoi(ln); + } + else + *length_in_ms = getlength(); + } + else if (fn != filename) + *length_in_ms = getlength(); + } +} + +int id3Dlg(const wchar_t *fn, HWND hwnd) +{ + return 1; +} + +extern const wchar_t *id3v1_genres[]; +extern size_t numGenres; + +static int our_change=0; + +#define GetMeta(hwnd) (Metadata *)GetPropW(GetParent(hwnd),L"mp3info") +#define SetMeta(hwnd,meta) SetPropW(GetParent(hwnd),L"mp3info",(HANDLE)meta) + +static INT_PTR CALLBACK id3v1_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static int my_change_v1=0; + static const int ctrls[] = + { + IDC_ID3V11_TRACK, + IDC_ID3_TITLE, + IDC_ID3_ARTIST, + IDC_ID3_ALBUM, + IDC_ID3_YEAR, + IDC_ID3_COMMENT, + IDC_ID3_GENRE, + }; + static const int strs_lim[] = + { + 3, + 30, + 30, + 30, + 4, + 28, + 1, + }; + static const char * strs[] = + { + "track", + "title", + "artist", + "album", + "year", + "comment", + "genre", + }; + switch (uMsg) + { + case WM_INITDIALOG: + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) + meta->AddRef(); + else + { + meta = new Metadata(); + meta->Open((wchar_t*)lParam); + SetMeta(hwndDlg,meta); + } + + wchar_t genre_buf[32] = {0}; + meta->id3v1.GetString("genre",genre_buf,32); + + our_change=1; + + SendDlgItemMessage(hwndDlg, IDC_ID3_GENRE, CB_RESETCONTENT, 0, 0); + SendDlgItemMessage(hwndDlg, IDC_ID3_GENRE, CB_SETCURSEL, -1, 0); + for (size_t x = 0; x != numGenres; x ++) + { + int y = (int)SendDlgItemMessage(hwndDlg, IDC_ID3_GENRE, CB_ADDSTRING, 0, (LPARAM)id3v1_genres[x]); + SendDlgItemMessage(hwndDlg, IDC_ID3_GENRE, CB_SETITEMDATA, y, x); + + if (_wcsicmp(genre_buf,id3v1_genres[x])==0) + SendDlgItemMessage(hwndDlg, IDC_ID3_GENRE, CB_SETCURSEL, y, 0); + } + + for (int i=0; i<sizeof(strs)/sizeof(char*); i++) + { + // make sure the edit boxes are limited to id3v1 spec sizes (trickier on number type fields) + wchar_t buf[32] = {0}; + SendDlgItemMessage(hwndDlg,ctrls[i],EM_SETLIMITTEXT,strs_lim[i],0); + meta->id3v1.GetString(strs[i],buf,32); + SetDlgItemTextW(hwndDlg,ctrls[i],buf); + } + + if (meta->id3v1.HasData()) + { + CheckDlgButton(hwndDlg,IDC_ID3V1,TRUE); + if (!config_write_id3v1) + { // if we have id3v1 writing turned off, disable controls + for (int i=0; i<sizeof(ctrls)/sizeof(int); i++) + EnableWindow(GetDlgItem(hwndDlg,ctrls[i]),FALSE); + } + } + else + { // no id3v1 tag present + for (int i=0; i<sizeof(ctrls)/sizeof(int); i++) + EnableWindow(GetDlgItem(hwndDlg,ctrls[i]),FALSE); + + if (!config_create_id3v1) + { // don't allow one to be created if the settings disallow + EnableWindow(GetDlgItem(hwndDlg,IDC_ID3V1),FALSE); + } + } + + our_change=0; + } + break; + case WM_USER: + if (wParam && lParam && !our_change && !my_change_v1) + { + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + + if (!config_write_id3v1) + break; + + if (!config_create_id3v1 && !meta->id3v1.HasData()) + break; + + wchar_t *key = (wchar_t*)wParam; + wchar_t *value = (wchar_t*)lParam; + AutoChar keyA(key); + for (int i=0; i<sizeof(strs)/sizeof(char*); i++) + { + if (_stricmp(keyA,strs[i])==0) + { + // benski> i don't think this is what we want? meta->SetExtendedData(strs[i],value); + meta->id3v1.SetString(strs[i], value); + wchar_t buf[2048]=L""; + meta->id3v1.GetString(strs[i],buf,2048); + + if (!IsDlgButtonChecked(hwndDlg,IDC_ID3V1)) + { + // re-enable stuff + CheckDlgButton(hwndDlg,IDC_ID3V1,TRUE); + for (int j=0; j<sizeof(ctrls)/sizeof(int); j++) + { + EnableWindow(GetDlgItem(hwndDlg,ctrls[j]),TRUE); + my_change_v1++; + SetDlgItemTextW(hwndDlg,ctrls[j],L""); + my_change_v1--; + } + } + my_change_v1++; + if (ctrls[i] == IDC_ID3_GENRE) + { + int n = (int)SendDlgItemMessage(hwndDlg, IDC_ID3_GENRE, CB_FINDSTRINGEXACT, -1, (LPARAM)buf); + SendDlgItemMessage(hwndDlg, IDC_ID3_GENRE, CB_SETCURSEL, n, 0); + } + else + SetDlgItemTextW(hwndDlg,ctrls[i],buf); + my_change_v1--; + break; + } + } + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + // this should be done by one pane ONLY. Doesn't matter which one. + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + + Stopper stopper; + if (!_wcsicmp(lastfn, meta->filename)) + stopper.Stop(); + int ret = meta->Save(); + stopper.Play(); + + wchar_t boxtitle[256] = {0}; + switch(ret) + { + case SAVE_SUCCESS: + { + // cheap way to trigger a metadata reset in a thread-safe manner + wchar_t d[10] = {0}; + extendedFileInfoStructW reset_info = {0}; + reset_info.filename=L".mp3"; + reset_info.metadata=L"artist"; + reset_info.ret = d; + reset_info.retlen=10; + SendMessage(mod.hMainWindow, WM_WA_IPC, (WPARAM)&reset_info, IPC_GET_EXTENDED_FILE_INFOW); + } + break; + case SAVE_ERROR_READONLY: + MessageBox(hwndDlg, + WASABI_API_LNGSTRINGW(IDS_METADATA_ERROR_READONLY), + WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SAVING_METADATA, boxtitle, 256), + MB_OK); + break; + case SAVE_ERROR_OPENING_FILE: + MessageBox(hwndDlg, + WASABI_API_LNGSTRINGW(IDS_METADATA_ERROR_OPENING_FILE), + WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SAVING_METADATA, boxtitle, 256), + MB_OK); + break; + case SAVE_APEV2_WRITE_ERROR: + MessageBox(hwndDlg, + WASABI_API_LNGSTRINGW(IDS_METADATA_ERROR_APEV2), + WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SAVING_METADATA, boxtitle, 256), + MB_OK); + break; + case SAVE_LYRICS3_WRITE_ERROR: + MessageBox(hwndDlg, + WASABI_API_LNGSTRINGW(IDS_METADATA_ERROR_LYRICS3), + WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SAVING_METADATA, boxtitle, 256), + MB_OK); + break; + case SAVE_ID3V1_WRITE_ERROR: + MessageBox(hwndDlg, + WASABI_API_LNGSTRINGW(IDS_METADATA_ERROR_ID3V1), + WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SAVING_METADATA, boxtitle, 256), + MB_OK); + break; + case SAVE_ID3V2_WRITE_ERROR: + MessageBox(hwndDlg, + WASABI_API_LNGSTRINGW(IDS_METADATA_ERROR_ID3V2), + WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SAVING_METADATA, boxtitle, 256), + MB_OK); + break; + default: + MessageBox(hwndDlg, + WASABI_API_LNGSTRINGW(IDS_METADATA_ERROR_UNSPECIFIED), + WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SAVING_METADATA, boxtitle, 256), + MB_OK); + break; + } + } + break; + case IDC_ID3V1_TO_V2: + { + my_change_v1=1; + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + for (int i=0; i<sizeof(ctrls)/sizeof(int); i++) + { + wchar_t buf[2048]=L""; + GetDlgItemTextW(hwndDlg,ctrls[i],buf,2048); + meta->id3v2.SetString(strs[i],buf); + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)(wchar_t*)AutoWide(strs[i]),(LPARAM)buf); + } + my_change_v1=0; + } + break; + case IDC_ID3V1: + { + our_change=1; + BOOL checked = IsDlgButtonChecked(hwndDlg,IDC_ID3V1); + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + if (!checked) + meta->id3v1.Clear(); + + for (int i=0; i<sizeof(ctrls)/sizeof(int); i++) + { + EnableWindow(GetDlgItem(hwndDlg,ctrls[i]),checked); + + wchar_t buf[2048]=L""; + if (checked) + { + GetDlgItemText(hwndDlg,ctrls[i],buf,2048); + if (buf[0]) + meta->id3v1.SetString(strs[i],buf); + } + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)(wchar_t*)AutoWide(strs[i]),(LPARAM)buf); + } + our_change=0; + } + break; + default: + if (!our_change && !my_change_v1 && (HIWORD(wParam) == EN_CHANGE || HIWORD(wParam) == CBN_SELCHANGE)) + { + our_change=1; + for (int i=0; i<sizeof(strs)/sizeof(char*); i++) + { + if (LOWORD(wParam) == ctrls[i]) + { + wchar_t buf[2048]=L""; + if (HIWORD(wParam) == EN_CHANGE) + GetDlgItemTextW(hwndDlg,ctrls[i],buf,2048); + else + { + LRESULT n = SendDlgItemMessage(hwndDlg, ctrls[i], CB_GETCURSEL, 0, 0); + n = SendDlgItemMessage(hwndDlg, ctrls[i], CB_GETITEMDATA, n, 0); + if (n>=0 && n<(LRESULT)numGenres) + lstrcpyn(buf,id3v1_genres[n],2048); + } + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + meta->id3v1.SetString(strs[i],buf); + if (!meta->id3v2.HasData()) + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)(wchar_t*)AutoWide(strs[i]),(LPARAM)buf); + } + } + our_change=0; + } + } + break; + case WM_DESTROY: + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) meta->Release(); + } + break; + } + return 0; +} + +static INT_PTR CALLBACK id3v2_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static int my_change_v2=0; + static const int ctrls[] = + { + IDC_ID3V2_TRACK, + IDC_ID3V2_TITLE, + IDC_ID3V2_ARTIST, + IDC_ID3V2_ALBUM, + IDC_ID3V2_YEAR, + IDC_ID3V2_COMMENT, + IDC_ID3V2_GENRE, + IDC_ID3V2_COMPOSER, + IDC_ID3V2_PUBLISHER, + IDC_ID3V2_MARTIST, + IDC_ID3V2_RECORD, + IDC_ID3V2_URL, + IDC_ID3V2_ENCODER, + IDC_ID3V2_ALBUM_ARTIST, + IDC_ID3V2_DISC, + IDC_TRACK_GAIN, + IDC_ALBUM_GAIN, + IDC_ID3V2_BPM, + }; + static const char * strs[] = + { + "track", + "title", + "artist", + "album", + "year", + "comment", + "genre", + "composer", + "publisher", + "originalartist", + "copyright", + "url", + "tool", + "albumartist", + "disc", + "replaygain_track_gain", // 15 + "replaygain_album_gain", // 16 + "bpm", + }; + switch (uMsg) + { + case WM_INITDIALOG: + { + our_change=1; + SendDlgItemMessage(hwndDlg, IDC_ID3V2_GENRE, CB_RESETCONTENT, 0, 0); + for (size_t x = 0; x != numGenres; x ++) + { + int y = (int)SendDlgItemMessage(hwndDlg, IDC_ID3V2_GENRE, CB_ADDSTRING, 0, (LPARAM)id3v1_genres[x]); + SendDlgItemMessage(hwndDlg, IDC_ID3V2_GENRE, CB_SETITEMDATA, y, x); + } + + Metadata *meta = GetMeta(hwndDlg); + if (meta) + meta->AddRef(); + else + { + meta = new Metadata(); + meta->Open((wchar_t*)lParam); + SetMeta(hwndDlg,meta); + } + + for (int i=0; i<sizeof(strs)/sizeof(char*); i++) + { + wchar_t buf[32768] = {0}; + meta->id3v2.GetString(strs[i],buf,32768); + if((i == 15 || i == 16) && buf[0]) + { + SetDlgItemTextW(hwndDlg,ctrls[i],L"buf"); + // this isn't nice but it localises the RG values + // for display as they're saved in the "C" locale + double value = _wtof_l(buf,WASABI_API_LNG->Get_C_NumericLocale()); + StringCchPrintfW(buf,64,L"%-+.2f dB", value); + } + SetDlgItemTextW(hwndDlg,ctrls[i],buf); + } + + if (meta->id3v2.HasData()) + CheckDlgButton(hwndDlg,IDC_ID3V2,TRUE); + else + for (int i=0; i<sizeof(ctrls)/sizeof(int); i++) + EnableWindow(GetDlgItem(hwndDlg,ctrls[i]),FALSE); + + our_change=0; + } + break; + case WM_USER: + if (wParam && lParam && !our_change && !my_change_v2) + { + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + wchar_t *key = (wchar_t*)wParam; + wchar_t *value = (wchar_t*)lParam; + AutoChar keyA(key); + for (int i=0; i<sizeof(strs)/sizeof(char*); i++) + { + if (_stricmp(keyA,strs[i])==0) + { + // benski> cut? i don't think this is what we want: meta->SetExtendedData(strs[i],value); + meta->id3v2.SetString(strs[i], value); + wchar_t buf[32768]=L""; + meta->id3v2.GetString(strs[i],buf,32768); + + if (!IsDlgButtonChecked(hwndDlg,IDC_ID3V2)) + { + // re-enable items + CheckDlgButton(hwndDlg,IDC_ID3V2,TRUE); + for (int j=0; j<sizeof(ctrls)/sizeof(int); j++) + { + EnableWindow(GetDlgItem(hwndDlg,ctrls[j]),TRUE); + my_change_v2++; + SetDlgItemTextW(hwndDlg,ctrls[j],L""); + my_change_v2--; + } + } + my_change_v2++; + SetDlgItemTextW(hwndDlg,ctrls[i],buf); + my_change_v2--; + break; + } + } + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_ID3V2_TO_V1: + { + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + my_change_v2=1; + for (int i=0; i<sizeof(ctrls)/sizeof(int); i++) + { + wchar_t buf[32768]=L""; + GetDlgItemTextW(hwndDlg,ctrls[i],buf,32768); + meta->id3v1.SetString(strs[i],buf); + meta->GetExtendedData(strs[i], buf, 32768); + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)(wchar_t*)AutoWide(strs[i]),(LPARAM)buf); + } + my_change_v2=0; + } + break; + case IDC_ID3V2: + { + our_change=1; + BOOL checked = IsDlgButtonChecked(hwndDlg,IDC_ID3V2); + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + if (!checked) + meta->id3v2.Clear(); + + for (int i=0; i<sizeof(ctrls)/sizeof(int); i++) + { + EnableWindow(GetDlgItem(hwndDlg,ctrls[i]),checked); + + wchar_t buf[32768]=L""; + if (checked) + { + GetDlgItemText(hwndDlg,ctrls[i],buf,32768); + if (buf[0]) + meta->id3v2.SetString(strs[i],buf); + } + meta->GetExtendedData(strs[i],buf,32768); + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)(wchar_t*)AutoWide(strs[i]),(LPARAM)buf); + } + our_change=0; + } + break; + case IDOK: + { + extern Metadata *m_ext_get_mp3info; + if (m_ext_get_mp3info) + m_ext_get_mp3info->Release(); + m_ext_get_mp3info=0; + } + break; + default: + if (!our_change && !my_change_v2 && (HIWORD(wParam) == EN_CHANGE || HIWORD(wParam) == CBN_SELCHANGE || HIWORD(wParam) == CBN_EDITCHANGE || HIWORD(wParam) == CBN_EDITUPDATE)) + { + our_change=1; + for (int i=0; i<sizeof(strs)/sizeof(char*); i++) + { + if (LOWORD(wParam) == ctrls[i]) + { + wchar_t buf[32768] = {0}; + if (HIWORD(wParam) == EN_CHANGE) + GetDlgItemTextW(hwndDlg,ctrls[i],buf,32768); + else + { + LRESULT n = SendMessage(GetDlgItem(hwndDlg, ctrls[i]), CB_GETCURSEL, 0, 0); + n = SendMessage(GetDlgItem(hwndDlg, ctrls[i]), CB_GETITEMDATA, n, 0); + if (n>=0 && n<(LRESULT)numGenres) + lstrcpyn(buf,id3v1_genres[n],32768); + else{ + GetDlgItemTextW(hwndDlg,ctrls[i],buf,32768); + } + } + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + meta->id3v2.SetString(strs[i],buf); + meta->GetExtendedData(strs[i],buf,32768); + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)(wchar_t*)AutoWide(strs[i]),(LPARAM)buf); + } + } + our_change=0; + } + } + break; + case WM_DESTROY: + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) + meta->Release(); + } + break; + } + return 0; +} + +static INT_PTR CALLBACK lyrics3_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static const int ctrls[] = + { + IDC_LYRICS3_TITLE, + IDC_LYRICS3_ARTIST, + IDC_LYRICS3_ALBUM, + }; + static const char * strs[] = + { + "title", + "artist", + "album", + }; + switch (uMsg) + { + case WM_INITDIALOG: + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) + meta->AddRef(); + else + { + meta = new Metadata(); + meta->Open((wchar_t*)lParam); + SetMeta(hwndDlg,meta); + } + + for (int i=0; i<sizeof(strs)/sizeof(char*); i++) + { + wchar_t buf[2048] = {0}; + SendDlgItemMessage(hwndDlg,ctrls[i],EM_SETLIMITTEXT,250,0); + meta->lyrics3.GetString(strs[i],buf,250); + SetDlgItemTextW(hwndDlg,ctrls[i],buf); + } + + if (meta->lyrics3.HasData()) + CheckDlgButton(hwndDlg,IDC_LYRICS3,TRUE); + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_LYRICS3: + { + BOOL checked = IsDlgButtonChecked(hwndDlg,IDC_LYRICS3); + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + if (!checked) + meta->lyrics3.Clear(); + else // clear the dirty state if re-enabled so we don't lose the lyrics3 tag + meta->lyrics3.ResetDirty(); + + for (int i=0; i<sizeof(ctrls)/sizeof(int); i++) + { + EnableWindow(GetDlgItem(hwndDlg,ctrls[i]),checked); + + wchar_t buf[2048]=L""; + if (checked) + { + GetDlgItemText(hwndDlg,ctrls[i],buf,2048); + if (buf[0]) + meta->lyrics3.SetString(strs[i],buf); + } + meta->GetExtendedData(strs[i],buf,2048); + // if we don't flag this then we can lose info in the id3v1 and v2 tags which is definitely bad + our_change++; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)(wchar_t*)AutoWide(strs[i]),(LPARAM)buf); + our_change--; + } + } + break; + } + break; + case WM_DESTROY: + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) meta->Release(); + } + break; + } + return 0; +} +/* ================ +APEv2 Editor Tab +================ */ + + + +static INT_PTR CALLBACK apev2_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static W_ListView listview; + static int my_change_ape=0; + switch (uMsg) + { + case WM_NOTIFYFORMAT: + return NFR_UNICODE; + + case WM_INITDIALOG: + { + our_change++; + Metadata *meta = GetMeta(hwndDlg); + if (meta) + { + meta->AddRef(); + } + else + { + meta = new Metadata(); + meta->Open((wchar_t*)lParam); + SetMeta(hwndDlg,meta); + } + + if (meta->apev2.HasData()) + CheckDlgButton(hwndDlg,IDC_APEV2,TRUE); + + listview.setwnd(GetDlgItem(hwndDlg, IDC_APE_LIST)); + listview.SetDoubleBuffered(true); + listview.AddCol(WASABI_API_LNGSTRINGW(IDS_NAME), 82); + listview.AddCol(WASABI_API_LNGSTRINGW(IDS_VALUE), 160); + + listview.SetVirtualCount((int)meta->apev2.GetNumItems()); + listview.AutoSizeColumn(0); + listview.AutoSizeColumn(1); + + SetDlgItemTextW(hwndDlg,IDC_APE_KEY,L""); + SetDlgItemTextW(hwndDlg,IDC_APE_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_KEY),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_DELETE),FALSE); + our_change--; + return 1; + } + break; + + case WM_COMMAND: + switch(LOWORD(wParam)) + { + case IDC_APE_KEY: + case IDC_APE_VALUE: + if(HIWORD(wParam) == EN_CHANGE) + { + int selected = listview.GetNextSelected(); + if (selected != LB_ERR) + { + listview.RefreshItem(selected); + } + } + else if(HIWORD(wParam) == EN_KILLFOCUS) + { + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + + my_change_ape++; + + char key[1024] = {0}; + wchar_t value[32768] = {0}; + GetDlgItemTextA(hwndDlg, IDC_APE_KEY, key, 1024); + GetDlgItemText(hwndDlg, IDC_APE_VALUE, value, 32768); + int selected = listview.GetNextSelected(); + if (selected != LB_ERR) + { + meta->apev2.SetKeyValueByIndex(selected, key, value); + } + + const wchar_t *winamp_key = APE::MapApeKeyToWinampKeyW(key); + if (winamp_key) + { + our_change++; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)winamp_key,(WPARAM)value); + our_change--; + } + my_change_ape--; + } + break; + + case IDC_APEV2: + { + BOOL checked = IsDlgButtonChecked(hwndDlg,IDC_APEV2); + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + if (!checked) + meta->apev2.MarkClear(); + else // clear the dirty state if re-enabled so we don't lose the apev2 tag + meta->apev2.ResetDirty(); + } + break; + + case IDC_DELETE_ALL: + { + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + my_change_ape++; + listview.UnselectAll(); + meta->apev2.Clear(); + listview.SetVirtualCount((int)meta->apev2.GetNumItems()); + my_change_ape--; + } + break; + + case IDC_APE_ADD: + { + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + int index = listview.GetCount(); + if (meta->apev2.AddItem() == APEv2::APEV2_SUCCESS) + { + listview.SetVirtualCount((int)meta->apev2.GetNumItems()); + listview.SetSelected(index); + listview.ScrollTo(index); + SetFocus(GetDlgItem(hwndDlg, IDC_APE_KEY)); + } + } + break; + + case IDC_APE_DELETE: + { + Metadata *meta = GetMeta(hwndDlg); + if (!meta) break; + int selected = listview.GetNextSelected(); + if (selected != LB_ERR) + { + listview.UnselectAll(); + meta->apev2.RemoveItem(selected); + listview.SetVirtualCount((int)meta->apev2.GetNumItems()); + } + } + break; + } + break; + + case WM_NOTIFY: + { + LPNMHDR l=(LPNMHDR)lParam; + if (l->idFrom==IDC_APE_LIST) switch (l->code) + { + case LVN_GETDISPINFO: + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) + { + NMLVDISPINFO *lpdi = (NMLVDISPINFO*) l; + if (lpdi->item.mask & LVIF_TEXT) + { + int selected = listview.GetNextSelected(); + switch (lpdi->item.iSubItem) + { + case 0: + { + if (lpdi->item.iItem == selected) + { + GetDlgItemText(hwndDlg, IDC_APE_KEY, lpdi->item.pszText, lpdi->item.cchTextMax); + } + else + { + const char *key=0; + meta->apev2.EnumValue(lpdi->item.iItem, &key, 0, 0); + MultiByteToWideCharSZ(CP_ACP, 0, key?key:"", -1, lpdi->item.pszText, lpdi->item.cchTextMax); + } + } + return 0; + + case 1: + { + if (lpdi->item.iItem == selected) + { + GetDlgItemText(hwndDlg, IDC_APE_VALUE, lpdi->item.pszText, lpdi->item.cchTextMax); + } + else + { + const char *key=0; + meta->apev2.EnumValue(lpdi->item.iItem, &key, lpdi->item.pszText, lpdi->item.cchTextMax); + } + } + return 0; + } + } + } + } + break; + + case LVN_KEYDOWN: + break; + + case LVN_ITEMCHANGED: + { + my_change_ape++; + LPNMLISTVIEW lv=(LPNMLISTVIEW)lParam; + if (lv->uNewState & LVIS_SELECTED) + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) + { + const char *key=0; + wchar_t value[32768] = {0}; + meta->apev2.EnumValue(lv->iItem, &key, value, 32768); + SetDlgItemTextA(hwndDlg,IDC_APE_KEY,key); + SetDlgItemText(hwndDlg,IDC_APE_VALUE,value); + BOOL editable = meta->apev2.IsItemReadOnly(lv->iItem)?FALSE:TRUE; + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_KEY),editable); + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_VALUE),editable); + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_DELETE),TRUE); + listview.RefreshItem(lv->iItem); + } + } + if (lv->uOldState & LVIS_SELECTED) + { + SetDlgItemTextW(hwndDlg,IDC_APE_KEY,L""); + SetDlgItemTextW(hwndDlg,IDC_APE_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_KEY),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_APE_DELETE),FALSE); + } + my_change_ape--; + } + } + } + break; + + case WM_USER: + if (wParam && lParam && !our_change && !my_change_ape) + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) + { + const wchar_t *keyW = (const wchar_t *)wParam; + const wchar_t *value = (const wchar_t *)lParam; + AutoChar key(keyW); + my_change_ape++; + meta->apev2.SetString(key, value); + listview.UnselectAll(); + listview.SetVirtualCount((int)meta->apev2.GetNumItems()); + listview.RefreshAll(); + my_change_ape--; + } + } + break; + + case WM_DESTROY: + { + Metadata *meta = GetMeta(hwndDlg); + if (meta) meta->Release(); + } + break; + } + return 0; +} + +extern "C" +{ + // return 1 if you want winamp to show it's own file info dialogue, 0 if you want to show your own (via In_Module.InfoBox) + // if returning 1, remember to implement winampGetExtendedFileInfo("formatinformation")! + __declspec(dllexport) int winampUseUnifiedFileInfoDlg(const wchar_t * fn) + { + if (!_wcsnicmp(fn, L"file://", 7)) fn += 7; + if (PathIsURLW(fn)) return 2; + return 1; + } + + // should return a child window of 513x271 pixels (341x164 in msvc dlg units), or return NULL for no tab. + // Fill in name (a buffer of namelen characters), this is the title of the tab (defaults to "Advanced"). + // filename will be valid for the life of your window. n is the tab number. This function will first be + // called with n == 0, then n == 1 and so on until you return NULL (so you can add as many tabs as you like). + // The window you return will recieve WM_COMMAND, IDOK/IDCANCEL messages when the user clicks OK or Cancel. + // when the user edits a field which is duplicated in another pane, do a SendMessage(GetParent(hwnd),WM_USER,(WPARAM)L"fieldname",(LPARAM)L"newvalue"); + // this will be broadcast to all panes (including yours) as a WM_USER. + __declspec(dllexport) HWND winampAddUnifiedFileInfoPane(int n, const wchar_t * filename, HWND parent, wchar_t *name, size_t namelen) + { + if (n == 0) + { + SetPropW(parent,L"INBUILT_NOWRITEINFO", (HANDLE)1); + lstrcpyn(name,L"ID3v1", (int)namelen); + return WASABI_API_CREATEDIALOGPARAMW(IDD_INFO_ID3V1, parent, id3v1_dlgproc, (LPARAM)filename); + } + if (n == 1) + { + lstrcpyn(name,L"ID3v2", (int)namelen); + return WASABI_API_CREATEDIALOGPARAMW(IDD_INFO_ID3V2, parent, id3v2_dlgproc, (LPARAM)filename); + } + if (n == 2) + { + Metadata *meta = (Metadata *)GetPropW(parent, L"mp3info"); + if (meta->lyrics3.HasData()) + { + lstrcpyn(name,L"Lyrics3", (int)namelen); + return WASABI_API_CREATEDIALOGPARAMW(IDD_INFO_LYRICS3, parent, lyrics3_dlgproc, (LPARAM)filename); + } + else if (meta->apev2.HasData()) + { + lstrcpyn(name,L"APEv2", (int)namelen); + return WASABI_API_CREATEDIALOGPARAMW(IDD_INFO_APEV2, parent, apev2_dlgproc, (LPARAM)filename); + } + } + if (n == 3) + { + Metadata *meta = (Metadata *)GetPropW(parent, L"mp3info"); + if (meta->lyrics3.HasData() && meta->apev2.HasData()) + { + lstrcpyn(name,L"APEv2", (int)namelen); + return WASABI_API_CREATEDIALOGPARAMW(IDD_INFO_APEV2, parent, apev2_dlgproc, (LPARAM)filename); + } + } + return NULL; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/ifc_mpeg_stream_reader.h b/Src/Plugins/Input/in_mp3/ifc_mpeg_stream_reader.h new file mode 100644 index 00000000..7370ed79 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/ifc_mpeg_stream_reader.h @@ -0,0 +1,45 @@ +#pragma once + +#include <bfc/dispatch.h> + +class ifc_mpeg_stream_reader : public Dispatchable +{ +protected: + ifc_mpeg_stream_reader() {} + ~ifc_mpeg_stream_reader() {} + +public: + int MPEGStream_Peek( void *buffer, size_t to_read, size_t *bytes_read ); + int MPEGStream_Read( void *buffer, size_t to_read, size_t *bytes_read ); + int MPEGStream_EOF(); + float MPEGStream_Gain(); + + DISPATCH_CODES + { + MPEGSTREAM_PEEK = 0, + MPEGSTREAM_READ = 1, + MPEGSTREAM_EOF = 2, + MPEGSTREAM_GAIN = 3, + }; +}; + +inline int ifc_mpeg_stream_reader::MPEGStream_Peek( void *buffer, size_t to_read, size_t *bytes_read ) +{ + return _call( MPEGSTREAM_PEEK, (int)1, buffer, to_read, bytes_read ); +} + +inline int ifc_mpeg_stream_reader::MPEGStream_Read( void *buffer, size_t to_read, size_t *bytes_read ) +{ + return _call( MPEGSTREAM_READ, (int)1, buffer, to_read, bytes_read ); +} + +inline int ifc_mpeg_stream_reader::MPEGStream_EOF() +{ + return _call( MPEGSTREAM_EOF, (int)true ); +} + +inline float ifc_mpeg_stream_reader::MPEGStream_Gain() +{ + return _call( MPEGSTREAM_GAIN, (float)1.0f ); +} + diff --git a/Src/Plugins/Input/in_mp3/in_mp3.rc b/Src/Plugins/Input/in_mp3/in_mp3.rc new file mode 100644 index 00000000..714bc992 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/in_mp3.rc @@ -0,0 +1,503 @@ +// 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 + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_PREFS, DIALOG + BEGIN + LEFTMARGIN, 6 + RIGHTMARGIN, 220 + TOPMARGIN, 6 + BOTTOMMARGIN, 121 + END + + IDD_OUTPUT, DIALOG + BEGIN + LEFTMARGIN, 6 + RIGHTMARGIN, 220 + TOPMARGIN, 6 + BOTTOMMARGIN, 156 + END + + IDD_HTTP, DIALOG + BEGIN + LEFTMARGIN, 6 + RIGHTMARGIN, 220 + TOPMARGIN, 6 + BOTTOMMARGIN, 148 + END + + IDD_TAGOPTS, DIALOG + BEGIN + LEFTMARGIN, 6 + RIGHTMARGIN, 220 + TOPMARGIN, 6 + BOTTOMMARGIN, 163 + END + + IDD_HTTPAUTH, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 153 + TOPMARGIN, 7 + BOTTOMMARGIN, 71 + END + + IDD_ADVANCED_TAGGING, DIALOG + BEGIN + LEFTMARGIN, 6 + RIGHTMARGIN, 220 + TOPMARGIN, 6 + BOTTOMMARGIN, 116 + END +END +#endif // APSTUDIO_INVOKED + + +#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_PREFS DIALOGEX 0, 0, 226, 127 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION +CAPTION "General" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "File Association",IDC_STATIC,6,6,214,61 + LTEXT "Extension list (semicolon delimited):",IDC_STATIC,12,18,140,8 + EDITTEXT IDC_EDIT1,12,31,201,12,ES_AUTOHSCROLL + PUSHBUTTON "Set to default",IDC_BUTTON1,12,49,50,13 + LTEXT "Default: MP3;MP2;MP1;AAC;VLB",IDC_STATIC,68,51,144,8 + GROUPBOX "Full File Buffering",IDC_STATIC,6,71,214,32 + LTEXT "Buffer the entire file from disk if the file is smaller than:",IDC_STATIC,13,81,115,16 + EDITTEXT IDC_BUFMAX,141,83,24,12,ES_AUTOHSCROLL | ES_NUMBER + LTEXT "kilobytes",IDC_STATIC,169,85,28,8 +END + +IDD_OUTPUT DIALOGEX 0, 0, 226, 163 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION +CAPTION "Decoder" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "Quality",IDC_STATIC,6,2,213,50 + CONTROL "&Full",IDC_FULLRATE,"Button",BS_AUTORADIOBUTTON | WS_GROUP,12,12,30,10 + CONTROL "&Half",IDC_HALFRATE,"Button",BS_AUTORADIOBUTTON,12,24,30,10 + CONTROL "&Quarter",IDC_QRATE,"Button",BS_AUTORADIOBUTTON,12,36,40,10 + LTEXT "Selecting 'Half' or 'Quarter' quality will downsample the output, resulting in less processor usage.",IDC_STATIC,80,12,125,34 + GROUPBOX "Misc Options",IDC_STATIC,6,55,213,40 + CONTROL "CRC checking",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,67,105,10 + CONTROL "Show average bitrate on VBR files",IDC_CHECK2,"Button",BS_AUTOCHECKBOX | BS_MULTILINE | WS_TABSTOP,12,80,180,10 + GROUPBOX "Equalizer",IDC_STATIC,6,98,213,61 + CONTROL "Logarithmic EQ (default)",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,12,110,128,10 + CONTROL "Linear EQ",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,12,120,72,10 + CONTROL "Fast Layer 3 EQ",IDC_FASTL3EQ,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,132,98,10 + CONTROL "Fast Layer 1/2 EQ",IDC_FASTL12EQ,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,144,106,10 +END + +IDD_HTTP DIALOGEX 0, 0, 226, 154 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION +CAPTION "Streaming" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "Streaming Data Buffer",IDC_STATIC,6,6,86,64 + EDITTEXT IDC_BUFFERS_NUMBUFS,12,18,27,12 + LTEXT "KB",IDC_STATIC,42,20,10,8 + LTEXT "Increase this value to give better skip protection on slower connections.",IDC_STATIC,12,33,73,33 + GROUPBOX "Streaming Prebuffer",IDC_STATIC,96,6,124,100 + CONTROL "Slider1",IDC_PREBUFSLIDER,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,102,18,111,10 + CTEXT "0% 50% 100%",IDC_STATIC,103,31,110,8 + CTEXT "(how much to prebuffer at the start of a stream)",IDC_STATIC,105,42,105,16 + CONTROL "Slider1",IDC_PREBUFSLIDER2,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,102,61,111,10 + CTEXT "0% 50% 100%",IDC_STATIC,103,74,110,8 + CTEXT "(how much to prebuffer after a buffer underrun)",IDC_STATIC,105,85,105,16 + GROUPBOX "Streaming Extensions",IDC_STATIC,6,72,86,76 + CONTROL "Enable SHOUTcast title support",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,12,83,74,18 + CONTROL "Include stream name in title",IDC_CHECK3,"Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,12,105,74,18 + CONTROL "Enable SHOUTcast v2 artwork support",IDC_SC_ARTWORK, + "Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,12,126,74,18 + GROUPBOX "Saving",IDC_STATIC,96,110,124,38 + CONTROL "Save files to:",IDC_CHECK2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,102,120,57,10 + PUSHBUTTON "",IDC_BUTTON2,102,132,112,11 +END + +IDD_TAGOPTS DIALOGEX 0, 0, 226, 168 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION +CAPTION "ID3 Tags" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "ID3 Tag Reading",IDC_STATIC,6,6,214,122 + CONTROL "Read ID3v1 tags",IDC_READ_ID3V1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,19,70,10 + CONTROL "Read ID3v2 tags",IDC_READ_ID3V2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,31,70,10 + LTEXT "Read ASCII tags as:",IDC_STATIC,98,16,65,8 + COMBOBOX IDC_COMBO1,109,28,80,40,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + GROUPBOX "ID3 Tag Writing",IDC_STATIC,6,47,214,81 + CONTROL "Modify ID3v1 tags",IDC_WRITE_ID3V1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,60,73,10 + CONTROL "Modify ID3v2 tags",IDC_WRITE_ID3V2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,72,73,10 + LTEXT "Write tags as:",IDC_STATIC,98,56,45,8 + COMBOBOX IDC_COMBO2,109,67,80,40,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + GROUPBOX "ID3 Tag Creation",IDC_STATIC,6,86,214,42 + CONTROL "Create new ID3v1 tags when adding metadata",IDC_CREATE_ID3V1, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,100,163,10 + LTEXT "",IDC_STATIC,17,107,8,8 + CONTROL "Create new ID3v2 tags when adding metadata",IDC_CREATE_ID3V2, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,112,163,10 + GROUPBOX "ID3v2 Tag Rating Field Email Address",IDC_STATIC,6,132,214,31 + EDITTEXT IDC_RATING_EMAIL,13,144,162,12,ES_AUTOHSCROLL + PUSHBUTTON "Reset",IDC_RATING_EMAIL_RESET,179,143,36,14 +END + +IDD_HTTPAUTH DIALOGEX 0, 0, 160, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "HTTP Login Required" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LTEXT "Realm: ",IDC_STATIC,7,7,25,8 + LTEXT "",IDC_REALM,40,7,113,8 + LTEXT "Enter your login and password in the form of:\n\tlogin:password",IDC_STATIC,7,18,146,17 + EDITTEXT IDC_EDIT1,7,39,146,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,58,50,13 + PUSHBUTTON "Cancel",IDCANCEL,61,58,50,13 +END + +IDD_INFO_ID3V1 DIALOGEX 0, 0, 341, 164 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "ID3v1",IDC_STATIC,0,0,175,133 + CONTROL "&Include ID3v1 tag in file",IDC_ID3V1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,9,100,10 + RTEXT "Track #",IDC_STATIC,7,24,36,8 + EDITTEXT IDC_ID3V11_TRACK,46,22,19,12,ES_AUTOHSCROLL | ES_NUMBER + RTEXT "Title",IDC_STATIC,7,39,36,8 + EDITTEXT IDC_ID3_TITLE,46,37,124,12,ES_AUTOHSCROLL + RTEXT "Artist",IDC_STATIC,7,54,36,8 + EDITTEXT IDC_ID3_ARTIST,46,52,124,12,ES_AUTOHSCROLL + RTEXT "Album",IDC_STATIC,7,69,36,8 + EDITTEXT IDC_ID3_ALBUM,46,67,124,12,ES_AUTOHSCROLL + RTEXT "Year",IDC_STATIC,7,83,36,8 + EDITTEXT IDC_ID3_YEAR,46,81,31,12,ES_NUMBER + RTEXT "Genre",IDC_STATIC,81,83,22,8 + COMBOBOX IDC_ID3_GENRE,106,81,64,159,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP + RTEXT "Comment",IDC_STATIC,7,99,36,8 + EDITTEXT IDC_ID3_COMMENT,46,97,123,12,ES_AUTOHSCROLL + PUSHBUTTON "Copy &to ID3v2",IDC_ID3V1_TO_V2,107,113,62,14 +END + +IDD_INFO_ID3V2 DIALOGEX 0, 0, 341, 164 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "ID3v2",IDC_STATIC,0,0,341,164 + CONTROL "&Include ID3v2 tag in file",IDC_ID3V2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,9,108,10 + RTEXT "Track #",IDC_STATIC,7,24,36,8 + EDITTEXT IDC_ID3V2_TRACK,46,22,36,12,ES_AUTOHSCROLL + RTEXT "Disc #",IDC_STATIC,90,24,36,8 + EDITTEXT IDC_ID3V2_DISC,128,22,42,12,ES_AUTOHSCROLL + RTEXT "Title",IDC_STATIC,7,39,36,8 + EDITTEXT IDC_ID3V2_TITLE,46,37,124,12,ES_AUTOHSCROLL + RTEXT "Artist",IDC_STATIC,7,54,36,8 + EDITTEXT IDC_ID3V2_ARTIST,46,52,124,12,ES_AUTOHSCROLL + RTEXT "Album",IDC_STATIC,7,69,36,8 + EDITTEXT IDC_ID3V2_ALBUM,46,67,124,12,ES_AUTOHSCROLL + RTEXT "Year",IDC_STATIC,7,83,36,8 + EDITTEXT IDC_ID3V2_YEAR,46,81,31,12,ES_AUTOHSCROLL | ES_NUMBER + RTEXT "Genre",IDC_STATIC,81,83,22,8 + COMBOBOX IDC_ID3V2_GENRE,106,81,64,159,CBS_DROPDOWN | CBS_AUTOHSCROLL | CBS_SORT | WS_VSCROLL | WS_TABSTOP + RTEXT "Comment",IDC_STATIC,7,99,36,8 + EDITTEXT IDC_ID3V2_COMMENT,46,97,124,29,ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL + RTEXT "Album Artist",IDC_STATIC,3,131,40,8 + EDITTEXT IDC_ID3V2_ALBUM_ARTIST,46,129,124,12,ES_AUTOHSCROLL + RTEXT "Composer",IDC_STATIC,7,146,36,8 + EDITTEXT IDC_ID3V2_COMPOSER,46,144,124,12,ES_AUTOHSCROLL + RTEXT "Publisher",IDC_STATIC,172,24,37,8 + EDITTEXT IDC_ID3V2_PUBLISHER,212,22,124,12,ES_AUTOHSCROLL + RTEXT "Orig. Artist",IDC_STATIC,173,39,36,8 + EDITTEXT IDC_ID3V2_MARTIST,212,37,124,12,ES_AUTOHSCROLL + RTEXT "Copyright",IDC_STATIC,173,54,36,8 + EDITTEXT IDC_ID3V2_RECORD,212,52,124,12,ES_AUTOHSCROLL + RTEXT "URL",IDC_STATIC,173,69,36,8 + EDITTEXT IDC_ID3V2_URL,212,67,124,12,ES_AUTOHSCROLL + RTEXT "Encoded by",IDC_STATIC,173,84,36,8 + EDITTEXT IDC_ID3V2_ENCODER,212,82,124,12,ES_AUTOHSCROLL + RTEXT "BPM",IDC_STATIC,173,99,36,8 + EDITTEXT IDC_ID3V2_BPM,212,97,41,12,ES_AUTOHSCROLL + RTEXT "Track Gain",IDC_STATIC,173,114,36,8 + EDITTEXT IDC_TRACK_GAIN,212,112,41,12,ES_AUTOHSCROLL | ES_READONLY + RTEXT "Album Gain",IDC_STATIC,255,114,36,8 + EDITTEXT IDC_ALBUM_GAIN,294,112,41,12,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "Copy &to ID3v1",IDC_ID3V2_TO_V1,272,144,62,14 +END + +IDD_INFO_LYRICS3 DIALOGEX 0, 0, 341, 164 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Lyrics3",-1,0,0,196,70 + CONTROL "&Include Lyrics3 tag in file",IDC_LYRICS3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,9,108,10 + RTEXT "Title",-1,7,24,36,8 + EDITTEXT IDC_LYRICS3_TITLE,46,22,144,12,ES_AUTOHSCROLL | ES_READONLY + RTEXT "Artist",-1,7,39,36,8 + EDITTEXT IDC_LYRICS3_ARTIST,46,37,144,12,ES_AUTOHSCROLL | ES_READONLY + RTEXT "Album",-1,7,54,36,8 + EDITTEXT IDC_LYRICS3_ALBUM,46,52,144,12,ES_AUTOHSCROLL | ES_READONLY +END + +IDD_INFO_APEV2 DIALOGEX 0, 0, 341, 164 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "APEv2",IDC_STATIC,0,0,341,164 + CONTROL "&Include APEv2 tag in file",IDC_APEV2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,9,108,10 + CONTROL "",IDC_APE_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_OWNERDATA | WS_BORDER | WS_TABSTOP,6,20,163,136 + LTEXT "Name:",IDC_STATIC,176,20,22,8 + EDITTEXT IDC_APE_KEY,176,31,158,14,ES_AUTOHSCROLL + LTEXT "Value:",IDC_STATIC,176,47,21,8 + EDITTEXT IDC_APE_VALUE,176,57,158,81,ES_MULTILINE | ES_AUTOHSCROLL | ES_OEMCONVERT | ES_WANTRETURN | WS_VSCROLL + PUSHBUTTON "Add New",IDC_APE_ADD,176,142,50,14 + PUSHBUTTON "Delete",IDC_APE_DELETE,230,142,50,14 + PUSHBUTTON "Delete All",IDC_DELETE_ALL,284,142,50,14 +END + +IDD_ADVANCED_TAGGING DIALOGEX 0, 0, 226, 122 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION +CAPTION "APEv2 and Lyrics3" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "APEv2 Tags",-1,6,6,214,111 + CONTROL "Read APEv2 tags",IDC_READ_APEV2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,17,73,10 + CONTROL "Modify existing APEv2 tags when updating metadata",IDC_WRITE_APEV2, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,29,181,10 + CONTROL "Create new APEv2 tags when adding metadata",IDC_CREATE_APEV2, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,40,165,10 + LTEXT "When writing APEv2 headers:",IDC_STATIC_APEV2_HEADER,13,54,96,8 + COMBOBOX IDC_APEV2_HEADER_OPTIONS,25,66,166,40,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Lyrics3 Tags",-1,6,90,214,27 + CONTROL "Read Lyrics3 tags",IDC_READ_LYRICS3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,102,73,10 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_MPEG_AUDIO_DECODER_OLD "Nullsoft MPEG Audio Decoder" + IDS_CANNOT_WRITE_STREAMS_TO_DISK "Sorry cannot write streams to disk" + IDS_BUFFER_X "Buffer: %d%%" + IDS_ERROR_SYNCING_TO_STREAM "Error syncing to stream" + IDS_CONNECTING "Connecting" + IDS_NO_VALID_MULTICONNECT_URL "No valid Multiconnect URL" + IDS_REDIRECT_LIMIT_EXCEEDED "Redirect limit exceeded" + IDS_READING_ID3 "Reading ID3" + IDS_STREAM_TERMINATED "Stream terminated" + IDS_STREAM_TEMPORARILY_INTERRUPTED "Stream temporarily interrupted" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_MPEG_AUDIO_DECODER "Nullsoft MPEG Audio Decoder v%s" + 65535 "{CD3EEF98-011C-4213-BC16-3F91C937B9B8}" +END + +STRINGTABLE +BEGIN + IDS_NETWORK_RECEIVED_X_BYTES "Network received: %llu bytes\n" + IDS_SERVER "Server: %s\n" + IDS_CONTENT_TYPE "Content-Type: %s\n" + IDS_ULTRAVOX_SYNC "Ultravox sync: %d messages, %d desyncs\n" + IDS_ULTRAVOX_DATA_MESSAGE "Ultravox Data Message: 0x%X\n" + IDS_ULTRAVOX_SID_AVGBR_MAXBR "Ultravox SID/AvgBR/MaxBR: %d/%d/%d\n" + IDS_METADATA_RECEIVED "Metadata received: %d bytes\n" + IDS_METADATA_INTERVAL "Metadata interval: %d bytes\n" + IDS_ID3v2_TAG "ID3v2 tag: %d bytes\n" + IDS_VBR_LEADING_FRAME "VBR leading frame: %d bytes\n" + IDS_STREAM_NAME "Stream name: %s\n" + IDS_CURRENT_TITLE "Current title: %s\n" + IDS_CONTENT_LENGTH "Content length: %d bytes\n" + IDS_SAVING_TO "Saving to: %s\n" +END + +STRINGTABLE +BEGIN + IDS_CUSTOM "Custom" + IDS_MONO "Mono" + IDS_STEREO "Stereo" + IDS_3_CHANNEL "3 channel" + IDS_4_CHANNEL "4 channel" + IDS_SURROUND "Surround" + IDS_5_1 "5.1" + IDS_7_1 "7.1" + IDS_ERROR "error" + IDS_NONE "None" + IDS_50_15_MICROSEC "50/15 microsec" + IDS_INVALID "invalid" + IDS_JOINT_STEREO "Joint Stereo" + IDS_2_CHANNEL "2 Channel" + IDS_PAYLOAD_SIZE "Payload Size: %u bytes" + IDS_FORMAT_AAC "\nFormat: AAC" +END + +STRINGTABLE +BEGIN + IDS_MPEG2_HE_AAC_IS "\nMPEG-2 HE-AAC (Implicitly Signalled)" + IDS_MPEG4_HE_AAC_IS "\nMPEG-4 HE-AAC (Implicitly Signalled)" + IDS_SAMPLE_RATE_OUTPUT "\nSample Rate: %u (Output: %u)" + IDS_SAMPLE_RATE "\nSample Rate: %u " + IDS_SBR_PRESENT "\nSBR: Present" + IDS_SBR_NOT_PRESENT "\nSBR: Not Present" + IDS_CHANNELS_OUTPUT "\nChannels: %u (Output: %u)" + IDS_CHANNELS "\nChannels: %u" + IDS_MODE_MONO " Mode: Mono" + IDS_MODE_STEREO " Mode: Stereo" + IDS_MODE_PARAMETRIC_STEREO " Mode: Parametric Stereo" + IDS_MODE_DUAL_CHANNEL " Mode: Dual Channel" + IDS_MODE_4_CHANNEL_2_CPE " Mode: 4 Channel 2 CPE" + IDS_MODE_4_CHANNEL_MPEG " Mode: 4 Channel MPEG" + IDS_MODE_5_CHANNEL " Mode: 5 Channel" + IDS_MODE_5_1 " Mode: 5.1" +END + +STRINGTABLE +BEGIN + IDS_MODE_6_1 " Mode: 6.1" + IDS_MODE_7_1 " Mode: 7.1" + IDS_BITRATE "\nBitrate: %u" + IDS_AVERAGE_BITRATE "\nBitrate: VBR (%u)" + IDS_HEADER_FOUND_AT_X_BYTES "\nHeader found at: %d bytes" + IDS_LENGTH_X_SECONDS "\nLength: %u seconds" + IDS_PROFILE "\nProfile: %s" + IDS_YES "Yes" + IDS_NO "No" + IDS_ENC_DELAY_ZERO_PADDING "\nEnc Delay: %u, Zero Padding: %u" + IDS_S_LAYER_X "\n%s layer %d" + IDS_X_KBIT "\n%d kbps, %d frames" + IDS_X_KBIT_APPROX "\n%d kbps, approx. %d frames" + IDS_X_KBIT_VBR "\n%d kbps (VBR%s), %d frames" + IDS_X_HZ_S "\n%d Hz %s" + IDS_COPYRIGHTED ", Copyrighted: %s" +END + +STRINGTABLE +BEGIN + IDS_ORIGINAL "\nOriginal: %s" + IDS_EMPHASIS ", Emphasis: %s" + IDS_EXT_MP3_SURROUND "\nExtension: MP3 Surround" + IDS_MP3_HAS_BEEN_MODIFIED_NOT_ALL_MAY_BE_CORRECT + "\nMP3 has been modified from its original encoding. Some information may be incorrect." + IDS_MPEG_AUDIO_FILES "MPEG Audio Files (" +END + +STRINGTABLE +BEGIN + IDS_MPEG_AUDIO_DECODER_SETTINGS "MPEG Audio Decoder Settings" + IDS_LATIN_1 "Latin-1" + IDS_SYSTEM_LANGUAGE "System Language" + IDS_UNICODE_UTF_16 "Unicode (UTF-16)" + IDS_SELECT_DIRECTORY_TO_SAVE_TO "Select directory to save streamed mp3s" +END + +STRINGTABLE +BEGIN + IDS_X_KBIT_ABR "\n%d kbps (ABR), %d frames" + IDS_NAME "Name" +END + +STRINGTABLE +BEGIN + IDS_VALUE "Value" + IDS_APEV2_RETAIN_HEADER "Retain existing header" + IDS_APEV2_ADD_HEADER "Add header" + IDS_APEV2_REMOVE_HEADER "Remove header" + IDS_ERROR_SAVING_METADATA "Error saving metadata" + IDS_METADATA_ERROR_READONLY "Cannot save metadata: File is read-only!" + IDS_METADATA_ERROR_OPENING_FILE + "Cannot save metadata: Error opening file." + IDS_METADATA_ERROR_APEV2 "Cannot save metadata: APEv2 tag writing failed." + IDS_METADATA_ERROR_LYRICS3 + "Cannot save metadata: Lyrics3 tag writing failed." + IDS_METADATA_ERROR_ID3V1 "Cannot save metadata: ID3v1 tag writing failed." + IDS_METADATA_ERROR_ID3V2 "Cannot save metadata: ID3v2 tag writing failed." + IDS_METADATA_ERROR_UNSPECIFIED "Cannot save metadata: Unspecified error." + IDS_TIMED_OUT "timed out" + IDS_FAMILY_STRING_MP3 "MPEG Layer 3 Audio File" + IDS_FAMILY_STRING_MP2 "MPEG Layer 2 Audio File" + IDS_FAMILY_STRING_MP1 "MPEG Layer 1 Audio File" +END + +STRINGTABLE +BEGIN + IDS_FAMILY_STRING_MPEG2_AAC "MPEG-2 Advanced Audio Coding File" + IDS_FAMILY_STRING_DOLBY "Dolby Very Low Bitrate AAC File" + IDS_ABOUT_TEXT "%s\n© 1998-2023 Winamp SA\n\nBuild date: %hs\n\nMPEG Audio Decoding provided by libmpg123"//MPEG Layer-3 audio compression technology\nlicensed by Fraunhofer IIS & THOMSON multimedia.\n\nMPEG-4 AAC decoding licensed by Fraunhofer IIS" + IDS_LENGTH_X_PART_SECONDS "\nLength: %.2f seconds" +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_mp3/in_mp3.sln b/Src/Plugins/Input/in_mp3/in_mp3.sln new file mode 100644 index 00000000..77d30f44 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/in_mp3.sln @@ -0,0 +1,102 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29424.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_mp3", "in_mp3.vcxproj", "{AEA9FF14-57EC-49AB-BC3A-764A011AC002}" + ProjectSection(ProjectDependencies) = postProject + {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F} + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} + {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "apev2", "..\apev2\apev2.vcxproj", "{48387E27-2666-4ACF-9C21-AE508C529580}" + ProjectSection(ProjectDependencies) = postProject + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\replicant\zlib\zlib.vcxproj", "{0F9730E4-45DA-4BD2-A50A-403A4BC9751A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "id3v2", "..\id3v2\id3v2.vcxproj", "{0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\replicant\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\replicant\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}" + ProjectSection(ProjectDependencies) = postProject + {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsutil", "..\nsutil\nsutil.vcxproj", "{DABE6307-F8DD-416D-9DAC-673E2DECB73F}" +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 + {AEA9FF14-57EC-49AB-BC3A-764A011AC002}.Debug|Win32.ActiveCfg = Debug|Win32 + {AEA9FF14-57EC-49AB-BC3A-764A011AC002}.Debug|Win32.Build.0 = Debug|Win32 + {AEA9FF14-57EC-49AB-BC3A-764A011AC002}.Debug|x64.ActiveCfg = Debug|x64 + {AEA9FF14-57EC-49AB-BC3A-764A011AC002}.Debug|x64.Build.0 = Debug|x64 + {AEA9FF14-57EC-49AB-BC3A-764A011AC002}.Release|Win32.ActiveCfg = Release|Win32 + {AEA9FF14-57EC-49AB-BC3A-764A011AC002}.Release|Win32.Build.0 = Release|Win32 + {AEA9FF14-57EC-49AB-BC3A-764A011AC002}.Release|x64.ActiveCfg = Release|x64 + {AEA9FF14-57EC-49AB-BC3A-764A011AC002}.Release|x64.Build.0 = Release|x64 + {48387E27-2666-4ACF-9C21-AE508C529580}.Debug|Win32.ActiveCfg = Debug|Win32 + {48387E27-2666-4ACF-9C21-AE508C529580}.Debug|Win32.Build.0 = Debug|Win32 + {48387E27-2666-4ACF-9C21-AE508C529580}.Debug|x64.ActiveCfg = Debug|x64 + {48387E27-2666-4ACF-9C21-AE508C529580}.Debug|x64.Build.0 = Debug|x64 + {48387E27-2666-4ACF-9C21-AE508C529580}.Release|Win32.ActiveCfg = Release|Win32 + {48387E27-2666-4ACF-9C21-AE508C529580}.Release|Win32.Build.0 = Release|Win32 + {48387E27-2666-4ACF-9C21-AE508C529580}.Release|x64.ActiveCfg = Release|x64 + {48387E27-2666-4ACF-9C21-AE508C529580}.Release|x64.Build.0 = Release|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.ActiveCfg = Debug|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.Build.0 = Debug|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.ActiveCfg = Debug|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.Build.0 = Debug|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.ActiveCfg = Release|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.Build.0 = Release|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.ActiveCfg = Release|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.Build.0 = Release|x64 + {0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}.Debug|Win32.ActiveCfg = Debug|Win32 + {0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}.Debug|Win32.Build.0 = Debug|Win32 + {0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}.Debug|x64.ActiveCfg = Debug|x64 + {0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}.Debug|x64.Build.0 = Debug|x64 + {0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}.Release|Win32.ActiveCfg = Release|Win32 + {0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}.Release|Win32.Build.0 = Release|Win32 + {0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}.Release|x64.ActiveCfg = Release|x64 + {0C7AAFA2-2A78-4B91-99A3-3E866B484ADB}.Release|x64.Build.0 = Release|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.ActiveCfg = Debug|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.Build.0 = Debug|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.ActiveCfg = Debug|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.Build.0 = Debug|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.ActiveCfg = Release|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.Build.0 = Release|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.ActiveCfg = Release|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FC646532-2050-40A5-A2AB-F699F1C071C4} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_mp3/in_mp3.vcxproj b/Src/Plugins/Input/in_mp3/in_mp3.vcxproj new file mode 100644 index 00000000..7a08280a --- /dev/null +++ b/Src/Plugins/Input/in_mp3/in_mp3.vcxproj @@ -0,0 +1,386 @@ +<?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>{AEA9FF14-57EC-49AB-BC3A-764A011AC002}</ProjectGuid> + <RootNamespace>in_mp3</RootNamespace> + <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" 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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|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> + <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"> + <VcpkgEnableManifest>false</VcpkgEnableManifest> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_DEBUG;AAC_SUPPORT;WIN32;_WINDOWS;_USRDLL;UNICODE_INPUT_PLUGIN;DO_LAYER12;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>wsock32.lib;comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>comctl32.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + <SubSystem>Windows</SubSystem> + </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;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_DEBUG;AAC_SUPPORT;WIN64;_WINDOWS;_USRDLL;UNICODE_INPUT_PLUGIN;DO_LAYER12;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>wsock32.lib;comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>comctl32.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + <SubSystem>Windows</SubSystem> + </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;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>NDEBUG;AAC_SUPPORT;WIN32;_WINDOWS;_USRDLL;UNICODE_INPUT_PLUGIN;DO_LAYER12;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4996;4244;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>wsock32.lib;comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>comctl32.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <GenerateMapFile>false</GenerateMapFile> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>true</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <SubSystem>Windows</SubSystem> + </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;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>NDEBUG;AAC_SUPPORT;WIN64;_WINDOWS;_USRDLL;UNICODE_INPUT_PLUGIN;DO_LAYER12;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4996;4244;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>wsock32.lib;comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>comctl32.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <GenerateMapFile>false</GenerateMapFile> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>true</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <SubSystem>Windows</SubSystem> + </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> + <ProjectReference Include="..\..\..\apev2\apev2.vcxproj"> + <Project>{48387e27-2666-4acf-9c21-ae508c529580}</Project> + <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies> + <ReferenceOutputAssembly>true</ReferenceOutputAssembly> + </ProjectReference> + <ProjectReference Include="..\..\..\id3v2\id3v2.vcxproj"> + <Project>{0c7aafa2-2a78-4b91-99a3-3e866b484adb}</Project> + <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies> + <ReferenceOutputAssembly>true</ReferenceOutputAssembly> + </ProjectReference> + <ProjectReference Include="..\..\..\nsutil\nsutil.vcxproj"> + <Project>{dabe6307-f8dd-416d-9dac-673e2decb73f}</Project> + </ProjectReference> + <ProjectReference Include="..\..\..\replicant\jnetlib\jnetlib.vcxproj"> + <Project>{e105a0a2-7391-47c5-86ac-718003524c3d}</Project> + </ProjectReference> + <ProjectReference Include="..\..\..\replicant\nu\nu.vcxproj"> + <Project>{f1f5cd60-0d5b-4cea-9eeb-2f87ff9aa915}</Project> + </ProjectReference> + <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj"> + <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\nu\RingBuffer.h" /> + <ClInclude Include="..\..\..\Wasabi\api\service\svcs\svc_metatag.h" /> + <ClInclude Include="AACFrame.h" /> + <ClInclude Include="adts.h" /> + <ClInclude Include="adts_vlb.h" /> + <ClInclude Include="AlbumArt.h" /> + <ClInclude Include="apev2.h" /> + <ClInclude Include="api__in_mp3.h" /> + <ClInclude Include="config.h" /> + <ClInclude Include="CVbriHeader.h" /> + <ClInclude Include="DecodeThread.h" /> + <ClInclude Include="DXHEAD.H" /> + <ClInclude Include="FactoryHelper.h" /> + <ClInclude Include="giofile.h" /> + <ClInclude Include="id3.h" /> + <ClInclude Include="ID3v1.h" /> + <ClInclude Include="ID3v2.h" /> + <ClInclude Include="ifc_mpeg_stream_reader.h" /> + <ClInclude Include="LAMEinfo.h" /> + <ClInclude Include="Lyrics3.h" /> + <ClInclude Include="main.h" /> + <ClInclude Include="Metadata.h" /> + <ClInclude Include="MetadataFactory.h" /> + <ClInclude Include="MP3Info.h" /> + <ClInclude Include="mpegutil.h" /> + <ClInclude Include="OFL.h" /> + <ClInclude Include="pdtimer.h" /> + <ClInclude Include="RawMediaReader.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="Stopper.h" /> + <ClInclude Include="uvox_3901.h" /> + <ClInclude Include="uvox_3902.h" /> + <ClInclude Include="WasabiMetadata.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\..\..\nu\listview.cpp" /> + <ClCompile Include="..\..\..\nu\RingBuffer.cpp" /> + <ClCompile Include="AACFrame.cpp" /> + <ClCompile Include="adts_vlb.cpp" /> + <ClCompile Include="AlbumArt.cpp" /> + <ClCompile Include="apev2.cpp" /> + <ClCompile Include="config.cpp" /> + <ClCompile Include="CVbriHeader.cpp" /> + <ClCompile Include="DecodeThread.cpp" /> + <ClCompile Include="DXHEAD.C"> + <CompileAs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">CompileAsCpp</CompileAs> + <CompileAs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">CompileAsCpp</CompileAs> + <CompileAs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">CompileAsCpp</CompileAs> + <CompileAs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">CompileAsCpp</CompileAs> + </ClCompile> + <ClCompile Include="ExtendedInfo.cpp" /> + <ClCompile Include="ExtendedRead.cpp" /> + <ClCompile Include="giofile.cpp" /> + <ClCompile Include="id3.cpp" /> + <ClCompile Include="id3dlg.cpp" /> + <ClCompile Include="ID3v1.cpp" /> + <ClCompile Include="ID3v2.cpp" /> + <ClCompile Include="LAMEinfo.cpp" /> + <ClCompile Include="Lyrics3.cpp" /> + <ClCompile Include="main.cpp" /> + <ClCompile Include="Metadata.cpp" /> + <ClCompile Include="MetadataFactory.cpp" /> + <ClCompile Include="MP3Info.cpp" /> + <ClCompile Include="mpegutil.cpp" /> + <ClCompile Include="OFL.cpp" /> + <ClCompile Include="pdtimer.cpp" /> + <ClCompile Include="RawMediaReader.cpp" /> + <ClCompile Include="Stopper.cpp" /> + <ClCompile Include="titlelist.cpp" /> + <ClCompile Include="uvox_3901.cpp" /> + <ClCompile Include="uvox_3902.cpp" /> + <ClCompile Include="WasabiMetadata.cpp" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_mp3.rc" /> + </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_mp3/in_mp3.vcxproj.filters b/Src/Plugins/Input/in_mp3/in_mp3.vcxproj.filters new file mode 100644 index 00000000..91c28cd8 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/in_mp3.vcxproj.filters @@ -0,0 +1,218 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="AACFrame.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="adts_vlb.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="AlbumArt.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="apev2.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="config.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="CVbriHeader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="DecodeThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="DXHEAD.C"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedRead.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="giofile.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="id3.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="id3dlg.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ID3v1.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ID3v2.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="LAMEinfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Lyrics3.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Metadata.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="MetadataFactory.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="MP3Info.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="mpegutil.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="OFL.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="pdtimer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="RawMediaReader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Stopper.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="titlelist.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="uvox_3902.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="uvox_3901.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="WasabiMetadata.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\listview.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\RingBuffer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="AACFrame.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="adts.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="adts_vlb.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="apev2.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="AlbumArt.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="api__in_mp3.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="config.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="CVbriHeader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="DecodeThread.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="DXHEAD.H"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FactoryHelper.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="giofile.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="id3.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ID3v1.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ID3v2.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ifc_mpeg_stream_reader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="LAMEinfo.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Lyrics3.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="main.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Metadata.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="MetadataFactory.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="MP3Info.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="mpegutil.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="OFL.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="pdtimer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="RawMediaReader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Stopper.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="uvox_3901.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="uvox_3902.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="WasabiMetadata.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\RingBuffer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Wasabi\api\service\svcs\svc_metatag.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{21897fc1-e5e6-4baf-8768-1f94870c3daf}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{cf21f14f-48fa-49b3-bd4e-121aea3d1c61}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{d4950784-f6d6-4b2a-8b50-077f3edbee40}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_mp3.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/main.cpp b/Src/Plugins/Input/in_mp3/main.cpp new file mode 100644 index 00000000..e0fb5df2 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/main.cpp @@ -0,0 +1,342 @@ +//#define PLUGIN_NAME "Nullsoft MPEG Audio Decoder" +#include "main.h" +#include <time.h> +#include "DecodeThread.h" +#include "api__in_mp3.h" +#include "../Winamp/wa_ipc.h" +#include "../nu/ServiceBuilder.h" +#include "config.h" +#include "AlbumArt.h" +#include "MetadataFactory.h" +#include "../nu/Singleton.h" +#include "RawMediaReader.h" + +char lastfn_status[256] = {0}; +int lastfn_status_err = 0; +CRITICAL_SECTION g_lfnscs; +CRITICAL_SECTION streamInfoLock; + +int lastfn_data_ready; + +int config_fastvis=0; +unsigned char config_miscopts=0; +unsigned char allow_sctitles=1; +unsigned char sctitle_format=1; +unsigned char config_eqmode=4,config_http_proxynonport80=1; +unsigned int winampVersion=0x00005010; // default version # to use if winamp version is 5.1 or less (and therefore doesn't have a valid HWND during Init) +char config_http_save_dir[MAX_PATH] = "C:\\"; +int config_http_buffersize=64, config_http_prebuffer=40, config_http_prebuffer_underrun=10; +unsigned char config_downmix=0, config_downsample=0, allow_scartwork=1; + +int config_max_bufsize_k=128; +int config_gapless=1; +char INI_FILE[MAX_PATH] = {0}; + +wchar_t lastfn[8192] = {0}; // currently playing file (used for getting info on the current file) + +// Used for correcting DSP plug-in pitch changes +int paused = 0; // are we paused? + +int m_is_stream = 0; +bool m_is_stream_seekable = true; + +volatile int killDecodeThread=0; // the kill switch for the decode thread +HANDLE thread_handle=INVALID_HANDLE_VALUE; // the handle to the decode thread + +DWORD WINAPI DecodeThread(LPVOID b); // the decode thread procedure + +extern char *getfileextensions(); + + +#include "api/service/waservicefactory.h" + +#include "FactoryHelper.h" + +// wasabi based services for localisation support +HINSTANCE WASABI_API_LNG_HINST = 0; +HINSTANCE WASABI_API_ORIG_HINST = 0; + +api_language *WASABI_API_LNG = 0; +api_application *WASABI_API_APP = 0; +api_config *AGAVE_API_CONFIG = 0; +api_memmgr *WASABI_API_MEMMGR = 0; + +AlbumArtFactory albumArtFactory; +MetadataFactory metadataFactory; + +static RawMediaReaderService raw_media_reader_service; +static SingletonServiceFactory<svc_raw_media_reader, RawMediaReaderService> raw_factory; + +int init() +{ + if (!IsWindow(mod.hMainWindow)) + return IN_INIT_FAILURE; + + winampVersion = (unsigned int)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETVERSION); + mod.service->service_register(&metadataFactory); + mod.service->service_register(&albumArtFactory); + raw_factory.Register(mod.service, &raw_media_reader_service); + + ServiceBuild(AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceBuild(WASABI_API_LNG, languageApiGUID); + ServiceBuild(WASABI_API_APP, applicationApiServiceGuid); + ServiceBuild(WASABI_API_MEMMGR, memMgrApiServiceGuid); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(mod.hDllInstance,InMp3LangGUID); + + static wchar_t szDescription[256]; + swprintf(szDescription, 256, WASABI_API_LNGSTRINGW(IDS_NULLSOFT_MPEG_AUDIO_DECODER), PLUGIN_VERSION); + mod.description = (char*)szDescription; + + InitializeCriticalSection(&g_lfnscs); + InitializeCriticalSection(&streamInfoLock); + mod.UsesOutputPlug|=2; + config_read(); + mod.FileExtensions=getfileextensions(); + return IN_INIT_SUCCESS; +} + +void quit() +{ + DeleteCriticalSection(&g_lfnscs); + DeleteCriticalSection(&streamInfoLock); + ServiceRelease(mod.service, AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceRelease(mod.service, WASABI_API_APP, applicationApiServiceGuid); + ServiceRelease(mod.service, WASABI_API_MEMMGR, memMgrApiServiceGuid); + mod.service->service_deregister(&albumArtFactory); + raw_factory.Deregister(mod.service); +} + +int g_eq_ok; + +int isourfile(const wchar_t *fn) +{ + if (!_wcsnicmp(fn,L"uvox://",7)) return 1; + if (!_wcsnicmp(fn,L"icy://",6)) return 1; + if (!_wcsnicmp(fn,L"sc://",5)) return 1; + if (!_wcsnicmp(fn,L"shoutcast://",12)) return 1; + return 0; +} + + +int m_force_seek=-1; + +// called when winamp wants to play a file +int play(const in_char *fn) +{ + DWORD thread_id; + lastfn_status_err=0; + paused=0; + g_length=-1000; + decode_pos_ms=0; + seek_needed=m_force_seek; + m_force_seek=-1; + m_is_stream = 0; + m_is_stream_seekable = false; + killDecodeThread=0; + g_sndopened=0; + lastfn_data_ready=0; + lastfn_status[0]=0; + g_bufferstat=0; + g_closeaudio=0; + lstrcpynW(lastfn,fn, 8192); + mod.is_seekable = 0; + mod.SetInfo(0,0,0,0); + + g_ds=config_downsample; + + g_eq_ok=1; + // launch decode thread + thread_handle = (HANDLE)CreateThread(NULL,0,(LPTHREAD_START_ROUTINE) DecodeThread,NULL,0,&thread_id); + SetThreadPriority(thread_handle, (int)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + + return 0; +} + +// standard pause implementation +void pause() +{ + paused=1; + if (g_sndopened) + mod.outMod->Pause(1); +} + +void unpause() +{ + paused=0; + if (g_sndopened) + mod.outMod->Pause(0); +} + +int ispaused() +{ + return paused; +} + +// stop playing. +void stop() +{ + killDecodeThread=1; + WaitForSingleObject(thread_handle,INFINITE); + CloseHandle(thread_handle); + g_eq_ok=0; + thread_handle = INVALID_HANDLE_VALUE; + g_length=-1000; + lastfn[0]=0; + if (g_closeaudio) + { + g_closeaudio=0; + mod.outMod->Close(); + mod.SAVSADeInit(); + } + g_sndopened=0; + m_force_seek=-1; +} + + +// returns length of playing track +int getlength() +{ + return g_length; +} + + +// returns current output position, in ms. +// you could just use return mod.outMod->GetOutputTime(), +// but the dsp plug-ins that do tempo changing tend to make +// that wrong. +int getoutputtime() +{ + if (g_bufferstat) + return g_bufferstat; + + if (!lastfn_data_ready||!g_sndopened) + return 0; + + if (seek_needed!=-1) + return seek_needed; + + return decode_pos_ms + + (mod.outMod->GetOutputTime()-mod.outMod->GetWrittenTime()); +} + + +// called when the user releases the seek scroll bar. +// usually we use it to set seek_needed to the seek +// point (seek_needed is -1 when no seek is needed) +// and the decode thread checks seek_needed. +void setoutputtime(int time_in_ms) +{ + + + if (m_is_stream == 0 || (m_is_stream !=0 && m_is_stream_seekable)) + { + seek_needed=time_in_ms; + m_force_seek=-1; + } +} + + +// standard volume/pan functions +void setvolume(int volume) +{ + mod.outMod->SetVolume(volume); +} +void setpan(int pan) +{ + mod.outMod->SetPan(pan); +} + + +// this is an odd function. it is used to get the title and/or +// length of a track. +// if filename is either NULL or of length 0, it means you should +// return the info of lastfn. Otherwise, return the information +// for the file in filename. +// if title is NULL, no title is copied into it. +// if length_in_ms is NULL, no length is copied into it. + +static int memcmpv(char *d, char v, int l) +{ + while (l--) + if (*d++ != v) return 1; + return 0; +} + +void eq_set(int on, char data[10], int preamp) +{ + int x; + eq_preamp = preamp; + eq_enabled = on; + for (x = 0; x < 10; x ++) + eq_tab[x] = data[x]; + + // if eq zeroed out, dont use eq + if (eq_enabled && preamp==31 && !memcmpv(data,31,10)) + eq_enabled=0; +} + + + +// render 576 samples into buf. +// this function is only used by DecodeThread. + +// note that if you adjust the size of sample_buffer, for say, 1024 +// sample blocks, it will still work, but some of the visualization +// might not look as good as it could. Stick with 576 sample blocks +// if you can, and have an additional auxiliary (overflow) buffer if +// necessary.. + + +// module definition. + +extern In_Module mod = +{ + IN_VER_RET, // defined in IN2.H + "nullsoft(in_mp3.dll)", + 0, // hMainWindow (filled in by winamp) + 0, // hDllInstance (filled in by winamp) + 0, + // this is a double-null limited list. "EXT\0Description\0EXT\0Description\0" etc. + 0, // is_seekable + 1, // uses output plug-in system + config, + about, + init, + quit, + getfileinfo, + id3Dlg, + isourfile, + play, + pause, + unpause, + ispaused, + stop, + + getlength, + getoutputtime, + setoutputtime, + + setvolume, + setpan, + + 0,0,0,0,0,0,0,0,0, // visualization calls filled in by winamp + + 0,0, // dsp calls filled in by winamp + + eq_set, + + NULL, // setinfo call filled in by winamp + + 0, // out_mod filled in by winamp +}; + +// exported symbol. Returns output module. +extern "C" +{ + __declspec(dllexport) In_Module * winampGetInModule2() + { + return &mod; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/main.h b/Src/Plugins/Input/in_mp3/main.h new file mode 100644 index 00000000..01febf7d --- /dev/null +++ b/Src/Plugins/Input/in_mp3/main.h @@ -0,0 +1,123 @@ +#pragma once +#ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include "giofile.h" +#include "dxhead.h" +#include "CVbriHeader.h" +#include "resource.h" +#include "in2.h" + +#define PLUGIN_VERSION L"4.6" + +extern In_Module mod; + +extern char INI_FILE[MAX_PATH]; +extern int g_length; +extern int lastfn_data_ready; +extern int id3Dlg(const wchar_t *fn, HWND hwnd); +extern int getlength(); +extern void getfileinfo(const wchar_t *filename, wchar_t *title, int *length_in_ms); + +extern int config_max_bufsize_k; +extern int config_gapless; +extern int config_fastvis; +extern unsigned char config_miscopts; +extern unsigned char config_downmix, config_downsample; +extern int config_http_buffersize, config_http_prebuffer, config_http_prebuffer_underrun; +extern unsigned char allow_sctitles,sctitle_format, allow_scartwork; +extern char config_http_save_dir[MAX_PATH]; + +extern wchar_t lastfn[8192]; // currently playing file (used for getting info on the current file) +extern char g_stream_title[256]; +extern char lastfn_status[256]; +extern int lastfn_status_err; +extern int paused; // are we paused? +extern void config_read(); +extern void about(HWND hwndParent); + +extern void strmBuf_Quit(); +extern int strmBuf_Start(char *streamurl, int num_bytes, int pre_buffer_top, int pre_buffer_bottom); +extern int strmBuf_Read(void *data, int bytes_requested); + +extern void config(HWND hwndParent); +extern volatile int killDecodeThread; + +extern unsigned char eq_preamp; +extern unsigned char eq_enabled; +extern unsigned char eq_tab[10]; +extern unsigned char config_eqmode; +extern unsigned int winampVersion; +extern int g_eq_ok; + +extern CRITICAL_SECTION g_lfnscs; +extern CRITICAL_SECTION streamInfoLock; + +#if !defined(__alpha) && !defined(_WIN64) +static __inline long float_to_long(double t) +{ + long r; + __asm fld t + __asm fistp r + return r; +} +#else +#define float_to_long(x) ((long)( x )) +#endif + +extern void processMetaDataC(char *data, int len, int msgId ); + +enum +{ + UVOX_METADATA_STYLE_AOLRADIO = 0, + UVOX_METADATA_STYLE_SHOUTCAST = 1, + UVOX_METADATA_STYLE_SHOUTCAST2 = 2, + UVOX_METADATA_STYLE_SHOUTCAST2_ARTWORK = 3, + UVOX_METADATA_STYLE_SHOUTCAST2_ARTWORK_PLAYING = 4, +}; + +typedef struct { + void *Next; + int style; + long timer; + char title[16384]; + int part; + int total_parts; + int part_len; + int type; + } TitleType; +#define TITLELISTTYPE TitleType + +extern TITLELISTTYPE *TitleLinkedList; +extern TITLELISTTYPE TitleListTerminator; + +extern void initTitleList(void); +extern TITLELISTTYPE *newTitleListEntry(void); +extern void removeTitleListEntry(TITLELISTTYPE *Entry); +extern void clearTitleList(void); + +char *GetUltravoxUserAgent(); +char *GetUserAgent(); +void w9x_lstrcpynW(wchar_t *dest, const wchar_t *src, int maxLen); + +// maximum acceptable deviance between LAME header bytesize and real filesize (minus id3 tag) +// has to be large enough to accomodate unknown tags (APE, lyrics3) +const int MAX_ACCEPTABLE_DEVIANCE = 16384; +void get_extended_info(const wchar_t *fn, int *len); +#define UVOX_3901_LEN 32768 + +void ConvertTryUTF8(const char *in, wchar_t *out, size_t outlen); + +#ifdef AAC_SUPPORT +extern char config_extlist_aac[129]; +#define config_extlist config_extlist_aac +#else +extern char config_extlist[129]; +#endif + +extern int m_force_seek; +extern CGioFile *g_playing_file; + +int _r_i(char *name, int def); +void _w_i(char *name, int d);
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/mp4.cpp b/Src/Plugins/Input/in_mp3/mp4.cpp new file mode 100644 index 00000000..b1f1c63b --- /dev/null +++ b/Src/Plugins/Input/in_mp3/mp4.cpp @@ -0,0 +1,44 @@ +#include "vlb_sub/aacdecoder.h" +#include "vlbout.h" + +void CStreamInfo::setSampleRate() +{ + SetSamplingRate(CChannelInfo::SamplingRateFromIndex(GetSamplingRateIndex ())); +} + +#ifndef ACTIVEX_CONTROL +//methods used by in_mp4 +extern "C" +{ + __declspec( dllexport ) int aacGetBitBuffer() + { + return (int) new CBitBuffer; + } + + __declspec( dllexport ) int aacGetDecoderInterfaces(CAacDecoder **decoder, CBitBuffer *buf, CStreamInfo **info, VLBOut **dataout) + { + *decoder=new CAacDecoder(*buf); + *info=new CStreamInfo; + *dataout=new VLBOut(); + return 1; + } + + __declspec( dllexport ) void aacDeleteBitBuffer(CBitBuffer *bitBuffer) + { + if (bitBuffer) + delete bitBuffer; + } + + __declspec( dllexport ) void aacDeleteDecoderInterfaces(CAacDecoder *decoder, CStreamInfo *info, VLBOut *dataout) + { + if (decoder) + delete decoder; + if (info) + delete info; + if (dataout) + delete dataout; + } + + +} +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/mpegutil.cpp b/Src/Plugins/Input/in_mp3/mpegutil.cpp new file mode 100644 index 00000000..27d76488 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/mpegutil.cpp @@ -0,0 +1,372 @@ +#include "DecodeThread.h" +#include "main.h" +// benski> cut some old shit +// this code would take the filterbank coefficients to get an approximate spectrum +// and modify the coefficients to do a quick&easy EQ +// it's been off by default for quite a few versions (i think starting with 5.12) +// but not I just yanked it out. cause none of us have 486SX's any more + +static float eq_lookup1[64]={ + 4.000000f,3.610166f,3.320019f,3.088821f,2.896617f, + 2.732131f,2.588368f,2.460685f,2.345845f,2.241498f, + 2.145887f,2.057660f,1.975760f,1.899338f,1.827707f, + 1.760303f,1.696653f,1.636363f,1.579094f,1.524558f, + 1.472507f,1.422724f,1.375019f,1.329225f,1.285197f, + 1.242801f,1.201923f,1.162456f,1.124306f,1.087389f, + 1.051628f,1.016951f,0.983296f,0.950604f,0.918821f, + 0.887898f,0.857789f,0.828454f,0.799853f,0.771950f, + 0.744712f,0.718108f,0.692110f,0.666689f,0.641822f, + 0.617485f,0.593655f,0.570311f,0.547435f,0.525008f, + 0.503013f,0.481433f,0.460253f,0.439458f,0.419035f, + 0.398970f,0.379252f,0.359868f,0.340807f,0.322060f, + 0.303614f,0.285462f,0.267593f,0.250000 +}; + +static float eq_lookup2[64] = { +2.00000000f, 1.96825397f, 1.93650794f, 1.90476191f, 1.87301588f, +1.84126985f, 1.80952382f, 1.77777779f, 1.74603176f, 1.71428573f, +1.68253970f, 1.65079367f, 1.61904764f, 1.58730161f, 1.55555558f, +1.52380955f, 1.49206352f, 1.46031749f, 1.42857146f, 1.39682543f, +1.36507940f, 1.33333337f, 1.30158734f, 1.26984131f, 1.23809528f, +1.20634925f, 1.17460322f, 1.14285719f, 1.11111116f, 1.07936513f, +1.04761910f, 1.01587307f, 0.98412699f, 0.95238096f, 0.92063493f, +0.88888890f, 0.85714287f, 0.82539684f, 0.79365081f, 0.76190478f, +0.73015875f, 0.69841272f, 0.66666669f, 0.63492066f, 0.60317463f, +0.57142860f, 0.53968257f, 0.50793654f, 0.47619048f, 0.44444445f, +0.41269842f, 0.38095239f, 0.34920636f, 0.31746033f, 0.28571430f, +0.25396827f, 0.22222222f, 0.19047619f, 0.15873016f, 0.12698413f, +0.09523810f, 0.06349207f, 0.03174603f, 0.00000000 +}; + +unsigned char eq_preamp = 0; +unsigned char eq_enabled = 0; +unsigned char eq_tab[10] = {0}; + +float g_vis_table[2][2][32][18] = {0}; + +void mp3GiveVisData(float vistable[2][32][18],int gr, int nch) +{ + if (g_vis_enabled) + { + memcpy(&g_vis_table[gr][0][0][0],&vistable[0][0][0],sizeof(float)*32*18*nch); + } +} + +void mp2Equalize(float *xr, int nch, int srate, int nparts) +{ + if (!g_eq_ok || !(mod.UsesOutputPlug & 2)) return; + if (!eq_enabled) return; + float *eq_lookup = (config_eqmode&1)?eq_lookup2:eq_lookup1; + float preamp = eq_lookup[eq_preamp]; + int offs[11] = { 0,1,2,3,4,5,6,9,15,18,32}; + int scale_offs[11] = {0}; + int x; + unsigned char eqt[12] = {0}; + memcpy(eqt+1,eq_tab,10); + eqt[0]=eqt[1]; + eqt[11]=eqt[10]; + for (x = 0; x < 11; x ++) + { + scale_offs[x] = float_to_long( ((float) offs[x] / (float) srate * 44100.0)); + if (scale_offs[x] > 32) scale_offs[x] = 32; + } + { + if (nch == 1) + { + register int i; + for (i = 0; i < 10; i ++) + { + register int x=scale_offs[i]; + register int t=scale_offs[i+1]; + float d = eq_lookup[(int)eqt[i]]*preamp; + float dd = (eq_lookup[(int)eqt[i+1]]*preamp-d)/(float)(t-x); + if (dd < 0.000000001f && dd > -0.000000001f) dd = 0.000000001f; + while (x < t) + { + register float *p = xr+x; + int e=nparts; + while (e--) + { + *(p) *= d; + p+=32; + } + p += 32*(18-nparts); + d += dd; + x++; + } + } + } + else + { + register int i; + for (i = 0; i < 10; i ++) + { + register int x=scale_offs[i]; + register int t=scale_offs[i+1]; + float d = eq_lookup[(int)eqt[i]]*preamp; + float dd = (eq_lookup[(int)eqt[i+1]]*preamp-d)/(float)(t-x); + if (dd < 0.000000001f && dd > -0.000000001f) dd = 0.000000001f; + while (x < t) + { + register float *p = xr+x; + int e=nparts; + while (e--) + { + *(p+32*18) *= d; + *(p) *= d; + p+=32; + } + p += 32*(18-nparts); + d += dd; + x++; + } + } + } + } +} + + +void mp3Equalize(float *xr, int nch, int srate) +{ + if (!g_eq_ok || !(mod.UsesOutputPlug & 2)) return; + if (!eq_enabled) return; + float *eq_lookup = (config_eqmode&1)?eq_lookup2:eq_lookup1; + float preamp = eq_lookup[eq_preamp]; + static int scale_offs[11]; + static int lrate; + unsigned char eqt[12] = {0}; + memcpy(eqt+1,eq_tab,10); + eqt[0]=eqt[1]; + eqt[11]=eqt[10]; + if (lrate!=srate) + { + lrate=srate; + for (int x = 0; x < 11; x ++) + { + int offs[11] = { 0,2,4, 9,16, 30, 63, 95,153,308, 576}; + scale_offs[x] = float_to_long( ((float) offs[x] / (float) srate * 44100.0)); + if (scale_offs[x] > 576) scale_offs[x] = 576; + } + } + { + if (nch == 1) + { + register float *p = xr; + register float d = eq_lookup[eqt[0]]*preamp; + register int i = 0; + for (i = 0; i < 10; i ++) + { + register int x=scale_offs[i]; + register int t=scale_offs[i+1]; + register float dd = (eq_lookup[eqt[i+1]]*preamp-d)/(float)(t-x); + while (x++ < t) + { + *(p++) *= d; + d += dd; + } + } + } + else + { + register float *p = xr; + register int i; + for (i = 0; i < 10; i ++) + { + register int x=scale_offs[i]; + register int t=scale_offs[i+1]; + float d = eq_lookup[(int)eqt[i]]*preamp; + float dd = (eq_lookup[(int)eqt[i+1]]*preamp-d)/(float)(t-x); + if (dd < 0.000000001f && dd > -0.000000001f) dd = 0.000000001f; + while (x++ < t) + { + *(p+32*18) *= d; + *(p++) *= d; + d += dd; + } + } + } + } +} + + +void genOsc(char *tempdata, short *samples, int len) +{ + float d = 0.0f, dd = len/(75.0f*2.0f); + short int *sbuf = samples; + int x,y=0; + signed char *c = (signed char *) tempdata; + for (x = 0; x < 75; x ++) + { + float q=0; + int td = float_to_long((d+=dd)); + for (; y < td; y ++) + q += *sbuf++; + q *= (32.0f/(dd*65536.0f)); + *c++ = (signed char) float_to_long(q); + } +} +void genSpec(char *tempdata, float *xr, int nch) +{ + static int offsets[76] = + { + 0,1,2,3,4,5,7,8,9,10,12,13,15,16,18,20,21,23,25,27,29,31,33,36,38,41,43,46,48,51, + 54,57,60,64,67,71,74,78,82,86,91,95,100,105,109,115,120,126,131,137,144,150,157, + 164,171,178,186,194,203,211,220,230,239,250,260,271,282,294,306,319,332,345,360, + 374,389,576 + }; + { + for (int x = 0; x < 75; x++) + { + float sum = 0.0; + int z; + int sx = offsets[x]; + int ex = offsets[x+1]; + if (nch == 2) + { + float *p = &xr[0]; + int w=32*18; + for (z = sx; z < ex; z ++) + { + register float t1=p[z], t2=p[w+z]; + if (t1 <0.0) t1=-t1; + if (t2<0.0f) t2=-t2; + sum += (t1+t2) * 0.5f; + } + } + else + { + float *p = &xr[0]; + for (z = sx; z < ex; z ++) + { + register float t=p[z]; + if (t < 0.0) t=-t; + sum += t; + } + } + sum *= 1.0f + (x) * 12.0f / (ex-sx) ; + sum *= 1.8f/24000.0f; + if (sum < 0.0) sum = 0.0; + if (sum > 255.0) sum = 255.0; + tempdata[x] = (unsigned char) float_to_long(sum); + } + } +} + +void do_layer3_vis(short *samples, float *xr, int nch, int ts) +{ + int vis_waveNch; + int vis_specNch; + int csa = mod.SAGetMode(); + int is_vis_running = mod.VSAGetMode(&vis_specNch,&vis_waveNch); + static char tempdata[75*2]; + int len=32*18*nch; + + if (is_vis_running) + { + char data[576*4] = {0}; + int data_offs=0; + int x; + + if (nch == 1 && vis_specNch > 0) + { + float *specdata = xr; + int y; + for (y=0; y < 576; y++) + { + float p = *specdata++ / 24.0f; + if (p < 0.0) p = -p; + if (p > 255.0) p = 255.0; + data[data_offs++] = (unsigned char) float_to_long(p); + } + if (vis_specNch == 2) + { + memcpy(data+data_offs,data+data_offs-576,576); + data_offs += 576; + } + } + else if (vis_specNch == 2) + { + for (x = 0; x < 2; x ++) + { + float *specdata = &xr[x*32*18]; + for (int y=0; y < 576; y++) + { + float p = *specdata++ / 24.0f; + if (p < 0.0) p = -p; + if (p > 255.0) p = 255.0; + data[data_offs++] = (unsigned char) float_to_long(p); + } + } + } + else if (vis_specNch == 1) + { + float *specdata = &xr[0]; + int y; + for (y = 0; y < 576; y++) + { + register float p1=specdata[0],p2=specdata[32*18],p; + if (p1 < 0.0) p1=-p1; + if (p2 < 0.0) p2=-p2; + p = (p1+p2)/ 48.0f; + specdata++; + if (p > 255.0) p = 255.0; + data[data_offs++] = (unsigned char) float_to_long(p); + } + } // end of spectrum code + + if (nch == 1 && vis_waveNch > 0) + { + for (x = 0; x < 576; x++) + { + data[data_offs++] = ((samples[x])>>8); + } + if (vis_waveNch == 2) + { + memcpy(data+data_offs,data+data_offs-576,576); + data_offs += 576; + } + } + else if (vis_waveNch == 2) + { + for (x = 0; x < 2; x ++) + { + int y; + for (y = 0; y < 576; y ++ ) + { + data[data_offs++] = ((samples[y*2+x])>>8); + } + } + } + else if (vis_waveNch == 1) + { + int x; + for (x = 0; x < 576; x ++) + { + data[data_offs++] = ((int)samples[x*2]+(int)samples[x*2+1])>>9; + } + } + mod.VSAAdd(data,ts); + } + if (csa==4) + { + tempdata[0] = 0; + tempdata[1] = 0; + mod.SAAdd(tempdata,ts,4); + } + else if (csa == 3) + { + genSpec(tempdata,xr,nch); + genOsc(tempdata+75,samples,len); + mod.SAAdd(tempdata,ts,0x80000003); + } + else if (csa == 2) + { + genOsc(tempdata,samples,len); + mod.SAAdd(tempdata,ts,2); + } + else if (csa == 1) { + genSpec(tempdata,xr,nch); + mod.SAAdd(tempdata,ts,1); + } +} + diff --git a/Src/Plugins/Input/in_mp3/mpegutil.h b/Src/Plugins/Input/in_mp3/mpegutil.h new file mode 100644 index 00000000..578f37b6 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/mpegutil.h @@ -0,0 +1,9 @@ +#ifndef NULLSOFT_MPEGUTILH +#define NULLSOFT_MPEGUTILH + +extern float g_vis_table[2][2][32][18]; +void do_layer3_vis(short *samples, float *xr, int nch, int ts); +void mp3Equalize(float *xr, int nch, int srate); +void mp2Equalize(float *xr, int nch, int srate, int nparts); +void mp3GiveVisData(float vistable[2][32][18],int gr, int nch); +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/pdtimer.cpp b/Src/Plugins/Input/in_mp3/pdtimer.cpp new file mode 100644 index 00000000..4b6b5b95 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/pdtimer.cpp @@ -0,0 +1,24 @@ +#include "pdtimer.h" + +__int64 pdReadResolution(void) +{ + __int64 myfeq; + LARGE_INTEGER feq; + + QueryPerformanceFrequency( &feq); + myfeq = feq.QuadPart; + + return myfeq; +} + +__int64 pdReadTimer(void) +{ + __int64 mynow; + + LARGE_INTEGER now; + + QueryPerformanceCounter( &now ); + mynow = now.QuadPart; + + return mynow; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/pdtimer.h b/Src/Plugins/Input/in_mp3/pdtimer.h new file mode 100644 index 00000000..1c0da887 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/pdtimer.h @@ -0,0 +1,9 @@ +#ifndef NULLSOFT_PDTIMERH +#define NULLSOFT_PDTIMERH + +#include <windows.h> + +__int64 pdReadResolution(void); +__int64 pdReadTimer(void); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/proxydt.h b/Src/Plugins/Input/in_mp3/proxydt.h new file mode 100644 index 00000000..8802075d --- /dev/null +++ b/Src/Plugins/Input/in_mp3/proxydt.h @@ -0,0 +1,16 @@ +// proxydt.h +#ifndef PROXYDT_H +#define PROXYDT_H + +#include <string> +#include <stdio.h> +#include <atlbase.h> + +char* detectBrowserProxy(); + +char* DetectIEProxy(); + +char* DetectNS4Proxy(); +char* DetectNS6Proxy(); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/resource.h b/Src/Plugins/Input/in_mp3/resource.h new file mode 100644 index 00000000..14776a1b --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resource.h @@ -0,0 +1,253 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by script1.rc +// +#define IDS_NULLSOFT_MPEG_AUDIO_DECODER_OLD 0 +#define IDS_CANNOT_WRITE_STREAMS_TO_DISK 1 +#define IDS_BUFFER_X 2 +#define IDC_ID3_REMOVE 3 +#define IDC_ID3V1_REMOVE 3 +#define IDC_REVERTTAG 3 +#define IDS_ERROR_SYNCING_TO_STREAM 3 +#define IDC_ID3V2_REMOVE 4 +#define IDS_CONNECTING 4 +#define IDC_ID3V1_SAVE 5 +#define IDS_NO_VALID_MULTICONNECT_URL 5 +#define IDC_ID3_REMOVE_ALL 6 +#define IDS_REDIRECT_LIMIT_EXCEEDED 6 +#define IDC_ID3V1_TO_V2 7 +#define IDS_READING_ID3 7 +#define IDC_ID3V2_SAVE 8 +#define IDS_STREAM_TERMINATED 8 +#define IDC_ID3V2_TO_V1 9 +#define IDS_STREAM_TEMPORARILY_INTERRUPTED 9 +#define IDC_ID3V2_STOP 10 +#define IDS_NETWORK_RECEIVED_X_BYTES 16 +#define IDS_SERVER 17 +#define IDS_CONTENT_TYPE 18 +#define IDS_ULTRAVOX_SYNC 19 +#define IDS_ULTRAVOX_DATA_MESSAGE 20 +#define IDS_ULTRAVOX_SID_AVGBR_MAXBR 21 +#define IDS_METADATA_RECEIVED 22 +#define IDS_METADATA_INTERVAL 23 +#define IDS_ID3v2_TAG 24 +#define IDS_VBR_LEADING_FRAME 25 +#define IDS_STREAM_NAME 26 +#define IDS_CURRENT_TITLE 27 +#define IDS_CONTENT_LENGTH 28 +#define IDS_SAVING_TO 29 +#define IDS_CUSTOM 32 +#define IDS_MONO 33 +#define IDS_STEREO 34 +#define IDS_3_CHANNEL 35 +#define IDS_4_CHANNEL 36 +#define IDS_SURROUND 37 +#define IDS_5_1 38 +#define IDS_7_1 39 +#define IDS_ERROR 40 +#define IDS_NONE 41 +#define IDS_50_15_MICROSEC 42 +#define IDS_INVALID 43 +#define IDS_JOINT_STEREO 44 +#define IDS_2_CHANNEL 45 +#define IDS_PAYLOAD_SIZE 46 +#define IDS_FORMAT_AAC 47 +#define IDS_MPEG2_HE_AAC_IS 48 +#define IDS_MPEG4_HE_AAC_IS 49 +#define IDS_SAMPLE_RATE_OUTPUT 50 +#define IDS_SAMPLE_RATE 51 +#define IDS_SBR_PRESENT 52 +#define IDS_SBR_NOT_PRESENT 53 +#define IDS_CHANNELS_OUTPUT 54 +#define IDS_CHANNELS 55 +#define IDS_MODE_MONO 56 +#define IDS_MODE_STEREO 57 +#define IDS_MODE_PARAMETRIC_STEREO 58 +#define IDS_MODE_DUAL_CHANNEL 59 +#define IDS_MODE_4_CHANNEL_2_CPE 60 +#define IDS_MODE_4_CHANNEL_MPEG 61 +#define IDS_MODE_5_CHANNEL 62 +#define IDS_MODE_5_1 63 +#define IDS_MODE_6_1 64 +#define IDS_MODE_7_1 65 +#define IDS_BITRATE 66 +#define IDS_AVERAGE_BITRATE 67 +#define IDS_HEADER_FOUND_AT_X_BYTES 68 +#define IDS_LENGTH_X_SECONDS 69 +#define IDS_PROFILE 70 +#define IDS_YES 71 +#define IDS_NO 72 +#define IDS_ENC_DELAY_ZERO_PADDING 73 +#define IDS_S_LAYER_X 74 +#define IDS_X_KBIT 75 +#define IDS_X_KBIT_APPROX 76 +#define IDS_X_KBIT_VBR 77 +#define IDS_X_HZ_S 78 +#define IDS_COPYRIGHTED 79 +#define IDS_ORIGINAL 80 +#define IDS_EMPHASIS 81 +#define IDS_EXT_MP3_SURROUND 82 +#define IDS_MP3_HAS_BEEN_MODIFIED_NOT_ALL_MAY_BE_CORRECT 83 +#define IDS_UPDATING_FILE 84 +#define IDS_CANNOT_MODIFY_TAG_READ_ONLY 85 +#define IDS_CANNOT_INSERT_ID3v2_TAG_DRIVE_FULL 86 +#define IDS_CANNOT_INSERT_ID3v2_TAG_STOP_PLAYBACK 87 +#define IDS_CANNOT_INSERT_ID3v2_TAG_READ_ONLY 88 +#define IDS_UPDATING_FILE_REMOVING_TAG 89 +#define IDS_CANNOT_REMOVE_ID3v2_TAG_DRIVE_FULL 90 +#define IDS_CANNOT_REMOVE_ID3v2_TAG_STOP_TRY_AGAIN 91 +#define IDS_CANNOT_REMOVE_ID3v2_TAG_READ_ONLY 92 +#define IDS_CANNOT_WRITE_ID3v1_TAG 93 +#define IDS_CANNOT_REMOVE_ID3v1_TAG 94 +#define IDS_MPEG_AUDIO_FILES 95 +#define IDS_MPEG_AUDIO_DECODER_SETTINGS 96 +#define IDS_LATIN_1 97 +#define IDS_SYSTEM_LANGUAGE 98 +#define IDS_UNICODE_UTF_16 99 +#define IDS_SELECT_DIRECTORY_TO_SAVE_TO 100 +#define ID_YES 101 +#define IDD_PREFS 103 +#define IDD_OUTPUT 104 +#define IDD_STREAM 115 +#define IDD_HTTP 177 +#define IDD_TAGOPTS 179 +#define IDD_HTTPAUTH 182 +#define IDD_INFO_ID3V1 186 +#define IDD_INFO_ID3V2 187 +#define IDD_INFO_LYRICS3 188 +#define IDS_X_KBIT_ABR 189 +#define IDD_INFO_APEV2 189 +#define IDD_ADVANCED_TAGGING 190 +#define IDS_NAME 191 +#define IDS_VALUE 192 +#define IDS_APEV2_RETAIN_HEADER 193 +#define IDS_APEV2_ADD_HEADER 194 +#define IDS_APEV2_REMOVE_HEADER 195 +#define IDS_ERROR_SAVING_METADATA 196 +#define IDS_METADATA_ERROR_READONLY 197 +#define IDS_METADATA_ERROR_OPENING_FILE 198 +#define IDS_METADATA_ERROR_APEV2 199 +#define IDS_METADATA_ERROR_LYRICS3 200 +#define IDS_METADATA_ERROR_ID3V1 201 +#define IDS_METADATA_ERROR_ID3V2 202 +#define IDS_METADATA_ERROR_UNSPECIFIED 203 +#define IDS_TIMED_OUT 204 +#define IDS_FAMILY_STRING_MP3 205 +#define IDS_FAMILY_STRING_MP2 206 +#define IDS_FAMILY_STRING_MP1 207 +#define IDS_FAMILY_STRING_MPEG2_AAC 208 +#define IDS_FAMILY_STRING_DOLBY 209 +#define IDS_ABOUT_STRING 210 +#define IDS_ABOUT_TEXT 210 +#define IDS_LENGTH_X_PART_SECONDS 211 +#define IDC_FASTL3EQ 1000 +#define IDC_BUFFERS_NUMBUFS 1001 +#define IDC_FASTL12EQ 1002 +#define IDC_ID3V1 1003 +#define IDC_ID3V2 1004 +#define IDC_16BIT 1005 +#define IDC_PREBUFSLIDER 1011 +#define IDC_PREBUFSLIDER2 1012 +#define IDC_STEREO 1046 +#define IDC_REVSTEREO 1048 +#define IDC_FULLRATE 1049 +#define IDC_HALFRATE 1050 +#define IDC_QRATE 1051 +#define IDC_PREFS_PRIORITY_DECODE 1076 +#define IDC_ID3_TITLE 1078 +#define IDC_ID3_ARTIST 1079 +#define IDC_ID3_ALBUM 1080 +#define IDC_ID3_COMMENT 1081 +#define IDC_ID3_YEAR 1082 +#define IDC_ID3_GENRE 1083 +#define IDC_ID3V2_TITLE 1086 +#define IDC_ID3V2_ARTIST 1087 +#define IDC_ID3V2_ALBUM 1088 +#define IDC_ID3V2_YEAR 1089 +#define IDC_ID3V2_GENRE 1090 +#define IDC_ID3V2_COMMENT 1091 +#define IDC_ID3V2_COMPOSER 1092 +#define IDC_ID3V2_MARTIST 1093 +#define IDC_ID3V2_RECORD 1094 +#define IDC_ID3V2_URL 1095 +#define IDC_ID3V2_ENCODER 1096 +#define IDC_ID3V2_TRACK 1097 +#define IDC_ID3V11_TRACK 1098 +#define IDC_BPM 1098 +#define IDC_ID3V2_BPM 1098 +#define IDC_ID3V2_ALBUM_ARTIST 1099 +#define IDC_ID3V2_ENCODER2 1100 +#define IDC_ID3V2_DISC 1100 +#define IDC_ID3V2_ENCODER3 1101 +#define IDC_ID3V2_PUBLISHER 1101 +#define IDC_RADIO1 1106 +#define IDC_RADIO2 1107 +#define IDC_CHECK1 1108 +#define IDC_STATUS 1109 +#define IDC_SC_ARTWORK 1109 +#define IDC_CHECK2 1110 +#define IDC_URL 1111 +#define IDC_BUTTON2 1112 +#define IDC_ID3FORMAT 1127 +#define IDC_NORMFORMAT 1128 +#define IDC_BUFMAX 1136 +#define IDC_CHECK3 1138 +#define IDC_ID3V2_STATUS_TEXT 1139 +#define IDC_EASTER 1143 +#define IDC_USEID3 1145 +#define IDC_EDIT1 1146 +#define IDC_BUTTON1 1147 +#define IDC_TRACK_GAIN 1147 +#define IDC_REALM 1148 +#define IDC_CHECK5 1150 +#define IDC_READ_LYRICS3 1150 +#define IDC_WRITE_LOCAL 1151 +#define IDC_READ_APEV2 1151 +#define IDC_WRITE_UTF16 1152 +#define IDC_WRITE_LATIN 1153 +#define IDC_READ_LATIN 1154 +#define IDC_READ_LOCAL 1155 +#define IDC_WRITE_ID3V1 1156 +#define IDC_CHECK7 1157 +#define IDC_WRITE_ID3V2 1157 +#define IDC_READ_ID3V1 1158 +#define IDC_READ_ID3V2 1159 +#define IDC_24BIT 1160 +#define IDC_WRITE_APEV2 1160 +#define IDC_CREATE_ID3V1 1160 +#define IDC_SURROUND 1161 +#define IDC_EDIT_REPLAYGAIN 1162 +#define IDC_CREATE_ID3V2 1162 +#define IDC_ALBUM_GAIN 1165 +#define IDC_COMBO1 1168 +#define IDC_COMBO2 1169 +#define IDC_LYRICS3_TITLE 1169 +#define IDC_LYRICS3_ARTIST 1170 +#define IDC_LYRICS3_ALBUM 1171 +#define IDC_LYRICS3V2 1172 +#define IDC_LYRICS3 1172 +#define IDC_APEV2 1173 +#define IDC_APELIST 1177 +#define IDC_APE_LIST 1177 +#define IDC_APE_KEY 1178 +#define IDC_DELETE_ALL 1179 +#define IDC_APE_VALUE 1180 +#define IDC_APE_ADD 1181 +#define IDC_APE_DELETE 1182 +#define IDC_CREATE_APEV2 1183 +#define IDC_STATIC_APEV2_HEADER 1184 +#define IDC_APEV2_HEADER_OPTIONS 1185 +#define IDC_RATING_EMAIL 1186 +#define IDC_RATING_EMAIL_RESET 1187 +#define IDS_NULLSOFT_MPEG_AUDIO_DECODER 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 212 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1188 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_mp3/resources/aboutMP3_256.bmp b/Src/Plugins/Input/in_mp3/resources/aboutMP3_256.bmp Binary files differnew file mode 100644 index 00000000..bc3629f9 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resources/aboutMP3_256.bmp diff --git a/Src/Plugins/Input/in_mp3/resources/hand.cur b/Src/Plugins/Input/in_mp3/resources/hand.cur Binary files differnew file mode 100644 index 00000000..614403a0 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resources/hand.cur diff --git a/Src/Plugins/Input/in_mp3/resources/llama.bmp b/Src/Plugins/Input/in_mp3/resources/llama.bmp Binary files differnew file mode 100644 index 00000000..5f2a6ec1 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resources/llama.bmp diff --git a/Src/Plugins/Input/in_mp3/resources/logoCodTech.90x42.bmp b/Src/Plugins/Input/in_mp3/resources/logoCodTech.90x42.bmp Binary files differnew file mode 100644 index 00000000..de49ffd5 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resources/logoCodTech.90x42.bmp diff --git a/Src/Plugins/Input/in_mp3/resources/logoIIS_127x42.bmp b/Src/Plugins/Input/in_mp3/resources/logoIIS_127x42.bmp Binary files differnew file mode 100644 index 00000000..3dd674ca --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resources/logoIIS_127x42.bmp diff --git a/Src/Plugins/Input/in_mp3/resources/logoId3v2_44x42.bmp b/Src/Plugins/Input/in_mp3/resources/logoId3v2_44x42.bmp Binary files differnew file mode 100644 index 00000000..851493ae --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resources/logoId3v2_44x42.bmp diff --git a/Src/Plugins/Input/in_mp3/resources/logoMp3surround_101x42.bmp b/Src/Plugins/Input/in_mp3/resources/logoMp3surround_101x42.bmp Binary files differnew file mode 100644 index 00000000..aba97616 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resources/logoMp3surround_101x42.bmp diff --git a/Src/Plugins/Input/in_mp3/resources/logoWA.bmp b/Src/Plugins/Input/in_mp3/resources/logoWA.bmp Binary files differnew file mode 100644 index 00000000..41266529 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/resources/logoWA.bmp diff --git a/Src/Plugins/Input/in_mp3/tagz.cpp b/Src/Plugins/Input/in_mp3/tagz.cpp new file mode 100644 index 00000000..0e110b23 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/tagz.cpp @@ -0,0 +1,953 @@ +#include <windows.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <stdio.h> + +#include "tagz.h" + +#ifdef TAGZ_UNICODE +#define _TX(X) L##X +#define t_strdup _wcsdup +#define t_strlen wcslen +#define t_strnicmp _wcsnicmp +#define t_strtoul wcstoul +#define t_stricmp _wcsicmp +#define t_strstr wcsstr + +#define sprintf swprintf +#else +#define _TX(X) X +#define t_strdup _strdup +#define t_strlen strlen +#define t_strnicmp _strnicmp +#define t_strtoul strtoul +#define t_stricmp _stricmp +#define t_strstr strstr + +#endif + +int t_atoi(T_CHAR * c) +{ + if (c[0]=='-') return -(int)t_strtoul(c+1,0,10); + else return (int)t_strtoul(c,0,10); +} + +#define TABSIZE(x) (sizeof(x)/sizeof(x[0])) +/* +char * strndup(char * p1,UINT n) +{ + char * r=(char*)malloc(n+1); + if (r) + { + memcpy(r,p1,n); + r[n]=0; + } + return n; +} +*/ + +class String +{ +private: + T_CHAR * data; + UINT size, used; +public: + String() {data=0;size=0;used=0;} + void AddChar(T_CHAR c) + { + if (!data) + { + data=(T_CHAR*)malloc((size=512)*sizeof(T_CHAR)); + used=0; + } + else if (size==used) + { + UINT old_size = size; + T_CHAR * old_data = data; + size<<=1; + data=(T_CHAR*)realloc((char*)data,size*sizeof(T_CHAR)); + if (!data) + { + size = old_size; + data = old_data; + return; + } + } + if (data) data[used++]=c; + } + void AddInt(int i) + { + T_CHAR foo[16]; + sprintf(foo,_TX("%i"),i); + AddString(foo); + } + void AddString(const T_CHAR * z) + { + while(*z) {AddChar(*z);z++;} + } + void AddString(String & s) + { + AddString(s.Peek()); + } + ~String() + { + if (data) free(data); + } + T_CHAR * GetBuf() + { + if (!data) return t_strdup(_TX("")); + T_CHAR * r=(T_CHAR*)realloc(data,(used+1)*sizeof(T_CHAR)); + r[used]=0; + data=0; + return r; + } + T_CHAR operator[](UINT i) + { + if (!data || i>=used) return 0; + else return data[i]; + } + UINT Len() {return data ? used : 0;} + void Reset() + { + if (data) {free(data);data=0;} + } + const T_CHAR * Peek() + { + AddChar(0); + used--; + return data; + } + T_CHAR * _strdup() + { + return ::t_strdup(Peek()); + } +}; + + + + +static int separator(T_CHAR x) +{ + if (!x || x==' ') return 1; + if (x=='\'' || x=='_') return 0; +#ifdef TAGZ_UNICODE + return !iswalnum(x); +#else + return !isalnum(x); +#endif +} + +static int sepcmp(T_CHAR* src,T_CHAR* val) +{ + UINT l=t_strlen(val); + return !t_strnicmp(src,val,l) && separator(src[l]); +} + +static char roman_num[]= +{ + 'I','V','X','L','C','D','M' +}; + + +static int is_roman(T_CHAR * ptr)//could be more smart i think +{ + if (ptr[0]==']' && ptr[1]=='[' && separator(ptr[2])) return 1; + while(!separator(*ptr)) + { + UINT n; + bool found=0; + for(n=0;n<TABSIZE(roman_num);n++) + { + if (*ptr==roman_num[n]) {found=1;break;} + } + if (!found) return 0; + ptr++; + } + return 1; +} + +static int need_full(T_CHAR* ptr) +{ + if (is_roman(ptr)) return 1; + if (sepcmp(ptr,_TX("RPG"))) return 1; + while(!separator(*ptr)) + { + if (*ptr<='0' || *ptr>='9') return 0; + ptr++; + } + return 1; +} + +class VarList +{ +private: + typedef struct tagVAR + { + tagVAR * next; + T_CHAR * name; + T_CHAR * value; + } VAR; + VAR * vars; +public: + VarList() {vars=0;} + ~VarList() + { + VAR * p=vars; + while(p) + { + VAR *n=p->next; + free(p->name); + free(p->value); + delete p; + p=n; + } + } + T_CHAR * Get(T_CHAR * name) + { + VAR * p=vars; + while(p) + { + if (!t_stricmp(name,p->name)) return p->value; + p=p->next; + } + return 0; + } + void Put(T_CHAR * name,T_CHAR* value) + { + VAR * p=vars; + while(p) + { + if (!t_stricmp(name,p->name)) + { + free(p->value); + p->value=t_strdup(value); + return; + } + p=p->next; + } + p=new VAR; + p->next=vars; + vars=p; + p->value=t_strdup(value); + p->name=t_strdup(name); + } +}; + +typedef void (*TEXTFUNC)(UINT n_src,T_CHAR **src,UINT*,String &out,VarList & vars); + + +#define MAKEFUNC(X) static void X(UINT n_src,T_CHAR ** src,UINT *found_src,String &out,VarList &vars) + +/* +void Blah(UINT n_src,T_CHAR ** src,UINT*,String &out) +{ + out.AddString("blah"); +} + +void Nop(UINT n_src,T_CHAR ** src,UINT *,String &out) +{ + UINT n; + for(n=0;n<n_src;n++) out.AddString(src[n]); +} +*/ + +MAKEFUNC(Get) +{ + if (n_src>=1) + { + T_CHAR * p=vars.Get(src[0]); + if (p) out.AddString(p); + } +} + +MAKEFUNC(Put) +{ + if (n_src>=2) + { + vars.Put(src[0],src[1]); + out.AddString(src[1]); + } +} + +MAKEFUNC(PutQ) +{ + if (n_src>=2) + { + vars.Put(src[0],src[1]); + } +} + +MAKEFUNC(If) +{ + if (n_src!=3) out.AddString(_TX("[INVALID $IF SYNTAX]")); + else + { + out.AddString(src[found_src[0] ? 1 : 2]); + } +} + +MAKEFUNC(Iflonger) +{ + if (n_src!=4) out.AddString(_TX("[INVALID $IFLONGER SYNTAX]")); + else + { + out.AddString(src[(int)t_strlen(src[0])>t_atoi(src[1]) ? 2 : 3]); + } +} + +MAKEFUNC(Ifgreater) +{ + if (n_src!=4) out.AddString(_TX("[INVALID $IFGREATER SYNTAX]")); + else + { + out.AddString(src[t_atoi(src[0])>t_atoi(src[1]) ? 2 : 3]); + } +} + +MAKEFUNC(Upper) +{ + if (n_src>=1) + { + T_CHAR * s=src[0]; + while(*s) + { + out.AddChar(toupper(*(s++))); + } + } +} + +MAKEFUNC(Lower) +{ + if (n_src>=1) + { + T_CHAR * s=src[0]; + while(*s) + { + out.AddChar(tolower(*(s++))); + } + } +} + +MAKEFUNC(Pad) +{ + if (n_src>=2) + { + T_CHAR *fill=_TX(" "); + if (n_src>=3 && src[2][0]) fill=src[2]; + int num=t_atoi(src[1]); + T_CHAR *p=src[0]; + while(*p) {out.AddChar(*(p++));num--;} + UINT fl=t_strlen(fill); + while(num>0) {out.AddChar(fill[(--num)%fl]);} + } +} + +MAKEFUNC(Cut) +{ + if (n_src>=2) + { + UINT num=t_atoi(src[1]); + T_CHAR *p=src[0]; + while(*p && num) {out.AddChar(*(p++));num--;} + } +} + +MAKEFUNC(PadCut) +{ + if (n_src>=2) + { + T_CHAR *fill=_TX(" "); + if (n_src>=3 && src[2][0]) fill=src[3]; + int num=t_atoi(src[1]); + T_CHAR *p=src[0]; + while(*p && num>0) {out.AddChar(*(p++));num--;} + UINT fl=t_strlen(fill); + while(num>0) {out.AddChar(fill[(--num)%fl]);} + } +} + +MAKEFUNC(Abbr) +{//abbr(string,len) + if (n_src==0 || (n_src>=2 && (int)t_strlen(src[0])<t_atoi(src[1]))) return; + T_CHAR * meta=src[0]; + bool w=0,r=0; + while(*meta) + { + bool an=!separator(*meta) || *meta==']' || *meta=='['; + if (w && !an) + { + w=0; + } + else if (!w && an) + { + w=1; + if (!sepcmp(meta,_TX("a")) && !sepcmp(meta,_TX("the"))) + { + r=need_full(meta)?1:0; + out.AddChar(*meta); + } + else r=0; + } + else if (w && r) + { + out.AddChar(*meta); + } + meta++; + } +} + + + +MAKEFUNC(Caps) +{ + if (n_src<1) return; + T_CHAR* sp=src[0]; + int sep=1; + while(*sp) + { + T_CHAR c=*(sp++); + int s = separator(c); + if (!s && sep) + c=toupper(c); + else if (!sep) c=tolower(c); + sep=s; + out.AddChar(c); + } +} + +MAKEFUNC(Caps2) +{ + if (n_src<1) return; + T_CHAR* sp=src[0]; + int sep=1; + while(*sp) + { + T_CHAR c=*(sp++); + int s = separator(c); + if (!s && sep) + c=toupper(c); + sep=s; + out.AddChar(c); + } +} + +MAKEFUNC(Longest) +{ + T_CHAR * ptr=0; + UINT n,m=0; + for(n=0;n<n_src;n++) + { + UINT l=t_strlen(src[n]); + if (l>m) {m=l;ptr=src[n];} + } + if (ptr) out.AddString(ptr); +} + +MAKEFUNC(Shortest) +{ + T_CHAR * ptr=0; + UINT n,m=(UINT)(-1); + for(n=0;n<n_src;n++) + { + UINT l=t_strlen(src[n]); + if (l<m) {m=l;ptr=src[n];} + } + if (ptr) out.AddString(ptr); +} + +MAKEFUNC(Num) +{ + if (n_src==2) + { + T_CHAR tmp[16]; + T_CHAR tmp1[16]; + sprintf(tmp1,_TX("%%0%uu"),t_atoi(src[1])); + sprintf(tmp,tmp1,t_atoi(src[0])); + out.AddString(tmp); + } +} + +MAKEFUNC(Hex) +{ + if (n_src==2) + { + T_CHAR tmp[16]; + T_CHAR tmp1[16]; + sprintf(tmp1,_TX("%%0%ux"),t_atoi(src[1])); + sprintf(tmp,tmp1,t_atoi(src[0])); + out.AddString(tmp); + } +} + +MAKEFUNC(StrChr) +{ + if (n_src==2) + { + T_CHAR * p=src[0]; + T_CHAR s=src[1][0]; + while(*p && *p!=s) p++; + if (*p==s) out.AddInt(1+p-src[0]); + else out.AddChar('0'); + } +} + +MAKEFUNC(StrRChr) +{ + if (n_src==2) + { + T_CHAR * p=src[0],*p1=0; + T_CHAR s=src[1][0]; + while(*p) + { + if (*p==s) p1=p; + p++; + } + if (p1) out.AddInt(1+p1-src[0]); + else out.AddChar('0'); + + } +} + +MAKEFUNC(StrStr) +{ + if (n_src==2) + { + T_CHAR * p=t_strstr(src[0],src[1]); + if (p) out.AddInt(1+p-src[0]); + else out.AddChar('0'); + } +} + +MAKEFUNC(SubStr) +{ + int n1,n2; + if (n_src<2) return; + n1=t_atoi(src[1]); + if (n_src>=3) + { + n2=t_atoi(src[2]); + } + else n2=n1; + if (n1<1) n1=1; + if (n2>=n1) + { + n1--; + n2--; + while(n1<=n2 && src[0][n1]) + { + out.AddChar(src[0][n1++]); + } + } +} + +MAKEFUNC(Len) +{ + if (n_src>=1) out.AddInt(t_strlen(src[0])); +} + +MAKEFUNC(FileName) +{ + if (n_src<1) return; + T_CHAR * p=src[0]; + T_CHAR * p1=0; + while(*p) + { + if (*p=='\\' || *p=='/') p1=p; + p++; + } + if (p1) p1++; + else p1=p; + while(*p1 && *p1!='.') out.AddChar(*(p1++)); +} + +MAKEFUNC(Add) +{ + UINT n; + int s=0; + for(n=0;n<n_src;n++) + { + s+=t_atoi(src[n]); + } + out.AddInt(s); +} + +MAKEFUNC(Sub) +{ + if (n_src>=1) + { + UINT n; + int s=t_atoi(src[0]); + for(n=1;n<n_src;n++) + { + s-=t_atoi(src[n]); + } + out.AddInt(s); + } +} + +MAKEFUNC(Mul) +{ + UINT n; + int s=1; + for(n=0;n<n_src;n++) + { + s*=t_atoi(src[n]); + } + out.AddInt(s); +} + +MAKEFUNC(Div) +{ + if (n_src>=1) + { + UINT n; + int s=t_atoi(src[0]); + for(n=1;n<n_src;n++) + { + int t=t_atoi(src[n]); + if (t) s/=t; + else t=0; + } + out.AddInt(s); + } +} + +MAKEFUNC(Mod) +{ + if (n_src>=1) + { + UINT n; + int s=t_atoi(src[0]); + for(n=1;n<n_src;n++) + { + int t=t_atoi(src[n]); + if (t) s%=t; + else t=0; + } + out.AddInt(s); + } +} + +MAKEFUNC(Max) +{ + if (!n_src) return; + int m=t_atoi(src[0]); + UINT n; + for(n=1;n<n_src;n++) + { + int t=t_atoi(src[n]); + if (t>m) m=t; + } + out.AddInt(m); +} + +MAKEFUNC(Min) +{ + if (!n_src) return; + int m=t_atoi(src[0]); + UINT n; + for(n=1;n<n_src;n++) + { + int t=t_atoi(src[n]); + if (t<m) m=t; + } + out.AddInt(m); +} + +MAKEFUNC(Null) +{ +} + +struct +{ + TEXTFUNC func; + const T_CHAR * name; +} FUNCS[]={ +// Blah,"blah", +// Nop,"nop", + If,_TX("if"), + Upper,_TX("upper"), + Lower,_TX("lower"), + Pad,_TX("pad"), + Cut,_TX("cut"), + PadCut,_TX("padcut"), + Abbr,_TX("abbr"), + Caps,_TX("caps"), + Caps2,_TX("caps2"), + Longest,_TX("longest"), + Shortest,_TX("shortest"), + Iflonger,_TX("iflonger"), + Ifgreater,_TX("ifgreater"), + Num,_TX("num"),Num,_TX("dec"), + Hex,_TX("hex"), + StrChr,_TX("strchr"), + StrChr,_TX("strlchr"), + StrRChr,_TX("strrchr"), + StrStr,_TX("strstr"), + SubStr,_TX("substr"), + Len,_TX("len"), + Add,_TX("add"), + Sub,_TX("sub"), + Mul,_TX("mul"), + Div,_TX("div"), + Mod,_TX("mod"), + FileName,_TX("filename"), + Min,_TX("min"), + Max,_TX("max"), + Get,_TX("get"), + Put,_TX("put"), + PutQ,_TX("puts"), + Null,_TX("null"), +}; + + +class FMT +{ +private: + String str; + VarList *vars; + T_CHAR * spec; + TAGFUNC f; + TAGFREEFUNC ff; + void * fp; + T_CHAR * org_spec; + int found; + + void Error(T_CHAR *e=0) {str.Reset();str.AddString(e ? e : _TX("[SYNTAX ERROR IN FORMATTING STRING]"));} + + T_CHAR * _FMT(T_CHAR * s,UINT *f=0) + { + FMT fmt(this,s); + T_CHAR * c=(T_CHAR*)fmt; + if (f) *f=fmt.found; + found+=fmt.found; + return c; + } + + static bool skipshit(T_CHAR** _p,T_CHAR *bl) + { + T_CHAR * p=*_p; + int bc1=0,bc2=0; + while(*p) + { + if (!bc1 && !bc2 && bl) + { + T_CHAR *z=bl; + while(*z) + { + if (*z==*p) break; + z++; + } + if (*z) break; + } + if (*p=='\'') + { + p++; + while(*p && *p!='\'') p++; + if (!*p) return 0; + } + else if (*p=='(') bc1++; + else if (*p==')') + { + if (--bc1<0) return 0; + } + else if (*p=='[') bc2++; + else if (*p==']') + { + if (--bc2<0) return 0; + } + p++; + } + *_p=p; + return *p && !bc1 && !bc2; + } + + void run() + { + if (!spec) {Error();return;} + while(*spec) + { + if (*spec=='%') + { + spec++; + if (*spec=='%') {str.AddChar('%');spec++;continue;} + T_CHAR* s1=spec+1; + while(*s1 && *s1!='%') s1++; + if (!*s1) {Error();break;} + *s1=0; + T_CHAR * tag=f(spec,fp); + *s1='%'; + //if (!tag) tag=tag_unknown; + if (tag && tag[0]) + { + found++; + str.AddString(tag); + } + else + { + str.AddString(_TX("?")); + } + if (tag && ff) ff(tag,fp); + spec=s1+1; + } + else if (*spec=='$') + { + spec++; + if (*spec=='$') {str.AddChar('$');spec++;continue;} + T_CHAR * s1=spec+1; + while(*s1 && *s1!='(') s1++; + if (!*s1) {Error();break;} + T_CHAR * s2=s1+1; + if (!skipshit(&s2,_TX(")"))) {Error();break;} + if (!*s2) {Error();break;}; + T_CHAR * p=s1+1; + T_CHAR* temp[64]; + UINT temp_f[64]; + UINT nt=0; + T_CHAR * p1=s1+1; + while(p<=s2 && nt<64) + { + if (!skipshit(&p,_TX(",)"))) {Error();return;} + if (p>s2 || (*p!=',' && *p!=')')) {Error(_TX("internal error"));return;} + T_CHAR bk=*p; + *p=0; + temp[nt]=_FMT(p1,&temp_f[nt]); + nt++; + *p=bk;; + p1=p+1; + p++; + } + *s1=0; + UINT n; + TEXTFUNC fn=0; + for(n=0;n<TABSIZE(FUNCS);n++) + { + if (!t_stricmp(spec,FUNCS[n].name)) {fn=FUNCS[n].func;break;} + } + *s1='('; + if (fn) + { + fn(nt,temp,temp_f,str,*vars); + } + else + { + str.AddString(_TX("[UNKNOWN FUNCTION]")); + } + for(n=0;n<nt;n++) free(temp[n]); + spec=s2+1; + } + else if (*spec=='\'') + { + spec++; + if (*spec=='\'') {str.AddChar('\'');spec++;continue;} + T_CHAR * s1=spec+1; + while(*s1 && *s1!='\'') s1++; + if (!*s1) {Error();break;} + *s1=0; + str.AddString(spec); + *s1='\''; + spec=s1+1; + } + else if (*spec=='[') + { + spec++; + T_CHAR * s1=spec; + if (!skipshit(&s1,_TX("]"))) {Error();break;} + T_CHAR bk=*s1; + *s1=0; + FMT fmt(this,spec); + fmt.run(); + if (fmt.found) + { + str.AddString(fmt); + found+=fmt.found; + } + *s1=bk; + spec=s1+1; + } + else if (*spec==']' || *spec=='(' || *spec==')') {Error();break;} + else + { + str.AddChar(*spec); + spec++; + } + } + } + + FMT(FMT* base,T_CHAR * _spec) + { + vars=base->vars; + found=0; + org_spec=0; + f=base->f; + ff=base->ff; + fp=base->fp; + spec=_spec; + } +public: + FMT(T_CHAR * p_spec,TAGFUNC _f,TAGFREEFUNC _ff,void * _fp,VarList * _vars) + { + vars=_vars; + found=0; + org_spec=spec=t_strdup(p_spec); + f=_f; + ff=_ff; + fp=_fp; + } + operator T_CHAR*() + { + run(); + return str.GetBuf(); + } + ~FMT() + { + if (org_spec) free(org_spec); + } +}; + +extern "C" +{ + +UINT tagz_format(T_CHAR * spec,TAGFUNC f,TAGFREEFUNC ff,void *fp,T_CHAR* out,UINT max) +{ + T_CHAR * zz=tagz_format_r(spec,f,ff,fp); + UINT r=0; + while(r<max-1 && zz[r]) + { + out[r]=zz[r]; + r++; + } + out[r]=0; + free(zz); + return r; +} + +T_CHAR * tagz_format_r(T_CHAR* spec,TAGFUNC f,TAGFREEFUNC ff,void * fp) +{ + VarList vars; + return FMT(spec,f,ff,fp,&vars); +} + +//char tagz_manual[]="TODO: WTFM"; + +char tagz_manual[]="Syntax reference: \n" + "\n" + "* %tagname% - inserts field named <tagname>, eg. \"%artist%\"\n" + "* $abbr(x) - inserts abbreviation of x, eg. \"$abbr(%album%)\" - will convert album name of \"Final Fantasy VI\" to \"FFVI\"\n" + "* $abbr(x,y) - inserts abbreviation of x if x is longer than y characters; otherwise inserts full value of x, eg. \"$abbr(%album%,10)\"\n" + "* $lower(x), $upper(x) - converts x to in lower/uppercase, eg. \"$upper(%title%)\"\n" + "* $num(x,y) - displays x number and pads with zeros up to y characters (useful for track numbers), eg. $num(%tracknumber%,2)\n" + "* $caps(x) - converts first letter in every word of x to uppercase, and all other letters to lowercase, eg. \"blah BLAH\" -> \"Blah Blah\"\n" + "* $caps2(x) - similar to $caps, but leaves uppercase letters as they are, eg. \"blah BLAH\" -> \"Blah BLAH\"\n" + "* $if(A,B,C) - if A contains at least one valid tag, displays B, otherwise displays C; eg. \"$if(%artist%,%artist%,unknown artist)\" will display artist name if present; otherwise will display \"unknown artist\"; note that \"$if(A,A,)\" is equivalent to \"[A]\" (see below)\n" + "* $longest(A,B,C,....) - compares lengths of output strings produced by A,B,C... and displays the longest one, eg. \"$longest(%title%,%comment%)\" will display either title if it's longer than comment; otherwise it will display comment\n" + "* $pad(x,y) - pads x with spaces up to y characters\n" + "* $cut(x,y) - truncates x to y characters\n" + "* $padcut(x,y) - pads x to y characters and truncates to y if longer\n" + "* [ .... ] - displays contents of brackets only if at least one of fields referenced inside has been found, eg. \"%artist% - [%album% / ]%title%\" will hide [] block if album field is not present\n" + "* \' (single quotation mark) - outputs raw text without parsing, eg, \'blah$blah%blah[][]\' will output the contained string and ignore all reserved characters (%,$,[,]) in it; you can use this feature to insert square brackets for an example.\n" + "\n" + "eg. \"[%artist% - ][$abbr(%album%,10)[ %tracknumber%] / ]%title%[ %streamtitle%]\"\n"; + +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/tagz.h b/Src/Plugins/Input/in_mp3/tagz.h new file mode 100644 index 00000000..4699ca98 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/tagz.h @@ -0,0 +1,26 @@ +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef UINT +typedef unsigned int UINT; +#endif + +#ifdef TAGZ_UNICODE +typedef unsigned short T_CHAR; +#else +#define T_CHAR char +#endif + +typedef T_CHAR* (*TAGFUNC)(T_CHAR * tag,void * p); //return 0 if not found +typedef void (*TAGFREEFUNC)(T_CHAR * tag,void * p); + + +UINT tagz_format(T_CHAR * spec,TAGFUNC f,TAGFREEFUNC ff,void *fp,T_CHAR * out,UINT max); +T_CHAR * tagz_format_r(T_CHAR * spec,TAGFUNC f,TAGFREEFUNC ff,void * fp); + +extern char tagz_manual[]; + +#ifdef __cplusplus +} +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/titlelist.cpp b/Src/Plugins/Input/in_mp3/titlelist.cpp new file mode 100644 index 00000000..65767e49 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/titlelist.cpp @@ -0,0 +1,62 @@ +#include "main.h" + +TITLELISTTYPE TitleListTerminator; +TITLELISTTYPE *TitleLinkedList = &TitleListTerminator; + +void initTitleList(void) +{ + TitleListTerminator.Next = NULL; + TitleListTerminator.timer = 0; +} + + +/* ----------------------------------------------------------------------------------------------- + Adds an entry in the list + -----------------------------------------------------------------------------------------------*/ +TITLELISTTYPE *newTitleListEntry(void) +{ +TITLELISTTYPE *TitleObject = (TITLELISTTYPE *)calloc(1,sizeof(TITLELISTTYPE)); /* Allocate new entry */ +TitleObject->Next = (void *)TitleLinkedList; /* New entry's next is old list _head */ +TitleLinkedList = TitleObject; /* new _head is new entry */ +return TitleObject; /* return pointer to new entry */ +} + +/* ----------------------------------------------------------------------------------------------- + Removes an entry from the list + -----------------------------------------------------------------------------------------------*/ +void removeTitleListEntry(TITLELISTTYPE *Entry) +{ +TITLELISTTYPE *TitleObject = TitleLinkedList; + + if (TitleObject == &TitleListTerminator) return; /* List is empty */ + + if (TitleObject == (void *)Entry) + { + TitleLinkedList = (TITLELISTTYPE *)TitleObject->Next; + free(TitleObject); + } + else + while (TitleObject->Next) /* While not terminator */ + { + if ((TITLELISTTYPE *)(TitleObject->Next) == (void *)Entry) /* If next entry is what we're looking for */ + { + TitleObject->Next = ((TITLELISTTYPE *)(TitleObject->Next))->Next; /* Skip one entry */ + free(Entry); /* free the entry we dont' want anymore */ + } + TitleObject = (TITLELISTTYPE *)TitleObject->Next; /* Get next entry */ + } +} + +void clearTitleList() +{ + TITLELISTTYPE *TitleObject = TitleLinkedList; + if (TitleObject == &TitleListTerminator) return; /* List is empty */ + + while (TitleObject->Next) /* While not terminator */ + { + TITLELISTTYPE *KillMe=TitleObject; + TitleObject = (TITLELISTTYPE *)KillMe->Next; /* Get next entry */ + free(KillMe); + } + TitleLinkedList = &TitleListTerminator; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/todo.txt b/Src/Plugins/Input/in_mp3/todo.txt new file mode 100644 index 00000000..4cbe62e6 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/todo.txt @@ -0,0 +1,97 @@ +Changes:
+ * [a7] made seeking work (slightly) better on realshitbox encoded mp3s (with broken
+ VBR headers)
+ * [a7] made save http file location persistent when turned off
+ * [a7] fixed id3v2 bug (1 character strings not being displayed correctly)
+ * [a6] fixed stupid file association bug (oops)
+ * [a5] fixed crash when repeating a non-existing file bug (added a Sleep())
+ * [a5] fixed shoutcast disk writer issue
+ * [a5] fixed SendMessage() potential issues (using postmessage and SendMessageTimeout() now)
+ * [a5] added new format-for-non-id3 files, added 'use id3 tag' option, which lets you disable them
+ completely
+ * [a5] return of the file association list
+ * [a5] made temp file handling slightly better-- checks for read only, better error messages.
+ * [a4] fixed lots of potential (and a few serious) bugs in id3lib.
+ * [a4] fixed pause right after playback starts bug
+ * fixed crash/hang/freeze when reading some mp3 files with a weird id3v2 tag (as found in some
+ real jukebox generated mp3s, etc...)
+ * this one will break a few things (i.e. windowshade vis), because justin is updating it to go
+ with winamp 2.7
+ * all code is now win32 file io
+ * %a will now display id3v1.1/v2 track #
+ * fixed id3v1 reading bug that added year field in album field
+ * why does it ask me to stop the currently playing file when updating an id3v2 tag ?
+ answer:
+ whenever you strip or update an id3v2 tag, it creates a tmp file (FILENAME.new),
+ writes it out, and if it wrote it out correctly, then it renames the original
+ to FILENAME.bak, and renames the new one to FILENAME, and if that was successful,
+ then deletes FILENAME.bak). this is required because of the implementation of the
+ id3v2 protocol.
+
+ * [2.666b] fixed crash when using crossfading output plugin
+ * [2.666b] fixed the issue that files with large id3v2 tags don't seek correctly
+ * [2.666b] added id3v1.1 track field editing
+ * [2.666b] simplified id3 edit box (removed all save/remove buttons, all is done via
+ update button now)
+ * [2.666b] fixed some more stuff in id3 edit box... should be more reliable
+ * [2.666b] contains devil easter egg
+
+ * winamp 2.666 release
+
+ * [a18] dll is smaller
+ * [a18] fixed vbr header reading on some musicmatch/crap generated files
+ * [a18] id3v1.1 track # reading support (who cares about id3v1.1 writing?)
+ * [a18] crc checking is now activable in prefs box
+ * [a18] "show average on VBR files" is now activable in prefs box
+ * [a17] "update tags" button only saves selected tags now
+ * [a17] vbr-division-by-0-bug-on-edit fixed
+ * [a17] long id3v1 tags reading corrected
+ * [a17] id3v2 url tag will now interact with the minibrowser
+ * [a17] added id3v2 variables to id3 title formatting
+ * [a16] corrected crash/bug in id3v2 genre reading
+ * [a16] corrected id3v2 comment editor to support multiple lines :)
+ * [a16] new "stop track" button in id3v2 editor so you don't have to retype everything
+ when id3v2 can't be updated because file is locked
+ * [a16] added track number id3v2 field
+ * [a16] id3v2 warnings no more appear under id3 tag editor
+ * [a10] streaming info improvements/fixes
+ * [a10] made more options for streaming title formatting (for you brennan)
+ * [a10] still needs better id3v2 reading writing. THIS IS ON THE WAY, CHILL.
+ * [a9] improved streaming error notification (i.e. on can't connect, can't resolve, timeout)
+ * [a9] made streaming detect id3v2 tag and skip it (todo: make it look at the id3v2 tag and use it)
+ * [a9] updated id3v2 support to detect invalid id3v2 tags, and autodetect their actual
+ size
+ * [a9] info box now tells you where the first mpeg header was found (useful)
+ * [a8] fixed live365 streaming (they need a space between User-Agent:
+ and the agent string. those assclowns.)
+ * [a8] rescheduled some of the polyphase for a few cycles
+ * [a7] bugfix: vbr headers read when id3v2 tag is present now
+ * [a7] downsampling modes have better vis support
+ * [a7] id3v2 writing support
+ * [a7] stream info box
+ * [a6] mmm.
+ * [a6] return of working id3 code
+ * [a5] optimized bitgetting.
+ * [a5] keen streaming buffer indicators in mini-vis display
+ * [a5] made fast eq modes optional (can use slow pcm eq like wav files)
+ * [a5] fixed fastly-changing-tracks bug
+ * [a4] tuned decode loop more
+ * [a4] optimized huffman decoding
+ * [a3] improved network code. updates status in title area.
+ * [a3] layer 1/2 eq code
+ * [a3] optimized decoder some more. we can still make it a bit faster me thinks.
+ * [a3] moved more code into decode thread.. should act much more asynchronously
+ * [a2] Improved skip robustness
+ * [a2] Optimized decoder for ppro. changed 8 bit mode for speed.
+ * [a2] partial ID3V2 support
+ * Fully ISO compliant decoder (based on FHG's implementation)
+ * Also fully supports MPEG 2.5 low bitrates.
+ * Full MPEG Layer 1 and Layer 2 support
+ * Improved equalizer code
+ * Optimized visualization data generation code
+ * Improved network code (single threaded)
+ * Lots of other cleanups
+
+todo:
+ make more blip resistant (see pvd.mp3)
+ remove seek-blip
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/uvox_3901.cpp b/Src/Plugins/Input/in_mp3/uvox_3901.cpp new file mode 100644 index 00000000..d385a1aa --- /dev/null +++ b/Src/Plugins/Input/in_mp3/uvox_3901.cpp @@ -0,0 +1,79 @@ +#include "uvox_3901.h" +#include "api__in_mp3.h" +#include "api/service/waservicefactory.h" +#include <strsafe.h> +#include "in2.h" +extern In_Module mod; + +#include "FactoryHelper.h" + + +Ultravox3901::Ultravox3901() : parser(0) +{ + title[0]=album[0]=artist[0]=album_art_url[0]=0; + ServiceBuild(parser, obj_xmlGUID); + if (parser) + { + parser->xmlreader_registerCallback(L"metadata\fsong\f*", this); + parser->xmlreader_open(); + } +} + +Ultravox3901::~Ultravox3901() +{ + ServiceRelease(parser, obj_xmlGUID); +} + +int Ultravox3901::Parse(const char *xml_data) +{ + if (parser) + { + int ret = parser->xmlreader_feed((void *)xml_data, strlen(xml_data)); + if (ret != API_XML_SUCCESS) + return ret; + return parser->xmlreader_feed(0, 0); + } + return API_XML_FAILURE; +} + +void Ultravox3901::TextHandler(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *str) +{ + if (!_wcsicmp(xmltag, L"name")) + { + StringCbCatW(title, sizeof(title), str); + } + else if (!_wcsicmp(xmltag, L"album")) + { + StringCbCatW(album, sizeof(album), str); + } + else if (!_wcsicmp(xmltag, L"artist")) + { + StringCbCatW(artist, sizeof(artist), str); + } + else if (!_wcsicmp(xmltag, L"album_art")) + { + StringCbCatW(album_art_url, sizeof(album_art_url), str); + } +} + +int Ultravox3901::GetExtendedData(const char *tag, wchar_t *data, int dataLen) +{ + if (!_stricmp(tag, "uvox/title")) + StringCchCopy(data, dataLen, title); + else if (!_stricmp(tag, "uvox/album")) + StringCchCopy(data, dataLen, album); + else if (!_stricmp(tag, "uvox/artist")) + StringCchCopy(data, dataLen, artist); + else if (!_stricmp(tag, "uvox/albumart")) + StringCchCopy(data, dataLen, album_art_url); + else + return 0; + + return 1; +} + +#define CBCLASS Ultravox3901 +START_DISPATCH; +VCB(ONCHARDATA, TextHandler) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/uvox_3901.h b/Src/Plugins/Input/in_mp3/uvox_3901.h new file mode 100644 index 00000000..69d0a8fe --- /dev/null +++ b/Src/Plugins/Input/in_mp3/uvox_3901.h @@ -0,0 +1,20 @@ +#pragma once +#include "../xml/obj_xml.h" +#include "../xml/ifc_xmlreadercallback.h" + + +class Ultravox3901 : public ifc_xmlreadercallback +{ +public: + Ultravox3901(); + ~Ultravox3901(); + int Parse(const char *xml_data); + int GetExtendedData(const char *tag, wchar_t *data, int dataLen); +private: + /* XML callbacks */ + void TextHandler(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *str); + + obj_xml *parser; + wchar_t title[256],artist[256],album[256],album_art_url[4096]; + RECVS_DISPATCH; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/uvox_3902.cpp b/Src/Plugins/Input/in_mp3/uvox_3902.cpp new file mode 100644 index 00000000..95521e58 --- /dev/null +++ b/Src/Plugins/Input/in_mp3/uvox_3902.cpp @@ -0,0 +1,77 @@ +#include "uvox_3902.h" +#include "api__in_mp3.h" +#include "api/service/waservicefactory.h" +#include <strsafe.h> +#include "in2.h" +extern In_Module mod; + +#include "FactoryHelper.h" + +Ultravox3902::Ultravox3902() : parser(0) +{ + title[0]=album[0]=artist[0]=0; + ServiceBuild(parser, obj_xmlGUID); + if (parser) + { + parser->xmlreader_setCaseSensitive(); + parser->xmlreader_registerCallback(L"metadata\f*", this); + parser->xmlreader_open(); + } +} + +Ultravox3902::~Ultravox3902() +{ + if (parser) + { + parser->xmlreader_unregisterCallback(this); + parser->xmlreader_close(); + } + ServiceRelease(parser, obj_xmlGUID); +} + +int Ultravox3902::Parse(const char *xml_data) +{ + if (parser) + { + int ret = parser->xmlreader_feed((void *)xml_data, strlen(xml_data)); + if (ret != API_XML_SUCCESS) + return ret; + return parser->xmlreader_feed(0, 0); + } + return API_XML_FAILURE; +} + +void Ultravox3902::TextHandler(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *str) +{ + if (!_wcsicmp(xmlpath, L"metadata\fTIT2")) + { + StringCbCatW(title, sizeof(title), str); + } + else if (!_wcsicmp(xmlpath, L"metadata\fTALB")) + { + StringCbCatW(album, sizeof(album), str); + } + else if (!_wcsicmp(xmlpath, L"metadata\fTPE1")) + { + StringCbCatW(artist, sizeof(artist), str); + } +} + +int Ultravox3902::GetExtendedData(const char *tag, wchar_t *data, int dataLen) +{ + if (!_stricmp(tag, "title")) + StringCchCopy(data, dataLen, title); + else if (!_stricmp(tag, "album")) + StringCchCopy(data, dataLen, album); + else if (!_stricmp(tag, "artist")) + StringCchCopy(data, dataLen, artist); + else + return 0; + return 1; +} + +#define CBCLASS Ultravox3902 +START_DISPATCH; +VCB(ONCHARDATA, TextHandler) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/uvox_3902.h b/Src/Plugins/Input/in_mp3/uvox_3902.h new file mode 100644 index 00000000..5314719d --- /dev/null +++ b/Src/Plugins/Input/in_mp3/uvox_3902.h @@ -0,0 +1,20 @@ +#pragma once +#include "../xml/obj_xml.h" +#include "../xml/ifc_xmlreadercallback.h" + + +class Ultravox3902 : public ifc_xmlreadercallback +{ +public: + Ultravox3902(); + ~Ultravox3902(); + int Parse(const char *xml_data); + int GetExtendedData(const char *tag, wchar_t *data, int dataLen); +private: + /* XML callbacks */ + void TextHandler(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *str); + + obj_xml *parser; + wchar_t title[256],artist[256],album[256]; + RECVS_DISPATCH; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp3/version.rc2 b/Src/Plugins/Input/in_mp3/version.rc2 new file mode 100644 index 00000000..2a761aed --- /dev/null +++ b/Src/Plugins/Input/in_mp3/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 4,6,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", "4,6,0,0" + VALUE "InternalName", "Nullsoft MPEG Audio Decoder" + VALUE "LegalCopyright", "Copyright © 1998-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_mp3.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_mp4/AlbumArt.cpp b/Src/Plugins/Input/in_mp4/AlbumArt.cpp new file mode 100644 index 00000000..d990162d --- /dev/null +++ b/Src/Plugins/Input/in_mp4/AlbumArt.cpp @@ -0,0 +1,232 @@ +#include "mp4.h" +#include "AlbumArt.h" +#include "api__in_mp4.h" +#include "main.h" +#include "../nu/AutoWide.h" +#include "VirtualIO.h" +#include "Stopper.h" +#include <shlwapi.h> +#include <strsafe.h> + +bool IsMyExtension(const wchar_t *filename) +{ + const wchar_t *extension = PathFindExtension(filename); + if (extension && *extension) + { + wchar_t exts[1024] = L""; + // TODO: build a copy of this at config load time so we don't have to run this every time + GetPrivateProfileStringW(L"in_mp4", L"extensionlist", defaultExtensions, exts, 1024, m_ini); + + extension++; + wchar_t *b = exts; + wchar_t *c = 0; + do + { + wchar_t d[20] = {0}; + StringCchCopyW(d, 15, b); + c = wcschr(b, L';'); + if (c) + { + if ((c-b)<15) + d[c - b] = 0; + } + + if (!_wcsicmp(extension, d)) + return true; + + b = c + 1; + } + while (c); + } + return false; +} + +bool MP4_AlbumArtProvider::IsMine(const wchar_t *filename) +{ + return IsMyExtension(filename); +} + +int MP4_AlbumArtProvider::ProviderType() +{ + return ALBUMARTPROVIDER_TYPE_EMBEDDED; +} + +static int MimeTypeToFlags(const wchar_t *mime_type) +{ + if (!mime_type) + return 0; + + if (!_wcsicmp(mime_type, L"jpeg") + || !_wcsicmp(mime_type, L"jpg") + || !_wcsicmp(mime_type, L"image/jpeg") + || !_wcsicmp(mime_type, L"image/jpg")) + return 13; /* JPEG */ + + if (!_wcsicmp(mime_type, L"png") + || !_wcsicmp(mime_type, L"image/png")) + return 14; /* PNG */ + + if (!_wcsicmp(mime_type, L"gif") + || !_wcsicmp(mime_type, L"image/gif")) + return 12; /* GIF */ + + if (!_wcsicmp(mime_type, L"bmp") + || !_wcsicmp(mime_type, L"image/bmp")) + return 27; /* BMP */ + + return 0; /* default to binary, I guess */ +} + +int MP4_AlbumArtProvider::GetAlbumArtData(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType) +{ + void *reader = CreateUnicodeReader(filename); + if (!reader) + return ALBUMARTPROVIDER_FAILURE; + + MP4FileHandle mp4 = MP4ReadEx(filename, reader, &UnicodeIO); + if (!mp4) + { + DestroyUnicodeReader(reader); + return ALBUMARTPROVIDER_FAILURE; + } + else + { + UnicodeClose(reader); // go ahead and close the file so we don't lock it + } + + u_int8_t *art = 0; + u_int32_t artSize = 0; + int flags = 0; + if (MP4GetMetadataCoverArt(mp4, &art, &artSize, &flags)) + { + *bits = WASABI_API_MEMMGR->sysMalloc(artSize); + memcpy(*bits, art, artSize); + *len=artSize; + /* TODO: use flags */ + *mimeType = 0; // no idea what the mime type is :( + MP4Free(art); + MP4Close(mp4); + DestroyUnicodeReader(reader); + return ALBUMARTPROVIDER_SUCCESS; + } + + MP4Close(mp4); + DestroyUnicodeReader(reader); + return ALBUMARTPROVIDER_FAILURE; +} + +int MP4_AlbumArtProvider::SetAlbumArtData(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType) +{ + MP4FileHandle mp4 = MP4Modify(filename, 0, 0); + if (!mp4) + { + return ALBUMARTPROVIDER_FAILURE; + } + + int flags = MimeTypeToFlags(mimeType); + if (MP4SetMetadataCoverArt(mp4, (u_int8_t *)bits, len, flags)) + { + MP4Close(mp4); + return ALBUMARTPROVIDER_SUCCESS; + } + + MP4Close(mp4); + return ALBUMARTPROVIDER_FAILURE; +} + +int MP4_AlbumArtProvider::DeleteAlbumArt(const wchar_t *filename, const wchar_t *type) +{ + MP4FileHandle mp4 = MP4Modify(filename, 0, 0); + + if (!mp4) + { + return ALBUMARTPROVIDER_FAILURE; + } + + if (MP4DeleteMetadataCoverArt(mp4)) + { + Stopper stopper; + if (!_wcsicmp(filename, lastfn)) + stopper.Stop(); + MP4Close(mp4); + stopper.Play(); + return ALBUMARTPROVIDER_SUCCESS; + } + + MP4Close(mp4); + return ALBUMARTPROVIDER_FAILURE; +} + + +#define CBCLASS MP4_AlbumArtProvider +START_DISPATCH; +CB(SVC_ALBUMARTPROVIDER_PROVIDERTYPE, ProviderType); +CB(SVC_ALBUMARTPROVIDER_GETALBUMARTDATA, GetAlbumArtData); +CB(SVC_ALBUMARTPROVIDER_SETALBUMARTDATA, SetAlbumArtData); +CB(SVC_ALBUMARTPROVIDER_DELETEALBUMART, DeleteAlbumArt); +CB(SVC_ALBUMARTPROVIDER_ISMINE, IsMine); +END_DISPATCH; +#undef CBCLASS + +static MP4_AlbumArtProvider albumArtProvider; + +// {315CA473-4A7B-43a9-BB1B-7E1C24B3BFE2} +static const GUID mp4_albumartproviderGUID = + { 0x315ca473, 0x4a7b, 0x43a9, { 0xbb, 0x1b, 0x7e, 0x1c, 0x24, 0xb3, 0xbf, 0xe2 } }; + +FOURCC AlbumArtFactory::GetServiceType() +{ + return svc_albumArtProvider::SERVICETYPE; +} + +const char *AlbumArtFactory::GetServiceName() +{ + return "MP4 Album Art Provider"; +} + +GUID AlbumArtFactory::GetGUID() +{ + return mp4_albumartproviderGUID; +} + +void *AlbumArtFactory::GetInterface(int global_lock) +{ + return &albumArtProvider; +} + +int AlbumArtFactory::SupportNonLockingInterface() +{ + return 1; +} + +int AlbumArtFactory::ReleaseInterface(void *ifc) +{ + //plugin.service->service_unlock(ifc); + return 1; +} + +const char *AlbumArtFactory::GetTestString() +{ + return 0; +} + +int AlbumArtFactory::ServiceNotify(int msg, int param1, int param2) +{ + return 1; +} + +#ifdef CBCLASS +#undef CBCLASS +#endif + +#define CBCLASS AlbumArtFactory +START_DISPATCH; +CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType) +CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName) +CB(WASERVICEFACTORY_GETGUID, GetGUID) +CB(WASERVICEFACTORY_GETINTERFACE, GetInterface) +CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface) +CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface) +CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString) +CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify) +END_DISPATCH;
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/AlbumArt.h b/Src/Plugins/Input/in_mp4/AlbumArt.h new file mode 100644 index 00000000..40dccba5 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/AlbumArt.h @@ -0,0 +1,39 @@ +#ifndef NULLSOFT_IN_MP3_ALBUMART_H +#define NULLSOFT_IN_MP3_ALBUMART_H + +#include "../Agave/AlbumArt/svc_albumArtProvider.h" + +class MP4_AlbumArtProvider : public svc_albumArtProvider +{ +public: + bool IsMine(const wchar_t *filename); + int ProviderType(); + // implementation note: use WASABI_API_MEMMGR to alloc bits and mimetype, so that the recipient can free through that + int GetAlbumArtData(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType); + int SetAlbumArtData(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType); + int DeleteAlbumArt(const wchar_t *filename, const wchar_t *type); +protected: + RECVS_DISPATCH; +}; + +#include <api/service/waservicefactory.h> +#include <api/service/services.h> + +class AlbumArtFactory : public waServiceFactory +{ +public: + FOURCC GetServiceType(); + const char *GetServiceName(); + GUID GetGUID(); + void *GetInterface(int global_lock); + int SupportNonLockingInterface(); + int ReleaseInterface(void *ifc); + const char *GetTestString(); + int ServiceNotify(int msg, int param1, int param2); + +protected: + RECVS_DISPATCH; +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/AudioSample.h b/Src/Plugins/Input/in_mp4/AudioSample.h new file mode 100644 index 00000000..df483b26 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/AudioSample.h @@ -0,0 +1,80 @@ +#ifndef NULLSOFT_IN_MP4_AUDIOSAMPLE_H +#define NULLSOFT_IN_MP4_AUDIOSAMPLE_H + +#include "main.h" + +class AudioSample +{ +public: + AudioSample(size_t maxInput, size_t maxOutput) + { + input = (unsigned __int8 *)calloc(maxInput, sizeof(unsigned __int8)); + inputSize = maxInput; + + output = (__int8 *)calloc(maxOutput, sizeof(__int8)); + outputSize = maxOutput; + + inputValid = outputValid = result = sampleRate = numChannels = + bitsPerSample = bitrate = sampleId = timestamp = duration = offset = 0; + outputCursor = 0; + } + ~AudioSample() + { + free(output); + free(input); + } + bool OK() + { + return input && output; + } + // input + unsigned __int8 *input; + size_t inputSize, inputValid; + MP4SampleId sampleId; + + // output + __int8 *output, *outputCursor; + size_t outputSize, outputValid; + MP4Duration duration, offset, timestamp; + int result; + unsigned int sampleRate, numChannels, bitsPerSample; + unsigned int bitrate; +}; + +class VideoSample +{ +public: + VideoSample(size_t maxInput) + { + input = (unsigned __int8 *)calloc(maxInput, sizeof(unsigned __int8)); + inputSize = maxInput; + timestamp = inputValid = 0; + } + ~VideoSample() + { + free(input); + } + bool OK() + { + return !!input; + } + // input + unsigned __int8 *input; + size_t inputSize, inputValid; + + MP4Timestamp timestamp; +}; + +class DecodedVideoSample +{ +public: + ~DecodedVideoSample() + { + decoder->FreePicture(output,decoder_data); + } + void *output; + void *decoder_data; + MP4VideoDecoder *decoder; + MP4Timestamp timestamp; +}; +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/ExtendedInfo.cpp b/Src/Plugins/Input/in_mp4/ExtendedInfo.cpp new file mode 100644 index 00000000..43b4ab4c --- /dev/null +++ b/Src/Plugins/Input/in_mp4/ExtendedInfo.cpp @@ -0,0 +1,1022 @@ +#include "main.h" +#include "VirtualIO.h" +#include "../nu/ns_wc.h" +#include "../nu/AutoChar.h" +#include <vector> +#include "../Winamp/wa_ipc.h" +#include "api__in_mp4.h" +#include "Stopper.h" +#include <shlwapi.h> +#include <strsafe.h> +#include "resource.h" + +static inline wchar_t *IncSafe(wchar_t *val, int x) +{ + while (x--) + { + if (*val) + val++; + } + return val; +} + +bool KeywordMatch(const char *mainString, const char *keyword) +{ + return !_stricmp(mainString, keyword); +} + +bool GetCustomMetadata(MP4FileHandle mp4, char *metadata, wchar_t *dest, int destlen, const char *owner) +{ + u_int8_t *value; + u_int32_t size; + bool success = MP4GetMetadataFreeForm(mp4, metadata, &value, &size, owner); + if (success) + { + int cnt = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)value, size, dest, destlen - 1); + dest[cnt] = 0; + MP4Free(value); + } + else + dest[0] = 0; + + return success; +} + +bool HasVideo(MP4FileHandle infile) +{ + /* find Video track */ + int numTracks = MP4GetNumberOfTracks(infile, NULL, /* subType */ 0); + + for (int i = 0; i < numTracks; i++) + { + MP4TrackId trackId = MP4FindTrackId(infile, i, NULL, /* subType */ 0); + const char* trackType = MP4GetTrackType(infile, trackId); + + if (!lstrcmpA(trackType, MP4_VIDEO_TRACK_TYPE)) + return true; + + } + + /* can't decode this */ + return false; +} +typedef struct __INFOKEYWORD +{ + LPCWSTR buffer; + INT len; +} INFOKEYWORD; + +typedef std::vector<INFOKEYWORD> KeywordList; + +/*static void KeywordList_PushBack(KeywordList *list, LPCWSTR buffer, INT len) +{ + if (NULL == list) return; + + INFOKEYWORD kw = { buffer, len }; + list->push_back(kw); +} + +static LPCWSTR ParseFormatInfoLine(LPCWSTR line, KeywordList *list) +{ + if (NULL == line || L'\0' == *line) return line; + LPCWSTR cursor = line; + + for(;;) + { + switch(*cursor) + { + case L'\r': + if (L'\n' == *(cursor + 1)) + { + KeywordList_PushBack(list, line, (INT)(cursor - line)); + cursor += 2; + return cursor; + } + case L'\0': + KeywordList_PushBack(list, line, (INT)(cursor - line)); + return NULL; + case L'\t': + KeywordList_PushBack(list, line, (INT)(cursor - line)); + line = ++cursor; + break; + default: + cursor++; + break; + } + } +} + +static HRESULT FormatInformationString(LPWSTR pszBuffer, INT cchBufferMax, LPCSTR formatInfo) +{ + if (NULL == pszBuffer) return E_POINTER; + *pszBuffer = L'\0'; + + if (NULL == formatInfo) + return S_OK; + + INT cchFormat = lstrlenA(formatInfo); + if (0 == cchFormat) return S_OK; + + LPWSTR pszFormat = (LPWSTR)malloc(sizeof(WCHAR) * (cchFormat + 1)); + if (NULL == pszFormat) return E_OUTOFMEMORY; + + INT result = MultiByteToWideCharSZ(CP_UTF8, 0, formatInfo, cchFormat, pszFormat, cchFormat + 1); + if (0 == result) + { + DWORD errorCode = GetLastError(); + return HRESULT_FROM_WIN32(errorCode); + } + + HRESULT hr(S_OK); + KeywordList keys; + KeywordList values; + LPCWSTR line = pszFormat; + line = ParseFormatInfoLine(line, &keys); + line = ParseFormatInfoLine(line, &values); + + size_t count = keys.size(); + if (0 != count && count == values.size()) + { + LPWSTR cursor = pszBuffer; + size_t remaining = cchBufferMax; + for (size_t index = 0; index < count; index++) + { + if (cursor != pszBuffer) + hr = StringCchCopyEx(cursor, remaining, L"\r\n", &cursor, &remaining, 0); + + if (SUCCEEDED(hr) && 0 != keys[index].len) + { + hr = StringCchCopyNEx(cursor, remaining, keys[index].buffer, keys[index].len, &cursor, &remaining, 0); + if (SUCCEEDED(hr)) + hr = StringCchCopyEx(cursor, remaining, L":", &cursor, &remaining, 0); + } + + if (SUCCEEDED(hr) && 0 != values[index].len) + { + if (SUCCEEDED(hr) && 0 != keys[index].len) + hr = StringCchCopyEx(cursor, remaining, L" ", &cursor, &remaining, 0); + + hr = StringCchCopyNEx(cursor, remaining, values[index].buffer, values[index].len, &cursor, &remaining, 0); + } + + if (FAILED(hr)) + break; + } + } + else + { + hr = StringCchCopy(pszBuffer, cchBufferMax, pszFormat); + } + + if (NULL != pszFormat) + free(pszFormat); + + return hr; +}*/ + +static MP4FileHandle getFileInfoMP4 = 0; +static wchar_t getFileInfoFn[MAX_PATH]=L""; +static void *getFileInfoReader=0; +static FILETIME ftLastWriteTime; + +// is used to determine if the last write time of the file has changed when +// asked to get the metadata for the same cached file so we can update things +BOOL HasFileTimeChanged(const wchar_t *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData = {0}; + if (GetFileAttributesExW(fn, GetFileExInfoStandard, &fileData) == TRUE) + { + if(CompareFileTime(&ftLastWriteTime, &fileData.ftLastWriteTime)) + { + ftLastWriteTime = fileData.ftLastWriteTime; + return TRUE; + } + } + return FALSE; +} + +void UpdateFileTimeChanged(const wchar_t *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData; + if (GetFileAttributesExW(fn, GetFileExInfoStandard, &fileData) == TRUE) + { + ftLastWriteTime = fileData.ftLastWriteTime; + } +} + +extern "C" +{ + __declspec( dllexport ) int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, size_t destlen) + { + if (KeywordMatch(data, "type")) + { + if (!fn || (fn && !fn[0]) || !PathFileExistsW(fn)) + { + const wchar_t *e = PathFindExtensionW(fn); + DWORD lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); + // check known video extensions + if ((CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L".M4V", -1)) + || (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L".MOV", -1)) + || (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L".F4V", -1)) + || (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L".MP4", -1))) + dest[0] = '1'; + else + dest[0] = '0'; + dest[1] = 0; + return 1; + } + } + else if (KeywordMatch(data, "rateable")) + { + dest[0] = '1'; + dest[1] = 0; + return 1; + } + + if (!fn || (fn && !fn[0])) return 0; + + if (KeywordMatch(data, "family")) + { + LPCWSTR e; + int pID = -1; + e = PathFindExtensionW(fn); + if (L'.' != *e) return 0; + e++; + DWORD lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); + if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"M4A", -1)) pID = IDS_FAMILY_STRING_M4A; + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"MP4", -1)) pID = IDS_FAMILY_STRING_MPEG4; + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"M4V", -1)) pID = IDS_FAMILY_STRING_M4V; + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"MOV", -1)) pID = IDS_FAMILY_STRING_QUICKTIME; + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"3GP", -1)) pID = IDS_FAMILY_STRING_3GPP; + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"F4V", -1)) pID = IDS_FAMILY_STRING_FLV; + if (pID != -1 && S_OK == StringCchCopyW(dest, destlen, WASABI_API_LNGSTRINGW(pID))) return 1; + return 0; + } + + if (KeywordMatch(data, "mime")) + { + LPCWSTR e; + e = PathFindExtensionW(fn); + if (L'.' != *e) return 0; + e++; + DWORD lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); + if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"M4A", -1)) { StringCchCopyW(dest, destlen, L"audio/mp4"); return 1; } + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"MP4", -1)) { StringCchCopyW(dest, destlen, L"audio/mp4"); return 1; } + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"M4V", -1)) { StringCchCopyW(dest, destlen, L"video/mp4"); return 1; } + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"MOV", -1)) { StringCchCopyW(dest, destlen, L"video/quicktime"); return 1; } + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"3GP", -1)) { StringCchCopyW(dest, destlen, L"video/3gp"); return 1; } + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"F4V", -1)) { StringCchCopyW(dest, destlen, L"video/f4v"); return 1; } + return 0; + } + + if (!getFileInfoMP4 || lstrcmpi(getFileInfoFn, fn) || HasFileTimeChanged(fn)) // different file than last time? + { + lstrcpyn(getFileInfoFn, fn, MAX_PATH); + + if (getFileInfoMP4) + MP4Close(getFileInfoMP4); + getFileInfoMP4=0; + + if (getFileInfoReader) + DestroyUnicodeReader(getFileInfoReader); + + getFileInfoReader = CreateUnicodeReader(fn); + if (!getFileInfoReader) + { + getFileInfoFn[0]=0; + return 0; + } + + getFileInfoMP4 = MP4ReadEx(fn, getFileInfoReader, &UnicodeIO); + if (!getFileInfoMP4) + { + DestroyUnicodeReader(getFileInfoReader); + getFileInfoReader=0; + getFileInfoFn[0]=0; + return 0; + } + else + { + UnicodeClose(getFileInfoReader); // go ahead and close the file so we don't lock it + } + } + + bool success = false; + char *pVal = NULL; + uint16_t *pVal_utf16 = NULL; + + if (KeywordMatch(data, "type")) + { + if (GetVideoTrack(getFileInfoMP4) != MP4_INVALID_TRACK_ID) // check for a video track + dest[0] = '1'; + else // assume no video mean audio (not necessarily true, for weird mp4 stuff like systems & text) + dest[0] = '0'; + dest[1] = 0; + success = true; + } + else if (KeywordMatch(data, "lossless")) + { + dest[0]='0'; + dest[1]=0; + MP4TrackId track = GetAudioTrack(getFileInfoMP4); + if (track != MP4_INVALID_TRACK_ID) + { + // TODO: benski> We should check more than just ALAC + // potentially asking the mpeg4audio services + // but for now this should suffice. + const char *media_data_name = MP4GetTrackMediaDataName(getFileInfoMP4, track); + if (media_data_name && KeywordMatch (media_data_name, "alac")) + dest[0]='1'; + } + success=true; + } + else if (KeywordMatch(data, "title")) + { + success = MP4GetMetadataName(getFileInfoMP4, &pVal); + if (!success) + success = MP4Get3GPMetadata(getFileInfoMP4, "titl", &pVal_utf16); + } + else if (KeywordMatch(data, "album")) + { + success = MP4GetMetadataAlbum(getFileInfoMP4, &pVal); + if (!success) + success = MP4Get3GPMetadata(getFileInfoMP4, "albm", &pVal_utf16); + } + else if (KeywordMatch(data, "artist")) + { + success = MP4GetMetadataArtist(getFileInfoMP4, &pVal); + if (!success) + success = MP4Get3GPMetadata(getFileInfoMP4, "perf", &pVal_utf16); + } + else if (KeywordMatch(data, "rating")) + { + success = MP4GetMetadataRating(getFileInfoMP4, &pVal); + // add a /* to enable reading from 3GP metadata, otherwise is only used on normal MP4 + if (success)/*/ + if (!success) + { + if ((success = MP4Get3GPMetadata(getFileInfoMP4, "rate", &pVal_utf16))) + { + wchar_t *value = (wchar_t*)pVal_utf16; + if(value && *value) { + int rating = _wtoi(value); + + // keeps things limited to our range of 0-100 + if (rating >= 100) { + rating = 5; + } + // 1-100 case + else if (rating > 0 && rating < 100) { + rating = (rating /= 20); + // shift up by one rating when in next band + // 1-20 = 1, 21-40 = 2, 41-60 = 3, 61-80 = 4, 81-100 = 5 + rating += ((_wtoi(value) - (rating * 20)) > 0); + } + // otherwise just make sure and set zero + else { + rating = 0; + } + + StringCchPrintfW(dest, destlen, L"%u", rating); + MP4Free(pVal_utf16); + return 1; + } + } + } + else/**/ + { + char *value = (char*)pVal; + if(value && *value) { + int rating = atoi(value); + + // keeps things limited to our range of 0-100 + if (rating >= 100) { + rating = 5; + } + // 1-100 case + else if (rating > 0 && rating < 100) { + rating = (rating /= 20); + // shift up by one rating when in next band + // 1-20 = 1, 21-40 = 2, 41-60 = 3, 61-80 = 4, 81-100 = 5 + rating += ((atoi(value) - (rating * 20)) > 0); + } + // otherwise just make sure and set zero + else { + rating = 0; + } + + StringCchPrintfW(dest, destlen, L"%u", rating); + MP4Free(pVal); + return 1; + } + } + } + else if (KeywordMatch(data, "comment")) + success = MP4GetMetadataComment(getFileInfoMP4, &pVal); + else if (KeywordMatch(data, "albumartist")) + success = MP4GetMetadataAlbumArtist(getFileInfoMP4, &pVal); + else if (KeywordMatch(data, "gain")) + { + StringCchPrintfW(dest, destlen, L"%+-.2f dB", (float)log10f(GetGain(getFileInfoMP4, false))*20.0f); + success = true; + } + else if (KeywordMatch(data, "replaygain_track_gain")) + { + GetCustomMetadata(getFileInfoMP4, "replaygain_track_gain", dest, destlen); + success = true; + } + else if (KeywordMatch(data, "replaygain_album_gain")) + { + GetCustomMetadata(getFileInfoMP4, "replaygain_album_gain", dest, destlen); + success = true; + } + else if (KeywordMatch(data, "replaygain_track_peak")) + { + GetCustomMetadata(getFileInfoMP4, "replaygain_track_peak", dest, destlen); + success = true; + } + else if (KeywordMatch(data, "replaygain_album_peak")) + { + GetCustomMetadata(getFileInfoMP4, "replaygain_album_peak", dest, destlen); + success = true; + } + else if (KeywordMatch(data, "bpm")) + { + unsigned __int16 tempo = 0; + success = MP4GetMetadataTempo(getFileInfoMP4, &tempo); + if (success && tempo) + StringCchPrintf(dest, destlen, L"%u", tempo); + } + else if (KeywordMatch(data, "year")) + { + success = MP4GetMetadataYear(getFileInfoMP4, &pVal); + if (!success) + { + uint64_t val = 0; + success = MP4Get3GPMetadataInteger(getFileInfoMP4, "yrrc", &val); + if (success) + { + StringCchPrintf(dest, destlen, L"%I64u", val); + } + } + } + else if (KeywordMatch(data, "bitrate")) + { + uint32_t audio_bitrate = 0, video_bitrate = 0; + MP4TrackId track = GetAudioTrack(getFileInfoMP4); + if (track != MP4_INVALID_TRACK_ID) + audio_bitrate = MP4GetTrackBitRate(getFileInfoMP4, track) / 1000; + + track = GetVideoTrack(getFileInfoMP4); + if (track != MP4_INVALID_TRACK_ID) + video_bitrate = MP4GetTrackBitRate(getFileInfoMP4, track) / 1000; + + + if (audio_bitrate || video_bitrate) + StringCchPrintf(dest, destlen, L"%u", audio_bitrate+video_bitrate); + else + dest[0] = 0; + success = true; + } + else if (KeywordMatch(data, "height")) + { + MP4TrackId track = GetVideoTrack(getFileInfoMP4); + if (track != MP4_INVALID_TRACK_ID) + { + uint16_t height = MP4GetTrackVideoHeight(getFileInfoMP4, track); + if (height) + { + StringCchPrintf(dest, destlen, L"%u", height); + } + else + dest[0]=0; + success=true; + } + } + else if (KeywordMatch(data, "width")) + { + + MP4TrackId track = GetVideoTrack(getFileInfoMP4); + if (track != MP4_INVALID_TRACK_ID) + { + uint16_t width = MP4GetTrackVideoWidth(getFileInfoMP4, track); + if (width) + { + StringCchPrintf(dest, destlen, L"%u", width); + } + else + dest[0]=0; + success=true; + } + } + //else if(KeywordMatch(data,"srate")) wsprintf(dest,"%d",srate); + //else if(KeywordMatch(data,"stereo")) wsprintf(dest,"%d",is_stereo); + //else if(KeywordMatch(data,"vbr")) wsprintf(dest,"%d",is_vbr); + else if (KeywordMatch(data, "genre")) + { + success = MP4GetMetadataGenre(getFileInfoMP4, &pVal); + if (!success) + success = MP4Get3GPMetadata(getFileInfoMP4, "gnre", &pVal_utf16); + } + else if (KeywordMatch(data, "disc")) + { + unsigned __int16 dummy = 0, dummy2 = 0; + success = MP4GetMetadataDisk(getFileInfoMP4, &dummy, &dummy2); + if (success && dummy) + { + if (dummy2) + StringCchPrintf(dest, destlen, L"%u/%u", dummy, dummy2); + else + StringCchPrintf(dest, destlen, L"%u", dummy); + } + else + dest[0] = 0; + } + else if (KeywordMatch(data, "track")) + { + unsigned __int16 dummy = 0, dummy2 = 0; + success = MP4GetMetadataTrack(getFileInfoMP4, &dummy, &dummy2); + if (success && dummy) + { + if (dummy2) + StringCchPrintf(dest, destlen, L"%u/%u", dummy, dummy2); + else + StringCchPrintf(dest, destlen, L"%u", dummy); + } + else + dest[0] = 0; + } + else if (KeywordMatch(data, "length")) + { + /* TODO: use sample rate and number of samples from iTunSMPB to get a more exact length */ + //MP4TrackId track = GetAudioTrack(getFileInfoMP4); + //if (track != MP4_INVALID_TRACK_ID) + //{ + // int m_timescale = MP4GetTrackTimeScale(getFileInfoMP4, track); + // unsigned __int64 trackDuration = MP4GetTrackDuration(getFileInfoMP4, track); + // double m_length = (double)(__int64)trackDuration / (double)m_timescale; + // StringCchPrintf(dest, destlen, L"%d", (int)(m_length*1000)); + // success = true; + //} + + double lengthAudio = 0; + double lengthVideo = 0; + double m_length = 0; + MP4TrackId audio_track = GetAudioTrack(getFileInfoMP4); + if (audio_track != -1) + { + int timescale = MP4GetTrackTimeScale(getFileInfoMP4, audio_track); + unsigned __int64 trackDuration = MP4GetTrackDuration(getFileInfoMP4, audio_track); + lengthAudio = (double)(__int64)trackDuration / (double)timescale; + } + MP4TrackId video_track = GetVideoTrack(getFileInfoMP4); + if (video_track != -1) + { + int timescale = MP4GetTrackTimeScale(getFileInfoMP4, video_track); + unsigned __int64 trackDuration = MP4GetTrackDuration(getFileInfoMP4, video_track); + lengthVideo = (double)(__int64)trackDuration / (double)timescale; + } + m_length = (max(lengthAudio, lengthVideo)); + StringCchPrintf(dest, destlen, L"%d", (int)(m_length * 1000)); + success = true; + + } + else if (KeywordMatch(data, "tool")) + success = MP4GetMetadataTool(getFileInfoMP4, &pVal); + else if (KeywordMatch(data, "composer")) + success = MP4GetMetadataWriter(getFileInfoMP4, &pVal); + else if (KeywordMatch(data, "category")) + success = MP4GetMetadataGrouping(getFileInfoMP4, &pVal); + else if (KeywordMatch(data, "GracenoteFileID")) + { + GetCustomMetadata(getFileInfoMP4, "gnid", dest, destlen); + success = true; + } + else if (KeywordMatch(data, "GracenoteExtData")) + { + GetCustomMetadata(getFileInfoMP4, "gnxd", dest, destlen); + success = true; + } + else if (KeywordMatch(data, "publisher")) + { + GetCustomMetadata(getFileInfoMP4, "publisher", dest, destlen, "com.nullsoft.winamp"); + success = true; + } + else if (KeywordMatch(data, "pregap")) + { + wchar_t gap_data[128] = {0}; + if (GetCustomMetadata(getFileInfoMP4, "iTunSMPB", gap_data, 128) && gap_data[0]) + { + wchar_t *itr = IncSafe(gap_data, 9); + + unsigned int pregap = wcstoul(itr, 0, 16); + StringCchPrintfW(dest, destlen, L"%u",pregap); + success=true; + } + } + else if (KeywordMatch(data, "postgap")) + { + wchar_t gap_data[128] = {0}; + if (GetCustomMetadata(getFileInfoMP4, "iTunSMPB", gap_data, 128) && gap_data[0]) + { + wchar_t *itr = IncSafe(gap_data, 18); + + unsigned int postgap = wcstoul(itr, 0, 16); + StringCchPrintfW(dest, destlen, L"%u",postgap); + success=true; + } + } + else if (KeywordMatch(data, "numsamples")) + { + wchar_t gap_data[128] = {0}; + if (GetCustomMetadata(getFileInfoMP4, "iTunSMPB", gap_data, 128) && gap_data[0]) + { + wchar_t *itr = IncSafe(gap_data,27); + + unsigned __int64 numsamples = _wcstoui64(itr, 0, 16); + StringCchPrintfW(dest, destlen, L"%I64u", numsamples); + success=true; + } + } + else if (KeywordMatch(data, "formatinformation")) + { + MP4TrackId track = GetAudioTrack(getFileInfoMP4); + if (track != MP4_INVALID_TRACK_ID) + { + char *track_type = MP4PrintAudioInfo(getFileInfoMP4, track); + if (track_type) + { + uint32_t bitrate = MP4GetTrackBitRate(getFileInfoMP4, track); + StringCchPrintfEx(dest, destlen, &dest, &destlen, 0, WASABI_API_LNGSTRINGW(IDS_AUDIO_INFO), track_type, (bitrate + 500) / 1000); + MP4Free(track_type); + } + } + + track = GetVideoTrack(getFileInfoMP4); + if (track != MP4_INVALID_TRACK_ID) + { + char *track_type = MP4PrintVideoInfo(getFileInfoMP4, track); + if (track_type) + { + uint32_t bitrate = MP4GetTrackBitRate(getFileInfoMP4, track); + StringCchPrintfEx(dest, destlen, &dest, &destlen, 0, WASABI_API_LNGSTRINGW(IDS_VIDEO_INFO), track_type, (bitrate + 500) / 1000); + u_int16_t width = MP4GetTrackVideoWidth(getFileInfoMP4, track); + u_int16_t height = MP4GetTrackVideoHeight(getFileInfoMP4, track); + if (width && height) + { + // TODO: framerate, but the MP4GetTrackVideoFrameRate method just guesses based on duration and samples + StringCchPrintfEx(dest, destlen, &dest, &destlen, 0, L"\t%ux%u\n", width, height); + } + MP4Free(track_type); + } + } + + success=true; + } + else // don't understand the name + { +// MP4Close(getFileInfoMP4); + return 0; + } + + if (pVal) + { + MultiByteToWideCharSZ(CP_UTF8, 0, pVal, -1, dest, destlen); + MP4Free(pVal); + } + else if (pVal_utf16) + { + StringCchCopyW(dest, destlen, (const wchar_t *)pVal_utf16); + MP4Free(pVal_utf16); + } + + if (!success) + dest[0] = 0; + + //MP4Close(getFileInfoMP4); + + return 1; + } + + + static MP4FileHandle setFileInfoMP4 = 0; + static int m_last_err = 0; + static wchar_t m_last_ext_fn[MAX_PATH] = L""; + static void *setFileInfoReader = 0; +static bool setFile3GP=false; + +/*static int SetExtendedInfo3GP(const char *data, const wchar_t *val) +{ + if (KeywordMatch(data, "title")) + { + if (val && *val) MP4Set3GPMetadata(setFileInfoMP4, "titl", (const uint16_t *)val); + else MP4Delete3GPMetadata(setFileInfoMP4, "titl"); + } + else if (KeywordMatch(data, "album")) + { + if (val && *val) MP4Set3GPMetadata(setFileInfoMP4, "albm", (const uint16_t *)val); + else MP4Delete3GPMetadata(setFileInfoMP4, "albm"); + } + else if (KeywordMatch(data, "genre")) + { + if (val && *val) MP4Set3GPMetadata(setFileInfoMP4, "gnre", (const uint16_t *)val); + else MP4Delete3GPMetadata(setFileInfoMP4, "gnre"); + } + else if (KeywordMatch(data, "artist")) + { + if (val && *val) MP4Set3GPMetadata(setFileInfoMP4, "perf", (const uint16_t *)val); + else MP4Delete3GPMetadata(setFileInfoMP4, "perf"); + } + else if (KeywordMatch(data, "year")) + { + if (val && *val) MP4Set3GPMetadataInteger(setFileInfoMP4, "yrrc", _wtoi64(val)); + else MP4Delete3GPMetadata(setFileInfoMP4, "yrrc"); + } + else + return 0; + + return 1; +}*/ + +static int SetExtendedInfoMP4(const char *data, const wchar_t *val) +{ + if (KeywordMatch(data, "title")) + { + if (val && *val) MP4SetMetadataName(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataName(setFileInfoMP4); + } + else if (KeywordMatch(data, "album")) + { + if (val && *val) + MP4SetMetadataAlbum(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataAlbum(setFileInfoMP4); + } + else if (KeywordMatch(data, "artist")) + { + if (val && *val) + MP4SetMetadataArtist(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataArtist(setFileInfoMP4); + } + else if (KeywordMatch(data, "rating")) + { + if (val && *val) + { + char temp[128] = {0}; + StringCchPrintfA(temp, 128, "%u", _wtoi(val) * 20); + MP4SetMetadataRating(setFileInfoMP4, temp); + } + else MP4DeleteMetadataRating(setFileInfoMP4); + } + else if (KeywordMatch(data, "comment")) + { + if (val && *val) + MP4SetMetadataComment(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataComment(setFileInfoMP4); + } + else if (KeywordMatch(data, "year")) + { + if (val && *val) + MP4SetMetadataYear(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataYear(setFileInfoMP4); + } + else if (KeywordMatch(data, "genre")) + { + if (val && *val) + MP4SetMetadataGenre(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataGenre(setFileInfoMP4); + } + else if (KeywordMatch(data, "albumartist")) + { + if (val && *val) + MP4SetMetadataAlbumArtist(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataAlbumArtist(setFileInfoMP4); + } + else if (KeywordMatch(data, "replaygain_track_gain")) + { + if (val && *val) + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_gain", "com.apple.iTunes"); + AutoChar utf8(val, CP_UTF8); + MP4SetMetadataFreeForm(setFileInfoMP4, "replaygain_track_gain", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "org.hydrogenaudio.replaygain"); + } + else + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_gain", "com.apple.iTunes"); + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_gain", "org.hydrogenaudio.replaygain"); + } + } + else if (KeywordMatch(data, "replaygain_track_peak")) + { + if (val && *val) + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_peak", "com.apple.iTunes"); + AutoChar utf8(val, CP_UTF8); + MP4SetMetadataFreeForm(setFileInfoMP4, "replaygain_track_peak", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "org.hydrogenaudio.replaygain"); + } + else + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_peak", "com.apple.iTunes"); + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_peak", "org.hydrogenaudio.replaygain"); + } + } + else if (KeywordMatch(data, "replaygain_album_gain")) + { + if (val && *val) + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_gain", "com.apple.iTunes"); + AutoChar utf8(val, CP_UTF8); + MP4SetMetadataFreeForm(setFileInfoMP4, "replaygain_album_gain", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "org.hydrogenaudio.replaygain"); + } + else + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_gain", "com.apple.iTunes"); + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_gain", "org.hydrogenaudio.replaygain"); + } + } + else if (KeywordMatch(data, "replaygain_album_peak")) + { + if (val && *val) + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_peak", "com.apple.iTunes"); + AutoChar utf8(val, CP_UTF8); + MP4SetMetadataFreeForm(setFileInfoMP4, "replaygain_album_peak", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "org.hydrogenaudio.replaygain"); + } + else + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_peak", "com.apple.iTunes"); + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_peak", "org.hydrogenaudio.replaygain"); + } + } + else if (KeywordMatch(data, "track")) + { + int track = _wtoi(val); + if (track) + { + int tracks = 0; + const wchar_t *_tracks = wcschr(val, L'/'); + if (_tracks) tracks = _wtoi(_tracks + 1); + MP4SetMetadataTrack(setFileInfoMP4, track, tracks); + } + else + MP4DeleteMetadataTrack(setFileInfoMP4); + } + else if (KeywordMatch(data, "disc")) + { + int disc = _wtoi(val); + if (disc) + { + int discs = 0; + const wchar_t *_discs = wcschr(val, L'/'); + if (_discs) discs = _wtoi(_discs + 1); + MP4SetMetadataDisk(setFileInfoMP4, disc, discs); + } + else + MP4DeleteMetadataDisk(setFileInfoMP4); + } + else if (KeywordMatch(data, "tool")) + { + if (val && *val) + MP4SetMetadataTool(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataTool(setFileInfoMP4); + } + else if (KeywordMatch(data, "bpm")) + { + if (val && *val && *val != '0') + MP4SetMetadataTempo(setFileInfoMP4, _wtoi(val)); + else + MP4DeleteMetadataTempo(setFileInfoMP4); + } + else if (KeywordMatch(data, "composer")) + { + if (val && *val) + MP4SetMetadataWriter(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataWriter(setFileInfoMP4); + } + else if (KeywordMatch(data, "GracenoteFileID")) + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "gnid", "com.apple.iTunes"); // delete obselete metadata storage scheme + if (val && *val) + { + AutoChar utf8(val, CP_UTF8); + MP4SetMetadataFreeForm(setFileInfoMP4, "gnid", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "com.gracenote.cddb"); + } + else + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "gnid", "com.gracenote.cddb"); + } + } + else if (KeywordMatch(data, "GracenoteExtData")) + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "gnxd", "com.apple.iTunes");// delete obselete metadata storage scheme + if (val && *val) + { + AutoChar utf8(val, CP_UTF8); + MP4SetMetadataFreeForm(setFileInfoMP4, "gnxd", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "com.gracenote.cddb"); + } + else + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "gnxd", "com.gracenote.cddb"); + } + } + else if (KeywordMatch(data, "publisher")) + { + if (val && *val) + { + AutoChar utf8(val, CP_UTF8); + MP4SetMetadataFreeForm(setFileInfoMP4, "publisher", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "com.nullsoft.winamp"); + } + else + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "publisher", "com.nullsoft.winamp"); + } + } + else if (KeywordMatch(data, "category")) + { + if (val && *val) + MP4SetMetadataGrouping(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataGrouping(setFileInfoMP4); + } + else + return 0; + + return 1; +} + + static Stopper stopper; + + __declspec( dllexport ) int winampSetExtendedFileInfoW(const wchar_t *fn, const char *data, const wchar_t *val) + { + if (!setFileInfoMP4 || lstrcmpi(m_last_ext_fn, fn)) // different file than last time? + { + m_last_err = 0; + lstrcpyn(m_last_ext_fn, fn, MAX_PATH); + + if (setFileInfoMP4) + MP4Close(setFileInfoMP4); + + /* TODO: make MP4ModifyEx so we can use this + if (setFileInfoReader) + DestroyUnicodeReader(setFileInfoReader); + + setFileInfoReader = CreateUnicodeReader(fn); + if (!setFileInfoReader) + return 0; + */ + + if (!_wcsicmp(m_last_ext_fn, lastfn)) + stopper.Stop(); + + setFileInfoMP4 = MP4Modify(m_last_ext_fn, 0, 0); + if (!setFileInfoMP4) + { + stopper.ChangeTracking(1); // enable stats updating + //DestroyUnicodeReader(setFileInfoReader); + m_last_err = 1; + return 0; + } + + setFile3GP = false; + const char *brand=0; + if (MP4GetStringProperty(setFileInfoMP4, "ftyp.majorBrand", &brand)) + { + if (!strncmp(brand, "3gp6", 4)) + setFile3GP=true; + } + } + /* + if (setFile3GP) + return SetExtendedInfo3GP(data, val); + else*/ + return SetExtendedInfoMP4(data, val); + + } + + __declspec(dllexport) int winampWriteExtendedFileInfo() + { + int err = m_last_err; + m_last_err = 0; + + // clear this as well, so dirty info isn't read back + if (getFileInfoMP4) + MP4Close(getFileInfoMP4); + getFileInfoMP4=0; + + if (getFileInfoReader) + DestroyUnicodeReader(getFileInfoReader); + getFileInfoReader=0; + + if (setFileInfoMP4) + { + MP4Close(setFileInfoMP4); + MP4Optimize(m_last_ext_fn, NULL, 0); //put the fields at the beginning of the file + setFileInfoMP4 = 0; + m_last_ext_fn[0] = 0; + stopper.Play(); + } + + // update last modified so we're not re-queried on our own updates + UpdateFileTimeChanged(m_last_ext_fn); + + return !err; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/ExtendedRead.cpp b/Src/Plugins/Input/in_mp4/ExtendedRead.cpp new file mode 100644 index 00000000..137c8a81 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/ExtendedRead.cpp @@ -0,0 +1,239 @@ +#include <stddef.h> +#include "main.h" +#include "mpeg4audio.h" +#include "api__in_mp4.h" +#include <api/service/waservicefactory.h> +#include <assert.h> +#include "../nu/GaplessRingBuffer.h" +#include "virtualIO.h" +struct ExtendedRead +{ + ExtendedRead() : mp4(0), mp4track(0), sampleId(1), + samples(0), audio(0), audioFactory(0), frameSize(0), reader(0), + sample_rate(0), timescale(0), max_sample_size(0), sample_buffer(0), decode_buffer(0) + {} + ~ExtendedRead() + { + if (mp4) MP4Close(mp4); mp4 = 0; + if (reader) DestroyUnicodeReader(reader); reader=0; + if (audio) + { + audio->Close(); + audioFactory->releaseInterface(audio); + } + audioFactory = 0; + audio = 0; + free(sample_buffer); + sample_buffer=0; + free(decode_buffer); + } + + bool Open(const wchar_t *fn, int *size, int *bps, int *nch, int *srate, bool useFloat); + MP4AudioDecoder *audio; + MP4FileHandle mp4; + MP4TrackId mp4track; + MP4SampleId sampleId, samples; + GaplessRingBuffer ringBuffer; + void *reader; + waServiceFactory *audioFactory; + size_t frameSize; + unsigned int sample_rate; + uint32_t timescale; + uint32_t max_sample_size; + void *sample_buffer; + uint8_t *decode_buffer; +}; + +bool ExtendedRead::Open(const wchar_t *fn, int *size, int *bps, int *nch, int *srate, bool useFloat) +{ + unsigned __int32 pregap, postgap; + int numBits = *bps; + int numChannels = *nch; + + reader = CreateUnicodeReader(fn); + if (!reader) + return false; + mp4 = MP4ReadEx(fn, reader, &UnicodeIO); + if (!mp4) + { + DestroyUnicodeReader(reader); + return false; + } + mp4track = GetAudioTrack(mp4); + if (mp4track == MP4_INVALID_TRACK_ID) return false; + if (!CreateDecoder(mp4, mp4track, audio, audioFactory)) + return false; + + int result; + result = audio->OpenMP4(mp4, mp4track, numBits, numChannels, useFloat); + + if (result != MP4_SUCCESS) + return false; + + + GetGaps(mp4, pregap, postgap); + ConfigureDecoderASC(mp4, mp4track, audio); + + timescale = MP4GetTrackTimeScale(mp4, mp4track); + + samples = MP4GetTrackNumberOfSamples(mp4, mp4track); + MP4SampleId sample = 0; + +// some codecs require a frame or two to get decoded. so we'll go until GetOutputProperties is valid + for (MP4SampleId sample = 1;sample <= samples; sample++) + { + int ret; + if (useFloat) + { + bool verifyFloat = false; + ret = audio->GetOutputPropertiesEx(&sample_rate, reinterpret_cast<unsigned int *>(nch), reinterpret_cast<unsigned int *>(bps), &verifyFloat); + if (ret == MP4_SUCCESS && !verifyFloat) + return false; + } + else + { + ret = audio->GetOutputProperties(&sample_rate, reinterpret_cast<unsigned int *>(nch), reinterpret_cast<unsigned int *>(bps)); + } + if (ret == MP4_SUCCESS) + { + MP4Duration duration = MP4GetTrackDuration(mp4, mp4track); + *srate = sample_rate; + frameSize = (*nch) * (*bps / 8); + size_t outputFrameSize; + *size = duration * frameSize; + if (audio->OutputFrameSize(&outputFrameSize) == MP4_SUCCESS) + { + + } + else + { + outputFrameSize = 65536; // err on the side of caution + } + + decode_buffer = (uint8_t *)malloc(outputFrameSize*frameSize); + ringBuffer.Initialize(outputFrameSize, *bps, *nch, pregap, postgap); + + max_sample_size = MP4GetTrackMaxSampleSize(mp4, mp4track); + sample_buffer = malloc(max_sample_size); + if (sample != 1) { + audio->Flush(); + } + return true; + } + + unsigned char *buffer = NULL; + unsigned __int32 buffer_size = 0; + if (MP4ReadSample(mp4, mp4track, sample, (unsigned __int8 **)&buffer, &buffer_size)) + { + unsigned char tempBuf[65536]; + size_t outSize = 65536; + int err = audio->DecodeSample(buffer, buffer_size, tempBuf, &outSize); + MP4Free(buffer); + + if (err != MP4_SUCCESS) + continue; + } + } + + return false; +} +extern "C" +{ + //returns handle!=0 if successful, 0 if error + //size will return the final nb of bytes written to the output, -1 if unknown + __declspec( dllexport ) intptr_t winampGetExtendedRead_openW(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) + { + ExtendedRead *ext = new ExtendedRead; + if (ext->Open(fn, size, bps, nch, srate, false)) + return reinterpret_cast<intptr_t>(ext); + + delete ext; + return 0; + } + + //returns handle!=0 if successful, 0 if error + //size will return the final nb of bytes written to the output, -1 if unknown + __declspec( dllexport ) intptr_t winampGetExtendedRead_openW_float(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) + { + ExtendedRead *ext = new ExtendedRead; + if (ext->Open(fn, size, bps, nch, srate, true)) + return reinterpret_cast<intptr_t>(ext); + + delete ext; + return 0; + } + + //returns nb of bytes read. -1 if read error (like CD ejected). if (ret == 0), EOF is assumed + __declspec( dllexport ) intptr_t winampGetExtendedRead_getData(intptr_t handle, char *dest, int len, volatile int *killswitch) + { + ExtendedRead *ext = (ExtendedRead *)handle; + + int bytesCopied = 0; + int skip = 0; + len -= (len % ext->frameSize); // round down to the nearest whole frame size + while (len && !*killswitch) + { + size_t copySize = ext->ringBuffer.Read(dest, len); + len -= copySize; + dest += copySize; + bytesCopied += copySize; + + if (ext->ringBuffer.Empty()) + { + size_t outSize = 0; + MP4Duration offset=0,duration=INT_MAX; + if (ext->sampleId <= ext->samples) { + unsigned char *buffer = (unsigned char *)ext->sample_buffer; + unsigned __int32 buffer_size = ext->max_sample_size; + MP4ReadSample(ext->mp4, ext->mp4track, ext->sampleId++, (unsigned __int8 **) & buffer, &buffer_size, 0, &duration, &offset); + + ext->audio->DecodeSample(buffer, buffer_size, ext->decode_buffer, &outSize); // TODO error check + } else { +#if 0 // TODO Drain decode + ext->audio->DecodeSample(0, 0, decode_buffer, &outSize); // TODO Drain method? +#else +#endif + return bytesCopied; + } + + // convert to the track timescale for purposes of duration/offset/gap stuff + int outSamples = MulDiv(outSize, ext->timescale, ext->sample_rate * ext->frameSize); + + if (offset > 0) + outSamples -= min(outSamples, offset); + + if (outSamples > duration) + outSamples = duration; + + // convert back to sample rate timescale + outSize = MulDiv(ext->sample_rate * ext->frameSize, outSamples, ext->timescale); + ext->ringBuffer.Write(ext->decode_buffer+offset*ext->frameSize, outSize); + } + } + return bytesCopied; + } + + // return nonzero on success, zero on failure. + __declspec( dllexport ) int winampGetExtendedRead_setTime(intptr_t handle, int millisecs) + { + ExtendedRead *ext = (ExtendedRead *)handle; + + MP4Duration duration = MP4ConvertToTrackDuration(ext->mp4, ext->mp4track, millisecs, MP4_MSECS_TIME_SCALE); + if(duration == MP4_INVALID_DURATION) return 0; + + MP4SampleId newSampleId = MP4GetSampleIdFromTime(ext->mp4, ext->mp4track, duration); + if(newSampleId > ext->samples) return 0; + + ext->sampleId = newSampleId; + ext->audio->Flush(); + // ext->bufferUsed=0; + ext->ringBuffer.Reset(); + return 1; + } + + __declspec( dllexport ) void winampGetExtendedRead_close(intptr_t handle) + { + ExtendedRead *ext = (ExtendedRead *)handle; + delete ext; + } +} diff --git a/Src/Plugins/Input/in_mp4/Main.cpp b/Src/Plugins/Input/in_mp4/Main.cpp new file mode 100644 index 00000000..59ca1a89 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/Main.cpp @@ -0,0 +1,723 @@ +#define PLUGIN_VERSION L"2.70" +#include "Main.h" +#include <windows.h> +#include <stdio.h> +#include <locale.h> +#include "resource.h" +#include "../Winamp/in2.h" +#include "../Winamp/wa_ipc.h" +#include "../nu/AutoChar.h" +#include "api__in_mp4.h" + +#include <api/service/waservicefactory.h> + +#pragma warning(disable:4786) +#include "mpeg4audio.h" + +#include <shlwapi.h> +#include <malloc.h> +#include "VirtualIO.h" +#include "AlbumArt.h" +#include <assert.h> +#include "../in_wmvdrm/Remaining.h" +#include "VideoThread.h" +#include "RawMediaReader.h" +#include "../nu/Singleton.h" +#include <strsafe.h> + +Remaining remaining; +nu::VideoClock video_clock; + +AlbumArtFactory albumArtFactory; + +wchar_t m_ini[MAX_PATH] = {0}; +int infoDlg(const wchar_t *fn, HWND hwnd); +#define WM_WA_IPC WM_USER +#define WM_WA_MPEG_EOF WM_USER+2 + +HANDLE hThread; +static DWORD WINAPI playProc(LPVOID lpParameter); +static int paused, m_kill; +HANDLE killEvent, seekEvent, pauseEvent; +static int m_opened; + + +static RawMediaReaderService raw_media_reader_service; +static SingletonServiceFactory<svc_raw_media_reader, RawMediaReaderService> raw_factory; +static void stop(); + +// wasabi based services for localisation support +api_language *WASABI_API_LNG = 0; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; + +api_config *AGAVE_API_CONFIG = 0; +api_memmgr *WASABI_API_MEMMGR = 0; +api_application *WASABI_API_APP = 0; + +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_MPEG4_AUDIO_DECODER_OLD,text,1024); + StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), + mod.description, TEXT(__DATE__)); + DoAboutMessageBox(hwndParent,text,message); +} + +static const wchar_t defaultExtensions_nonpro[] = {L"M4A;MP4"}; +static const wchar_t defaultExtensions_pro[] = {L"M4A;MP4;M4V"}; +const wchar_t *defaultExtensions = defaultExtensions_pro; + + +// the return pointer has been malloc'd. Use free() when you are done. +char *BuildExtensions(const char *extensions) +{ + char name[64] = {0}; + WASABI_API_LNGSTRING_BUF(IDS_MP4_FILE,name,64); + size_t length = strlen(extensions) + 1 + strlen(name) + 2; + char *newExt = (char *)calloc(length, sizeof(char)); + char *ret = newExt; // save because we modify newExt + + // copy extensions + StringCchCopyExA(newExt, length, extensions, &newExt, &length, 0); + newExt++; + length--; + + // copy description + StringCchCopyExA(newExt, length, name, &newExt, &length, 0); + newExt++; + length--; + + // double null terminate + assert(length == 1); + *newExt = 0; + + return ret; +} + +int init() +{ + if (!IsWindow(mod.hMainWindow)) + return IN_INIT_FAILURE; + + killEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + seekEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + pauseEvent = CreateEvent(NULL, TRUE, TRUE, NULL); + + mod.service->service_register(&albumArtFactory); + raw_factory.Register(mod.service, &raw_media_reader_service); + + waServiceFactory *sf = mod.service->service_getServiceByGuid(AgaveConfigGUID); + if (sf) + AGAVE_API_CONFIG = (api_config *)sf->getInterface(); + sf = mod.service->service_getServiceByGuid(applicationApiServiceGuid); + if (sf) + WASABI_API_APP = (api_application *)sf->getInterface(); + sf = mod.service->service_getServiceByGuid(memMgrApiServiceGuid); + if (sf) + WASABI_API_MEMMGR = (api_memmgr *)sf->getInterface(); + sf = mod.service->service_getServiceByGuid(DownloadManagerGUID); + if (sf) + WAC_API_DOWNLOADMANAGER = (api_downloadManager *)sf->getInterface(); + sf = mod.service->service_getServiceByGuid(ThreadPoolGUID); + if (sf) + WASABI_API_THREADPOOL = (api_threadpool *)sf->getInterface(); + + // loader so that we can get the localisation service api for use + sf = mod.service->service_getServiceByGuid(languageApiGUID); + if (sf) WASABI_API_LNG = reinterpret_cast<api_language*>(sf->getInterface()); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(mod.hDllInstance,InMp4LangGUID); + + static wchar_t szDescription[256]; + StringCchPrintfW(szDescription,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_MPEG4_AUDIO_DECODER),PLUGIN_VERSION); + mod.description = (char*)szDescription; + + const wchar_t *inipath = (wchar_t *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETINIDIRECTORYW); + + PathCombineW(m_ini, inipath, L"Plugins"); + CreateDirectoryW(m_ini, NULL); + PathAppendW(m_ini, L"in_mp4.ini"); + + wchar_t exts[1024] = {0}; + GetPrivateProfileStringW(L"in_mp4", L"extensionlist", defaultExtensions, exts, 1024, m_ini); + mod.FileExtensions = BuildExtensions(AutoChar(exts)); + return IN_INIT_SUCCESS; +} + +void quit() +{ + CloseHandle(killEvent); + CloseHandle(seekEvent); + + raw_factory.Deregister(mod.service); + waServiceFactory *sf = mod.service->service_getServiceByGuid(AgaveConfigGUID); + if (sf) + sf->releaseInterface(AGAVE_API_CONFIG); + sf = mod.service->service_getServiceByGuid(applicationApiServiceGuid); + if (sf) + sf->releaseInterface(WASABI_API_APP); + sf = mod.service->service_getServiceByGuid(DownloadManagerGUID); + if (sf) + sf->releaseInterface( WAC_API_DOWNLOADMANAGER ); + mod.service->service_deregister(&albumArtFactory); + + free(mod.FileExtensions); +} + +int isourfile(const wchar_t *fn) +{ + return 0; +} + +void config(HWND hwndParent); + +void setoutputtime(int time_in_ms) +{ + m_needseek = time_in_ms; + SetEvent(seekEvent); +} + +MP4TrackId GetVideoTrack(MP4FileHandle infile) +{ + int numTracks = MP4GetNumberOfTracks(infile, NULL, /* subType */ 0); + + for (int i = 0; i < numTracks; i++) + { + MP4TrackId trackId = MP4FindTrackId(infile, i, NULL, /* subType */ 0); + const char* trackType = MP4GetTrackType(infile, trackId); + + if (!lstrcmpA(trackType, MP4_VIDEO_TRACK_TYPE)) + return trackId; + } + + /* can't decode this */ + return MP4_INVALID_TRACK_ID; +} + +MP4TrackId GetAudioTrack(MP4FileHandle infile) +{ + int ret = MP4_INVALID_TRACK_ID; + __try + { + /* find AAC track */ + int numTracks = MP4GetNumberOfTracks(infile, NULL, /* subType */ 0); + + for (int i = 0; i < numTracks; i++) + { + MP4TrackId trackId = MP4FindTrackId(infile, i, NULL, /* subType */ 0); + if (trackId != MP4_INVALID_TRACK_ID) + { + const char* trackType = MP4GetTrackType(infile, trackId); + + if (trackType && !lstrcmpA(trackType, MP4_AUDIO_TRACK_TYPE)) + return trackId; + } + + } + + /* can't decode this */ + return MP4_INVALID_TRACK_ID; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + return MP4_INVALID_TRACK_ID; + } + return ret; +} + +MP4SampleId numSamples, numVideoSamples; +MP4FileHandle MP4hFile; +MP4TrackId audio_track, video_track; + +double m_length; +volatile int m_needseek = -1; +unsigned int audio_srate, audio_nch, audio_bps, audio_bitrate=0; +unsigned int video_bitrate=0; + +wchar_t lastfn[MAX_PATH*4] = L""; + +MP4AudioDecoder *audio = 0; +waServiceFactory *audioFactory = 0, *videoFactory = 0; +MP4VideoDecoder *video = 0; + +uint32_t m_timescale = 0, m_video_timescale = 0; +static void *reader = 0; +bool audio_chunk = false; +enum +{ + READER_UNICODE=0, + READER_HTTP=1, +}; +int reader_type=READER_UNICODE; + +bool open_mp4(const wchar_t *fn) +{ + audio = 0; + video = 0; + if (!_wcsnicmp(fn, L"http://", 7) || !_wcsnicmp(fn, L"https://", 8)) + { + reader = CreateReader(fn, killEvent); + reader_type=READER_HTTP; + MP4hFile = MP4ReadEx(fn, reader, &HTTPIO); + } + else + { + reader = CreateUnicodeReader(fn); + if (!reader) + return false; + reader_type=READER_UNICODE; + MP4hFile = MP4ReadEx(fn, reader, &UnicodeIO); + } + + if (!MP4hFile) + { + return false; + } + + m_opened = 1; + + unsigned int output_bits = AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"bits", 16); + if (output_bits >= 24) + output_bits = 24; + else + output_bits = 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; + + audio_track = GetAudioTrack(MP4hFile); + if (audio_track == MP4_INVALID_TRACK_ID || !CreateDecoder(MP4hFile, audio_track, audio, audioFactory) || audio->OpenMP4(MP4hFile, audio_track, output_bits, max_channels, false) != MP4_SUCCESS) + { + audio = 0; + video_clock.Start(); + } + + video_track = GetVideoTrack(MP4hFile); + if (video_track != MP4_INVALID_TRACK_ID) + { + CreateVideoDecoder(MP4hFile, video_track, video, videoFactory); + if (video) + video->Open(MP4hFile, video_track); + } + else + video=0; + + if (!audio && !video) + { + return false; + } + + numVideoSamples = MP4GetTrackNumberOfSamples(MP4hFile, video_track); + m_video_timescale = MP4GetTrackTimeScale(MP4hFile, video_track); + unsigned __int64 trackDuration; + + double lengthAudio = 0; + double lengthVideo = 0; + + if (audio_track != MP4_INVALID_TRACK_ID) + { + if (audio) + { + ConfigureDecoderASC(MP4hFile, audio_track, audio); + audio_chunk = !!audio->RequireChunks(); + } + else + audio_chunk = false; + + numSamples = audio_chunk?MP4GetTrackNumberOfChunks(MP4hFile, audio_track):MP4GetTrackNumberOfSamples(MP4hFile, audio_track); + m_timescale = MP4GetTrackTimeScale(MP4hFile, audio_track); + trackDuration = MP4GetTrackDuration(MP4hFile, audio_track); + lengthAudio = (double)(__int64)trackDuration / (double)m_timescale; + } + else + { + numSamples = numVideoSamples; + trackDuration = MP4GetTrackDuration(MP4hFile, video_track); + lengthVideo = (double)(__int64)trackDuration / (double)m_video_timescale; + } + + /* length in Sec. */ + m_length = max(lengthAudio, lengthVideo); //(double)(__int64)trackDuration / (double)m_timescale; + + audio_bitrate = MP4GetTrackBitRate(MP4hFile, audio_track) / 1000; + if (video) + video_bitrate = MP4GetTrackBitRate(MP4hFile, video_track) / 1000; + else + video_bitrate = 0; + + if (audio && audio->SetGain(GetGain(MP4hFile)) == MP4_SUCCESS) + mod.UsesOutputPlug |= 8; + else + mod.UsesOutputPlug &= ~8; + + return true; +} + +int play(const wchar_t *fn) +{ + video_clock.Reset(); + if (!videoOutput) // grab this now while we're on the main thread + videoOutput = (IVideoOutput *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GET_IVIDEOOUTPUT); + audio = 0; + video = 0; + paused = 0; + m_kill = 0; + ResetEvent(killEvent); + m_length = 0; + ResetEvent(seekEvent); + m_needseek = -1; + SetEvent(pauseEvent); + if (m_force_seek != -1) + { + setoutputtime(m_force_seek); + } + m_opened = 0; + + lstrcpynW(lastfn, fn, MAX_PATH*4); + + DWORD thread_id; + HANDLE threadCreatedEvent = CreateEvent(0, FALSE, FALSE, 0); + hThread = CreateThread(NULL, NULL, PlayProc, (LPVOID)threadCreatedEvent, NULL, &thread_id); + SetThreadPriority(hThread, AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + WaitForSingleObject(threadCreatedEvent, INFINITE); + CloseHandle(threadCreatedEvent); + + return 0; +} + +static inline wchar_t *IncSafe(wchar_t *val, int x) +{ + while (x--) + { + if (*val) + val++; + } + + return val; +} + +void GetGaps(MP4FileHandle mp4, unsigned __int32 &pre, unsigned __int32 &post) +{ + wchar_t gap_data[128] = {0}; + if (GetCustomMetadata(mp4, "iTunSMPB", gap_data, 128) && gap_data[0]) + { + wchar_t *itr = IncSafe(gap_data, 9); + + pre = wcstoul(itr, 0, 16); + + itr = IncSafe(itr, 9); + post = wcstoul(itr, 0, 16); + + // don't care about total number of samples, really + /* + itr+=9; + unsigned int numSamples = wcstoul(itr, 0, 16);*/ + + } + else + { + pre = 0; + post = 0; + } +} + +float GetGain(MP4FileHandle mp4, bool allowDefault) +{ + if (AGAVE_API_CONFIG && AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false)) + { + float dB = 0, peak = 1.0f; + wchar_t gain[128] = L"", peakVal[128] = L""; + _locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale(); + + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_source", 0)) + { + case 0: // track + if ((!GetCustomMetadata(mp4, "replaygain_track_gain", gain, 128) || !gain[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + GetCustomMetadata(mp4, "replaygain_album_gain", gain, 128); + + if ((!GetCustomMetadata(mp4, "replaygain_track_peak", peakVal, 128) || !peakVal[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + GetCustomMetadata(mp4, "replaygain_album_peak", peakVal, 128); + break; + case 1: + if ((!GetCustomMetadata(mp4, "replaygain_album_gain", gain, 128) || !gain[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + GetCustomMetadata(mp4, "replaygain_track_gain", gain, 128); + + if ((!GetCustomMetadata(mp4, "replaygain_album_peak", peakVal, 128) || !peakVal[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + GetCustomMetadata(mp4, "replaygain_track_peak", peakVal, 128); + break; + } + + if (gain[0]) + { + if (gain[0] == L'+') + dB = (float)_wtof_l(&gain[1],C_locale); + else + dB = (float)_wtof_l(gain,C_locale); + } + else if (allowDefault) + { + dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0); + return powf(10.0f, dB / 20.0f); + } + + if (peakVal[0]) + { + peak = (float)_wtof_l(peakVal,C_locale); + } + + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_mode", 1)) + { + case 0: // apply gain + return powf(10.0f, dB / 20.0f); + case 1: // apply gain, but don't clip + return min(powf(10.0f, dB / 20.0f), 1.0f / peak); + case 2: // normalize + return 1.0f / peak; + case 3: // prevent clipping + if (peak > 1.0f) + return 1.0f / peak; + else + return 1.0f; + } + + } + + return 1.0f; // no gain +} + + + + +bool first; + +void pause() +{ + paused = 1; + if (audio) + { + mod.outMod->Pause(1); + } + else + { + video_clock.Pause(); + } + ResetEvent(pauseEvent); // pauseEvent signal state is opposite of pause state +} + +void unpause() +{ + paused = 0; + if (audio) + { + mod.outMod->Pause(0); + } + else + { + video_clock.Unpause(); + } + SetEvent(pauseEvent); // pauseEvent signal state is opposite of pause state +} + +int ispaused() +{ + return paused; +} + +void stop() +{ + if (reader && reader_type==READER_HTTP) StopReader(reader); + + lastfn[0] = 0; + SetEvent(killEvent); + m_kill = 1; + WaitForSingleObject(hThread, INFINITE); + + mod.outMod->Close(); + mod.SAVSADeInit(); + + if (m_opened) MP4Close(MP4hFile); + MP4hFile = 0; + m_opened = 0; + + if (audio) + { + audio->Close(); + audioFactory->releaseInterface(audio); + } + + audioFactory = 0; + audio = 0; + + if (video) + { + video->Close(); + videoFactory->releaseInterface(video); + } + videoFactory=0; + video = 0; + + if (reader) + { + if (reader_type == READER_HTTP) + DestroyReader(reader); + else + DestroyUnicodeReader(reader); + } + reader = 0; +} + +int getlength() +{ + return (int)(m_length*1000); +} + +int getoutputtime() +{ + if (m_needseek == -1) + return (int)GetClock(); + else + return m_needseek; // this prevents the seekbar from jumping around while the playthread is seeking +} + +void setvolume(int volume) +{ + mod.outMod->SetVolume(volume); +} +void setpan(int pan) +{ + mod.outMod->SetPan(pan); +} +/* +void FillInfo(HWND hwndDlg, MP4FileHandle hMp4); +void CALLBACK CurrentlyPlayingInfoBox(ULONG_PTR param) +{ + ThreadInfoBox *threadInfo = (ThreadInfoBox *)param; + FillInfo(threadInfo->hwndDlg, MP4hFile); + SetEvent(threadInfo->completionEvent); +} +*/ +void getfileinfo(const wchar_t *filename, wchar_t *title, int *length_in_ms) +{ + if (!filename || !*filename) // currently playing file + { + if (length_in_ms) *length_in_ms = getlength(); + if (title) // get non-path portion.of filename + { + lstrcpynW(title, lastfn, GETFILEINFO_TITLE_LENGTH); + PathStripPathW(title); + PathRemoveExtensionW(title); + } + } + else // some other file + { + if (length_in_ms) // calculate length + { + *length_in_ms = -1000; // the default is unknown file length (-1000). + + MP4FileHandle hMp4 = MP4Read(filename); + if (hMp4) + { + double lengthAudio = 0; + double lengthVideo = 0; + MP4TrackId audio_track = GetAudioTrack(hMp4); + if (audio_track != -1) + { + int timescale = MP4GetTrackTimeScale(hMp4, audio_track); + unsigned __int64 trackDuration = MP4GetTrackDuration(hMp4, audio_track); + lengthAudio = (double)(__int64)trackDuration / (double)timescale; + } + MP4TrackId video_track = GetVideoTrack(hMp4); + if (video_track != -1) + { + int timescale = MP4GetTrackTimeScale(hMp4, video_track); + unsigned __int64 trackDuration = MP4GetTrackDuration(hMp4, video_track); + lengthVideo = (double)(__int64)trackDuration / (double)timescale; + } + *length_in_ms = (int)(max(lengthAudio, lengthVideo) * 1000); + MP4Close(hMp4); + } + } + if (title) // get non path portion of filename + { + lstrcpynW(title, filename, GETFILEINFO_TITLE_LENGTH); + PathStripPathW(title); + PathRemoveExtensionW(title); + } + } +} + +void eq_set(int on, char data[10], int preamp) +{} + +// module definition. + +In_Module mod = +{ + IN_VER_RET, // defined in IN2.H + "nullsoft(in_mp4.dll)", //"Nullsoft MPEG-4 Audio Decoder v1.22" + 0, // hMainWindow (filled in by winamp) + 0, // hDllInstance (filled in by winamp) + 0, // this is a double-null limited list. "EXT\0Description\0EXT\0Description\0" etc. + 1, // is_seekable + 1, // uses output plug-in system + config, + about, + init, + quit, + getfileinfo, + infoDlg, + isourfile, + play, + pause, + unpause, + ispaused, + stop, + + getlength, + getoutputtime, + setoutputtime, + + setvolume, + setpan, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, // visualization calls filled in by winamp + + 0, 0, // dsp calls filled in by winamp + + eq_set, + + NULL, // setinfo call filled in by winamp + + 0 // out_mod filled in by winamp +}; + +extern "C" +{ + __declspec(dllexport) In_Module * winampGetInModule2() + { + return &mod; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/Main.h b/Src/Plugins/Input/in_mp4/Main.h new file mode 100644 index 00000000..661e182d --- /dev/null +++ b/Src/Plugins/Input/in_mp4/Main.h @@ -0,0 +1,76 @@ +#ifndef NULLSOFT_IN_MP4_MAINH +#define NULLSOFT_IN_MP4_MAINH + +#include "mp4.h" +#include "../Winamp/in2.h" +#include "mpeg4audio.h" +#include "mpeg4video.h" +#include "AudioSample.h" +#include "../nu/AutoLock.h" +#include "../nu/VideoClock.h" + +extern nu::VideoClock video_clock; + +MP4TrackId GetAudioTrack(MP4FileHandle infile); +MP4TrackId GetVideoTrack(MP4FileHandle infile); +int GetAACTrack(MP4FileHandle infile); +class waServiceFactory; +bool CreateVideoDecoder(MP4FileHandle file, MP4TrackId track, MP4VideoDecoder *&decoder, waServiceFactory *&serviceFactory); + +class MP4AudioDecoder; + +bool CreateDecoder(MP4FileHandle file, MP4TrackId track, MP4AudioDecoder *&decoder, waServiceFactory *&serviceFactory); + +void ConfigureDecoderASC(MP4FileHandle file, MP4TrackId track, MP4AudioDecoder *decoder); +bool GetCustomMetadata(MP4FileHandle mp4, char *metadata, wchar_t *dest, int destlen, const char *owner=0); +float GetGain(MP4FileHandle mp4, bool allowDefault=true); +void GetGaps(MP4FileHandle mp4, unsigned __int32 &pre, unsigned __int32 &post); + +struct ThreadInfoBox +{ + HWND hwndDlg; + HANDLE completionEvent; +}; +VOID CALLBACK CurrentlyPlayingInfoBox(ULONG_PTR param); + +extern wchar_t lastfn[MAX_PATH*4]; + +extern HANDLE killEvent, seekEvent, pauseEvent; +extern In_Module mod; // the output module +extern MP4FileHandle MP4hFile; +extern MP4TrackId audio_track, video_track; +class AudioSample; +int TryWriteAudio(AudioSample *sample); +extern MP4AudioDecoder *audio; +extern MP4VideoDecoder *video; +extern unsigned int audio_bitrate, video_bitrate; +extern MP4SampleId numSamples, numVideoSamples; +extern DWORD WINAPI PlayProc(LPVOID lpParameter); +extern bool first; + +extern HANDLE hThread; + +extern Nullsoft::Utility::LockGuard play_mp4_guard; + +extern volatile int m_needseek; +void CALLBACK Seek(ULONG_PTR data); +void CALLBACK Pause(ULONG_PTR data); +extern uint32_t m_video_timescale; +extern uint32_t m_timescale; +extern const wchar_t *defaultExtensions; +extern wchar_t m_ini[MAX_PATH]; +char *BuildExtensions(const char *extensions); +extern bool config_show_average_bitrate; +void FlushOutput(); +extern int m_force_seek; +MP4Duration GetClock(); +MP4Duration GetDecodeClock(); +extern bool audio_chunk; +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = + { + 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } + }; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/PlayThread.cpp b/Src/Plugins/Input/in_mp4/PlayThread.cpp new file mode 100644 index 00000000..661d3003 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/PlayThread.cpp @@ -0,0 +1,417 @@ +#include "main.h" +#include "../winamp/wa_ipc.h" +#include "VideoThread.h" +#include "AudioSample.h" +#include "api__in_mp4.h" +#include <assert.h> +#include <api/service/waservicefactory.h> +#include "../nu/AudioOutput.h" +#include <strsafe.h> + +const DWORD PAUSE_TIMEOUT = 100; // number of milliseconds to sleep for when paused + +static bool audio_opened; +static HANDLE events[3]; +static bool done; +bool open_mp4(const wchar_t *fn); +static AudioSample *sample = 0; +static DWORD waitTime; +static MP4SampleId nextSampleId; +Nullsoft::Utility::LockGuard play_mp4_guard; + +static MP4Duration first_timestamp=0; + +class MP4Wait +{ +public: + int WaitOrAbort(int time_in_ms) + { + WaitForMultipleObjects(3, events, FALSE, INFINITE); // pauseEvent signal state is opposite of pause state + int ret = WaitForMultipleObjects(2, events, FALSE, time_in_ms); + if (ret == WAIT_TIMEOUT) + return 0; + if (ret == WAIT_OBJECT_0+1) + return 2; + return 1; + } +}; + +nu::AudioOutput<MP4Wait> audio_output(&mod); + +MP4Duration GetClock() +{ + if (audio) + { + return audio_output.GetFirstTimestamp() + mod.outMod->GetOutputTime(); + } + else if (video) + { + return video_clock.GetOutputTime(); + } + else + { + return 0; + } +} + +static void OutputSample(AudioSample *sample) +{ + if (first) + { + first_timestamp = MP4ConvertFromTrackTimestamp(MP4hFile, audio_track, sample->timestamp, MP4_MSECS_TIME_SCALE); + first = false; + } + + if (sample->result == MP4_SUCCESS) + { + if (!audio_opened) + { + audio_opened=true; + + if (audio_output.Open(first_timestamp, sample->numChannels, sample->sampleRate, sample->bitsPerSample) == false) + { + PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return ; + } + unsigned __int32 pregap = 0; + unsigned __int32 postgap = 0; + GetGaps(MP4hFile, pregap, postgap); + audio_output.SetDelays(0, pregap, postgap); + mod.SetInfo(audio_bitrate + video_bitrate, sample->sampleRate / 1000, sample->numChannels, 1); + } + + int skip = 0; + int sample_size = (sample->bitsPerSample / 8) * sample->numChannels; + int outSamples = MulDiv(sample->outputValid, m_timescale, sample->sampleRate * sample_size); + /* if (!audio_chunk && outSamples > sample->duration) + outSamples = (int)sample->duration; */ + + if (sample->offset > 0) + { + int cut = (int)min(outSamples, sample->offset); + outSamples -= cut; + skip = cut; + } + + size_t outSize = MulDiv(sample_size * sample->sampleRate, outSamples, m_timescale); + + if (audio_bitrate != sample->bitrate) + { + audio_bitrate = sample->bitrate; + mod.SetInfo(audio_bitrate + video_bitrate, -1, -1, 1); + } + + if (audio_output.Write(sample->output + MulDiv(sample_size * sample->sampleRate, skip, m_timescale), outSize) == 1) + { + return ; + } + + if (sample->sampleId == numSamples) // done! + done = true; // TODO: probably don't want to bail out yet if video is playing + } +} + +static bool DecodeAudioSample(AudioSample *sample) +{ + if (m_needseek != -1) + { + sample->outputValid = 0; + sample->outputCursor = sample->output; + sample->result = MP4_SUCCESS; + sample->sampleRate = m_timescale; + audio->GetOutputProperties(&sample->sampleRate, &sample->numChannels, &sample->bitsPerSample); + if (audio->GetCurrentBitrate(&sample->bitrate) != MP4_SUCCESS || !sample->bitrate) + sample->bitrate = audio_bitrate; + } + else + { + sample->outputValid = sample->outputSize; + sample->outputCursor = 0; + sample->result = audio->DecodeSample(sample->input, sample->inputValid, sample->output, &sample->outputValid); + if (sample->inputValid == 0 && sample->outputValid == 0) { + return false; + } + sample->sampleRate = m_timescale; + audio->GetOutputProperties(&sample->sampleRate, &sample->numChannels, &sample->bitsPerSample); + if (audio->GetCurrentBitrate(&sample->bitrate) != MP4_SUCCESS || !sample->bitrate) + sample->bitrate = audio_bitrate; + OutputSample(sample); + } + return true; +} + +static void ReadNextAudioSample() +{ + if (nextSampleId > numSamples) + { + return; + } + + unsigned __int32 buffer_size = sample->inputSize; + + bool sample_read = false; + play_mp4_guard.Lock(); + if (audio_chunk) + sample_read = MP4ReadChunk(MP4hFile, audio_track, nextSampleId++, (unsigned __int8 **)&sample->input, &buffer_size, &sample->timestamp, &sample->duration); + else + sample_read = MP4ReadSample(MP4hFile, audio_track, nextSampleId++, (unsigned __int8 **)&sample->input, &buffer_size, &sample->timestamp, &sample->duration, &sample->offset); + play_mp4_guard.Unlock(); + if (sample_read) + { + sample->inputValid = buffer_size; + + if (audio_chunk) + { + sample->duration = 0; + sample->offset = 0; + } + sample->sampleId = nextSampleId-1; + + DecodeAudioSample(sample); + } +} + +static bool BuildAudioBuffers() +{ + size_t outputFrameSize; + //if (audio->OutputFrameSize(&outputFrameSize) != MP4_SUCCESS || !outputFrameSize) + //{ + outputFrameSize = 8192 * 6; // fallback size + //} + + u_int32_t maxSize = 0; + if (audio) + { + if (audio_chunk) + maxSize = 65536; // TODO!!!! + else + maxSize = MP4GetTrackMaxSampleSize(MP4hFile, audio_track); + if (!maxSize) + return 0; + + sample = new AudioSample(maxSize, outputFrameSize); + if (!sample->OK()) + { + delete sample; + return false; + } + } + + if (video) + { + maxSize = MP4GetTrackMaxSampleSize(MP4hFile, video_track); + + video_sample = new VideoSample(maxSize); + if (!video_sample->OK()) + { + delete video_sample; + return false; + } + } + + return true; +} + +DWORD WINAPI PlayProc(LPVOID lpParameter) +{ + // set an event when we start. this keeps Windows from queueing an APC before the thread proc even starts (evil, evil windows) + HANDLE threadCreatedEvent = (HANDLE)lpParameter; + SetEvent(threadCreatedEvent); + + video=0; + if (!open_mp4(lastfn)) + { + if (WaitForSingleObject(killEvent, 200) != WAIT_OBJECT_0) + PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return 0; + } + + audio_output.Init(mod.outMod); + + if (videoOutput && video) + { + // TODO: this is really just a placeholder, we should do smarter stuff + // like query the decoder object for a name rather than guess + char set_info[256] = {0}; + char *audio_info = MP4PrintAudioInfo(MP4hFile, audio_track); + char *video_info = 0; + if (video_track != MP4_INVALID_TRACK_ID) + video_info = MP4PrintVideoInfo(MP4hFile, video_track); + + if (video_info) + { + StringCchPrintfA(set_info, 256, "%s, %s %ux%u", audio_info, video_info, MP4GetTrackVideoWidth(MP4hFile, video_track), MP4GetTrackVideoHeight(MP4hFile, video_track)); + videoOutput->extended(VIDUSER_SET_INFOSTRING,(INT_PTR)set_info,0); + MP4Free(video_info); + } + MP4Free(audio_info); + } + + if (!BuildAudioBuffers()) + { + // TODO: benski> more cleanup work has to be done here! + if (WaitForSingleObject(killEvent, 200) != WAIT_OBJECT_0) + PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return 0; + } + + nextSampleId = 1; + nextVideoSampleId = 1; + if (video) + Video_Init(); + + first = true; + audio_opened = false; + first_timestamp= 0; + + events[0]=killEvent; + events[1]=seekEvent; + events[2]=pauseEvent; + waitTime = audio?0:INFINITE; + done = false; + + while (!done) + { + int ret = WaitForMultipleObjects(2, events, FALSE, waitTime); + switch (ret) + { + case WAIT_OBJECT_0: // kill event + done = true; + break; + + case WAIT_OBJECT_0 + 1: // seek event + { + bool rewind = m_needseek < GetClock(); + // TODO: reset pregap? + MP4SampleId new_video_sample = MP4_INVALID_SAMPLE_ID; + if (video) + { + SetEvent(video_start_flushing); + WaitForSingleObject(video_flush_done, INFINITE); + + MP4Duration duration = MP4ConvertToTrackDuration(MP4hFile, video_track, m_needseek, MP4_MSECS_TIME_SCALE); + if (duration != MP4_INVALID_DURATION) + { + new_video_sample = MP4GetSampleIdFromTime(MP4hFile, video_track, duration, true, rewind); + if (new_video_sample == MP4_INVALID_SAMPLE_ID) + new_video_sample = MP4GetSampleIdFromTime(MP4hFile, video_track, duration, false); // try again without keyframe seeking + + /* TODO: make sure the new seek direction is in the same as the request seek direction. + e.g. make sure a seek FORWARD doesn't go BACKWARD + MP4Timestamp video_timestamp = MP4GetSampleTime(MP4hFile, video_track, seek_video_sample); + int new_time = MP4ConvertFromTrackTimestamp(MP4hFile, video_track, video_timestamp, MP4_MSECS_TIME_SCALE); + if (m_needseek < GetClock()) + video_timestamp = MP4GetSampleIdFromTime(MP4hFile, video_track, duration, true); // first closest keyframe prior + */ + if (new_video_sample != MP4_INVALID_SAMPLE_ID) + { + int m_old_needseek = m_needseek; + + MP4Timestamp video_timestamp = MP4GetSampleTime(MP4hFile, video_track, new_video_sample); + m_needseek = MP4ConvertFromTrackTimestamp(MP4hFile, video_track, video_timestamp, MP4_MSECS_TIME_SCALE); + if (!audio) + { + MP4Timestamp video_timestamp = MP4GetSampleTime(MP4hFile, video_track, new_video_sample); + m_needseek = MP4ConvertFromTrackTimestamp(MP4hFile, video_track, video_timestamp, MP4_MSECS_TIME_SCALE); + video_clock.Seek(m_needseek); + m_needseek = -1; + } + else + { + // TODO check this will just do what is needed + // aim of this is when there is 1 artwork + // frame then we don't lock audio<->video + // as it otherwise prevents audio seeking + if (!m_needseek && m_old_needseek != m_needseek && new_video_sample == 1) + { + m_needseek = m_old_needseek; + } + } + } + } + } + + if (audio) + { + MP4Duration duration = MP4ConvertToTrackDuration(MP4hFile, audio_track, m_needseek, MP4_MSECS_TIME_SCALE); + if (duration != MP4_INVALID_DURATION) + { + MP4SampleId newSampleId = audio_chunk?MP4GetChunkIdFromTime(MP4hFile, audio_track, duration):MP4GetSampleIdFromTime(MP4hFile, audio_track, duration); + if (newSampleId != MP4_INVALID_SAMPLE_ID) + { + audio->Flush(); + + if (video) + { + if (new_video_sample == MP4_INVALID_SAMPLE_ID) + { + SetEvent(video_resume); + } + else + { + nextVideoSampleId = new_video_sample; + SetEvent(video_flush); + } + WaitForSingleObject(video_flush_done, INFINITE); + } + m_needseek = MP4ConvertFromTrackTimestamp(MP4hFile, audio_track, duration, MP4_MILLISECONDS_TIME_SCALE); + ResetEvent(seekEvent); + audio_output.Flush(m_needseek); + m_needseek = -1; + nextSampleId = newSampleId; + continue; + } + } + } + else + { + if (new_video_sample == MP4_INVALID_SAMPLE_ID) + { + SetEvent(video_resume); + } + else + { + nextVideoSampleId = new_video_sample; + SetEvent(video_flush); + } + WaitForSingleObject(video_flush_done, INFINITE); + + ResetEvent(seekEvent); + continue; + } + } + break; + + case WAIT_TIMEOUT: + ReadNextAudioSample(); + break; + } + } + + if (WaitForSingleObject(killEvent, 0) == WAIT_TIMEOUT) // if (!killed) + { + // tell audio decoder about end-of-stream and get remaining audio + /* if (audio) { + audio->EndOfStream(); + + sample->inputValid = 0; + while (DecodeAudioSample(sample)) { + } + } + */ + audio_output.Write(0,0); + audio_output.WaitWhilePlaying(); + + if (WaitForSingleObject(killEvent, 0) == WAIT_TIMEOUT) + PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + } + SetEvent(killEvent); + + // eat the rest of the APC messages + while (SleepEx(0, TRUE) == WAIT_IO_COMPLETION) {} + + if (video) + Video_Close(); + + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/RawMediaReader.cpp b/Src/Plugins/Input/in_mp4/RawMediaReader.cpp new file mode 100644 index 00000000..4d077a6b --- /dev/null +++ b/Src/Plugins/Input/in_mp4/RawMediaReader.cpp @@ -0,0 +1,145 @@ +#include "RawMediaReader.h" +#include "virtualIO.h" +#include <limits.h> + +bool IsMyExtension(const wchar_t *filename); + +int RawMediaReaderService::CreateRawMediaReader(const wchar_t *filename, ifc_raw_media_reader **out_reader) +{ + if (IsMyExtension(filename)) + { + void *unicode_reader = CreateUnicodeReader(filename); + if (!unicode_reader ) + return NErr_FileNotFound; + + MP4FileHandle mp4 = MP4ReadEx(filename, unicode_reader, &UnicodeIO); + if (!mp4) + { + DestroyUnicodeReader(unicode_reader); + return NErr_Malformed; + } + + + RawMediaReader *reader = new RawMediaReader(mp4, unicode_reader); + if (!reader) + { + MP4Close(mp4); + DestroyUnicodeReader(unicode_reader); + return NErr_OutOfMemory; + } + + *out_reader = reader; + return NErr_Success; + } + else + { + return NErr_False; + } +} + +#define CBCLASS RawMediaReaderService +START_DISPATCH; +CB(CREATERAWMEDIAREADER, CreateRawMediaReader); +END_DISPATCH; +#undef CBCLASS + +RawMediaReader::RawMediaReader(MP4FileHandle file, void *reader) : file(file), reader(reader) +{ + track_num=0; + number_of_tracks=MP4GetNumberOfTracks(file); + current_track = MP4_INVALID_TRACK_ID; + chunk_position=0; + chunk_size=0; + chunk_buffer=0; +} + +RawMediaReader::~RawMediaReader() +{ + if (chunk_buffer) + MP4Free(chunk_buffer); + MP4Close(file); + DestroyUnicodeReader(reader); +} + +int RawMediaReader::ReadNextChunk() +{ +again: + /* see if it's time to cycle to the next track */ + if (current_track == MP4_INVALID_TRACK_ID) + { + if (track_num == number_of_tracks) + return NErr_EndOfFile; + + current_track = MP4FindTrackId(file, track_num); + if (current_track == MP4_INVALID_TRACK_ID) + return NErr_EndOfFile; + + track_num++; + + const char* trackType = MP4GetTrackType(file, current_track); + if (!MP4_IS_AUDIO_TRACK_TYPE(trackType) && !MP4_IS_VIDEO_TRACK_TYPE(trackType)) + { + current_track = MP4_INVALID_TRACK_ID; + goto again; + } + + chunk_id = 1; + number_of_chunks= MP4GetTrackNumberOfChunks(file, current_track); + } + + /* see if we've read all of our samples */ + if (chunk_id > number_of_chunks) + { + current_track = MP4_INVALID_TRACK_ID; + goto again; + } + + bool readSuccess = MP4ReadChunk(file, current_track, chunk_id, &chunk_buffer, &chunk_size); + if (!readSuccess) + return NErr_Error; + + chunk_position=0; + chunk_id++; + return NErr_Success; +} + +int RawMediaReader::Read(void *buffer, size_t buffer_size, size_t *bytes_read) +{ + if (buffer_size > INT_MAX) + return NErr_BadParameter; + + if (chunk_position==chunk_size) + { + MP4Free(chunk_buffer); + chunk_buffer=0; + } + + if (!chunk_buffer) + { + int ret = ReadNextChunk(); + if (ret != NErr_Success) + return ret; + } + + size_t to_read = chunk_size-chunk_position; + if (to_read > buffer_size) + to_read = buffer_size; + + memcpy(buffer, &chunk_buffer[chunk_position], to_read); + chunk_position += to_read; + *bytes_read = to_read; + return NErr_Success; +} + +size_t RawMediaReader::Release() +{ + delete this; + return 0; +} + +#define CBCLASS RawMediaReader +START_DISPATCH; +CB(RELEASE, Release); +CB(RAW_READ, Read); +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/RawMediaReader.h b/Src/Plugins/Input/in_mp4/RawMediaReader.h new file mode 100644 index 00000000..9080db25 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/RawMediaReader.h @@ -0,0 +1,40 @@ +#pragma once +#include "../Agave/DecodeFile/svc_raw_media_reader.h" +#include "../Agave/DecodeFile/ifc_raw_media_reader.h" +#include <mp4.h> + +// {5CBD1F27-5A63-4D8C-9297-D74518E1EF3A} +static const GUID mpeg4_raw_reader_guid = +{ 0x5cbd1f27, 0x5a63, 0x4d8c, { 0x92, 0x97, 0xd7, 0x45, 0x18, 0xe1, 0xef, 0x3a } }; + +class RawMediaReaderService : public svc_raw_media_reader +{ +public: + static const char *getServiceName() { return "MPEG-4 Raw Reader"; } + static GUID getServiceGuid() { return mpeg4_raw_reader_guid; } + int CreateRawMediaReader(const wchar_t *filename, ifc_raw_media_reader **reader); +protected: + RECVS_DISPATCH; +}; + +class RawMediaReader : public ifc_raw_media_reader +{ +public: + RawMediaReader(MP4FileHandle file, void *reader); + ~RawMediaReader(); + int Read(void *buffer, size_t buffer_size, size_t *bytes_read); + size_t Release(); +protected: + RECVS_DISPATCH; +private: + uint16_t track_num; + uint32_t number_of_tracks; + MP4TrackId current_track; + MP4FileHandle file; + void *reader; + MP4ChunkId chunk_id; + MP4ChunkId number_of_chunks; + uint32_t chunk_position, chunk_size; + uint8_t *chunk_buffer; + int ReadNextChunk(); +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/Stopper.cpp b/Src/Plugins/Input/in_mp4/Stopper.cpp new file mode 100644 index 00000000..8599d0cb --- /dev/null +++ b/Src/Plugins/Input/in_mp4/Stopper.cpp @@ -0,0 +1,47 @@ +#include "Stopper.h" +#include "main.h" +#include "../Winamp/wa_ipc.h" + +int m_force_seek=-1; + +Stopper::Stopper() : isplaying(0), timems(0) +{ +} + +void Stopper::ChangeTracking(bool mode) +{ + SendMessage(mod.hMainWindow, WM_USER, mode, IPC_ALLOW_PLAYTRACKING); // enable / disable stats updating +} + +void Stopper::Stop() +{ + isplaying = SendMessage(mod.hMainWindow, WM_USER, 0, IPC_ISPLAYING); + if (isplaying) + { + ChangeTracking(0); // disable stats updating + timems = SendMessage(mod.hMainWindow, WM_USER, 0, IPC_GETOUTPUTTIME); + SendMessage(mod.hMainWindow, WM_COMMAND, 40047, 0); // Stop + } +} + +void Stopper::Play() +{ + if (isplaying) // this works _most_ of the time, not sure why a small portion of the time it doesnt hrmph :/ + // ideally we should replace it with a system that pauses the decode thread, closes its file, + // does the shit, and reopens and reseeks to the new offset. for gaplessness + { + if (timems) + { + m_force_seek = timems; // SendMessage(mod.hMainWindow,WM_USER,timems,106); + } + else m_force_seek = -1; + SendMessage(mod.hMainWindow, WM_COMMAND, 40045, 0); // Play + m_force_seek = -1; + if (isplaying & 2) + { + SendMessage(mod.hMainWindow, WM_COMMAND, 40046, 0); // Pause + } + ChangeTracking(1); // enable stats updating + } + isplaying = 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/Stopper.h b/Src/Plugins/Input/in_mp4/Stopper.h new file mode 100644 index 00000000..fa6d984e --- /dev/null +++ b/Src/Plugins/Input/in_mp4/Stopper.h @@ -0,0 +1,11 @@ +#pragma once +class Stopper +{ +public: + Stopper(); + void ChangeTracking(bool); + void Stop(); + void Play(); + int isplaying, timems; +}; + diff --git a/Src/Plugins/Input/in_mp4/VideoThread.cpp b/Src/Plugins/Input/in_mp4/VideoThread.cpp new file mode 100644 index 00000000..8b844800 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/VideoThread.cpp @@ -0,0 +1,215 @@ +#include "main.h" +#include "VideoThread.h" +#include "../Winamp/wa_ipc.h" + +VideoSample *video_sample=0; +IVideoOutput *videoOutput=0; +static bool video_reopen=false; +static int height=0; +static int width=0; +static bool video_opened=false; +static int consecutive_early_frames; +HANDLE video_flush = 0, video_start_flushing=0, video_flush_done = 0, video_resume = 0; +static HANDLE video_thread = 0; +MP4SampleId nextVideoSampleId=1; // set in conjunction with video_flush +static void OpenVideo() +{ + if (!video_opened || video_reopen) + { + consecutive_early_frames = 0; + if (!videoOutput) + videoOutput = (IVideoOutput *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GET_IVIDEOOUTPUT); + + int color_format; + double aspect_ratio=1.0; + if (video && video->GetOutputFormat(&width, &height, &color_format, &aspect_ratio) == MP4_VIDEO_SUCCESS) + { + videoOutput->extended(VIDUSER_SET_THREAD_SAFE, 1, 0); + videoOutput->open(width, height, 0, 1.0/aspect_ratio, color_format); + video_opened = true; + video_reopen = false; + } + } +} + +static DecodedVideoSample *GetNextPicture() +{ + void *data, *decoder_data; + MP4Timestamp timestamp=video_sample?(video_sample->timestamp):0; + switch(video->GetPicture(&data, &decoder_data, ×tamp)) + { + case MP4_VIDEO_OUTPUT_FORMAT_CHANGED: + video_reopen=true; + // fall through + case MP4_VIDEO_SUCCESS: + DecodedVideoSample *decoded = new DecodedVideoSample; + decoded->decoder = video; + decoded->decoder_data = decoder_data; + decoded->timestamp = timestamp; + decoded->output = data; + return decoded; + } + return 0; +} + +static void OutputPicture(DecodedVideoSample *decoded_video_sample) +{ + if (decoded_video_sample) + { + int outputTime = (int)((decoded_video_sample->timestamp*1000ULL)/(uint64_t)m_video_timescale); +again: + MP4Duration realTime = GetClock(); + int time_diff = outputTime - realTime; + if (time_diff > 12 && consecutive_early_frames) // plenty of time, go ahead and turn off frame dropping + { + if (--consecutive_early_frames == 0) + video->HurryUp(0); + } + else if (time_diff < -50) // shit we're way late, start dropping frames + { + video->HurryUp(1); + consecutive_early_frames += 3; + } + if (time_diff > 3) + { + HANDLE handles[] = {killEvent, video_start_flushing}; + if (WaitForMultipleObjects(2, handles, FALSE, outputTime-realTime) != WAIT_TIMEOUT) + { + delete decoded_video_sample; + decoded_video_sample=0; + return; + } + goto again; // TODO: handle paused state a little better than this + } + + OpenVideo(); // open video if we havn't already + + videoOutput->draw(decoded_video_sample->output); + + delete decoded_video_sample; + decoded_video_sample=0; + /* TODO: probably want separate audio and video done flags + if (temp->sampleId == numSamples) // done! + done = true; + */ + } +} + +static bool ReadNextVideoSample() +{ + while (nextVideoSampleId <= numVideoSamples) + { + VideoSample &sample=*video_sample; + unsigned __int32 buffer_size = sample.inputSize; + bool isSync=false; + MP4Duration duration, offset; + play_mp4_guard.Lock(); + bool sample_read=MP4ReadSample(MP4hFile, video_track, nextVideoSampleId++, (unsigned __int8 **)&sample.input, &buffer_size, &sample.timestamp, &duration, &offset, &isSync); + play_mp4_guard.Unlock(); + if (sample_read) + { + // some buggy movies store signed int32 offsets, so let's deal with it + offset = (uint32_t)offset; + int32_t signed_offset = (int32_t)offset; + + sample.timestamp += signed_offset; + //int outputTime = (int)((sample.timestamp*1000ULL) /(uint64_t)m_video_timescale); + sample.inputValid = buffer_size; + return true; + } + } + return false; +} + +static DWORD WINAPI VideoPlayThread(LPVOID parameter) +{ + DWORD waitTime = 0; + HANDLE handles[] = {killEvent, video_flush, video_start_flushing, video_resume}; + while (1) + { + int ret = WaitForMultipleObjects(4, handles, FALSE, waitTime); + if (ret == WAIT_OBJECT_0) // kill + break; + else if (ret == WAIT_OBJECT_0+1) // flush + { + if (video) + video->Flush(); + ResetEvent(video_flush); + waitTime = 0; + SetEvent(video_flush_done); + } + else if (ret == WAIT_OBJECT_0+2) // start flushing + { + waitTime = INFINITE; // this will stop us from decoding samples for a while + ResetEvent(video_start_flushing); + SetEvent(video_flush_done); + } + else if (ret == WAIT_OBJECT_0+3) // resume playback (like flush but don't flush the decoder) + { + ResetEvent(video_resume); + waitTime = 0; + SetEvent(video_flush_done); + } + else if (ret == WAIT_TIMEOUT) + { + if (ReadNextVideoSample()) + { + int ret = video->DecodeSample(video_sample->input, video_sample->inputValid, video_sample->timestamp); + if (ret == MP4_VIDEO_OUTPUT_FORMAT_CHANGED) + video_reopen=true; + if (ret == MP4_VIDEO_AGAIN) + nextVideoSampleId--; + + DecodedVideoSample *picture = 0; + while (picture = GetNextPicture()) + { + OutputPicture(picture); + } + waitTime = 0; + } + else + { + // TODO: tell decoder end-of-file and get any buffers in queue + if (!audio) + PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + waitTime = INFINITE; // out of stuff to do, wait for kill or flush + } + } + else // error + break; + } + if (videoOutput) + videoOutput->close(); + return 0; +} + +void Video_Init() +{ + width=0; + height=0; + video_reopen=false; + video_opened=false; + video_flush = CreateEvent(NULL, TRUE, FALSE, NULL); + video_start_flushing = CreateEvent(NULL, TRUE, FALSE, NULL); + video_flush_done = CreateEvent(NULL, FALSE, FALSE, NULL); + video_resume = CreateEvent(NULL, TRUE, FALSE, NULL); + video_thread = CreateThread(0, 0, VideoPlayThread, 0, 0, 0); +} + +void Video_Close() +{ + WaitForSingleObject(video_thread, INFINITE); + CloseHandle(video_thread); + video_thread = 0; + CloseHandle(video_start_flushing); + video_start_flushing=0; + CloseHandle(video_flush); + video_flush=0; + CloseHandle(video_resume); + video_resume=0; + CloseHandle(video_flush_done); + video_flush_done = 0; + + delete video_sample; + video_sample=0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/VideoThread.h b/Src/Plugins/Input/in_mp4/VideoThread.h new file mode 100644 index 00000000..389e3cf8 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/VideoThread.h @@ -0,0 +1,10 @@ +#pragma once +#include "AudioSample.h" +#include "../Winamp/wa_ipc.h" +void Video_Init(); +void Video_Close(); + +extern IVideoOutput *videoOutput; +extern VideoSample *video_sample; +extern HANDLE video_start_flushing, video_flush, video_flush_done, video_resume; +extern MP4SampleId nextVideoSampleId; // set in conjunction with video_flush
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/VirtualIO.cpp b/Src/Plugins/Input/in_mp4/VirtualIO.cpp new file mode 100644 index 00000000..b5ea4fc9 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/VirtualIO.cpp @@ -0,0 +1,716 @@ +#include "main.h" +#include "VirtualIO.h" +#include "api__in_mp4.h" +#include "api/service/waservicefactory.h" +#include "../../..\Components\wac_network\wac_network_http_receiver_api.h" +#include "../nu/AutoChar.h" +#include "../nu/ProgressTracker.h" + +#include <assert.h> +#include <strsafe.h> + +#define HTTP_BUFFER_SIZE 65536 +// {C0A565DC-0CFE-405a-A27C-468B0C8A3A5C} +static const GUID internetConfigGroupGUID = +{ + 0xc0a565dc, 0xcfe, 0x405a, { 0xa2, 0x7c, 0x46, 0x8b, 0xc, 0x8a, 0x3a, 0x5c } +}; + +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); +} + +static api_httpreceiver *SetupConnection(const char *url, uint64_t start_position, uint64_t end_position) +{ + api_httpreceiver *http = 0; + waServiceFactory *sf = mod.service->service_getServiceByGuid(httpreceiverGUID); + if (sf) http = (api_httpreceiver *)sf->getInterface(); + + if (!http) + return http; + + int use_proxy = 1; + 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; + + const wchar_t *proxy = use_proxy?AGAVE_API_CONFIG->GetString(internetConfigGroupGUID, L"proxy", 0):0; + http->open(API_DNS_AUTODNS, HTTP_BUFFER_SIZE, (proxy && proxy[0]) ? (const char *)AutoChar(proxy) : NULL); + if (start_position && start_position != (uint64_t)-1) + { + if (end_position == (uint64_t)-1) + { + char temp[128] = {0}; + StringCchPrintfA(temp, 128, "Range: bytes=%I64u-", start_position); + http->addheader(temp); + } + else + { + char temp[128] = {0}; + StringCchPrintfA(temp, 128, "Range: bytes=%I64u-%I64u", start_position, end_position); + http->addheader(temp); + } + } + SetUserAgent(http); + http->connect(url); + return http; +} + +static DWORD CALLBACK ProgressiveThread(LPVOID param); + +static __int64 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 bufferCount; +static void Buffering(int bufStatus, const wchar_t *displayString) +{ + if (bufStatus < 0 || bufStatus > 100) + return; + + char tempdata[75*2] = {0, }; + + int csa = mod.SAGetMode(); + if (csa & 1) + { + for (int x = 0; x < bufStatus*75 / 100; x ++) + tempdata[x] = x * 16 / 75; + } + else if (csa&2) + { + int offs = (csa & 1) ? 75 : 0; + int x = 0; + while (x < bufStatus*75 / 100) + { + tempdata[offs + x++] = -6 + x * 14 / 75; + } + while (x < 75) + { + tempdata[offs + x++] = 0; + } + } + else if (csa == 4) + { + tempdata[0] = tempdata[1] = (bufStatus * 127 / 100); + } + if (csa) mod.SAAdd(tempdata, ++bufferCount, (csa == 3) ? 0x80000003 : csa); + + /* + TODO + wchar_t temp[64] = {0}; + StringCchPrintf(temp, 64, L"%s: %d%%",displayString, bufStatus); + SetStatus(temp); + */ + //SetVideoStatusText(temp); // TODO: find a way to set the old status back + // videoOutput->notifyBufferState(static_cast<int>(bufStatus*2.55f)); +} + +class ProgressiveReader +{ +public: + ProgressiveReader(const char *url, HANDLE killswitch) : killswitch(killswitch) + { + thread_abort = CreateEvent(NULL, FALSE, FALSE, NULL); + download_thread = 0; + progressive_file_read = 0; + progressive_file_write = 0; + + content_length=0; + current_position=0; + stream_disconnected=false; + connected=false; + end_of_file=false; + + wchar_t temppath[MAX_PATH-14] = {0}; // MAX_PATH-14 'cause MSDN said so + GetTempPathW(MAX_PATH-14, temppath); + GetTempFileNameW(temppath, L"wdl", 0, filename); + this->url = _strdup(url); + http = SetupConnection(url, 0, (uint64_t)-1); + + progressive_file_read = CreateFileW(filename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0); + progressive_file_write = CreateFileW(filename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0); + download_thread = CreateThread(0, 0, ProgressiveThread, this, 0, 0); + + while (!connected && !stream_disconnected && WaitForSingleObject(killswitch, 55) == WAIT_TIMEOUT) + { + // nop + } + Buffer(); + } + + ~ProgressiveReader() + { + if (download_thread) + { + SetEvent(thread_abort); + WaitForSingleObject(download_thread, INFINITE); + CloseHandle(download_thread); + } + + if (thread_abort) + { + CloseHandle(thread_abort); + } + + CloseHandle(progressive_file_read); + CloseHandle(progressive_file_write); + DeleteFile(filename); + + if (http) + { + waServiceFactory *sf = mod.service->service_getServiceByGuid(httpreceiverGUID); + if (sf) http = (api_httpreceiver *)sf->releaseInterface(http); + http=0; + } + } + + void Buffer() + { + bufferCount=0; + for (int i=0;i<101;i++) + { + Buffering(i, L"Buffering: "); + WaitForSingleObject(killswitch, 55); + } + } + + void OnFinish() + { + stream_disconnected=true; + } + + + bool WaitForPosition(uint64_t position, uint64_t size) + { + do + { + bool valid = progress_tracker.Valid(position, position+size); + if (valid) + return true; + else + { + if (position < current_position) + { + Reconnect(position, position+size); + } + else + { + Buffer(); + } + } + } while (WaitForSingleObject(killswitch, 0) == WAIT_TIMEOUT); + return false; + } + + size_t Read(void *buffer, size_t size) + { + if (WaitForPosition(current_position, (uint64_t)size) == false) + return 0; + + DWORD bytes_read=0; + ReadFile(progressive_file_read, buffer, size, &bytes_read, NULL); + current_position += bytes_read; + return bytes_read; + } + + uint64_t GetFileLength() + { + return content_length; + } + + void Reconnect(uint64_t position, uint64_t end) + { + SetEvent(thread_abort); + WaitForSingleObject(download_thread, INFINITE); + ResetEvent(thread_abort); + + uint64_t new_start, new_end; + progress_tracker.Seek(position, end, &new_start, &new_end); + + CloseHandle(download_thread); + stream_disconnected=false; + connected=false; + if (http) + { + waServiceFactory *sf = mod.service->service_getServiceByGuid(httpreceiverGUID); + if (sf) http = (api_httpreceiver *)sf->releaseInterface(http); + http=0; + } + + http = SetupConnection(url, new_start, new_end); + Seek64(progressive_file_write, new_start, SEEK_SET); + download_thread = CreateThread(0, 0, ProgressiveThread, this, 0, 0); + while (!connected && !stream_disconnected && WaitForSingleObject(killswitch, 55) == WAIT_TIMEOUT) + { + // nop + } + Buffer(); + } + + int SetPosition(uint64_t position) + { + if (position == content_length) + { + end_of_file=true; + } + else + { + if (!progress_tracker.Valid(position, position)) + { + Reconnect(position, (uint64_t)-1); + } + current_position = Seek64(progressive_file_read, position, SEEK_SET); + end_of_file=false; + } + return 0; + } + + int GetPosition(uint64_t *position) + { + if (end_of_file) + *position = content_length; + else + *position = current_position; + return 0; + } + + int EndOfFile() + { + return !!stream_disconnected; + } + + int Close() + { + SetEvent(thread_abort); + while (!stream_disconnected && WaitForSingleObject(killswitch, 55) == WAIT_TIMEOUT) + { + // nop + } + return 0; + } + + /* API used by download thread */ + void Write(const void *data, size_t data_len) + { + DWORD bytes_written = 0; + WriteFile(progressive_file_write, data, data_len, &bytes_written, 0); + progress_tracker.Write(data_len); + } + + int Wait(int milliseconds) + { + HANDLE handles[] = {killswitch, thread_abort}; + int ret = WaitForMultipleObjects(2, handles, FALSE, milliseconds); + if (ret == WAIT_OBJECT_0+1) + return 1; + else if (ret == WAIT_TIMEOUT) + return 0; + else + return -1; + } + + int DoRead(void *buffer, size_t bufferlen) + { + int ret = http->run(); + int bytes_received; + do + { + ret = http->run(); + bytes_received= http->get_bytes(buffer, bufferlen); + if (bytes_received) + Write(buffer, bytes_received); + } while (bytes_received); + return ret; + } + + int Connect() + { + do + { + int ret = http->run(); + if (ret == -1) // connection failed + return ret; + + // ---- check our reply code ---- + int replycode = http->getreplycode(); + switch (replycode) + { + case 0: + case 100: + break; + case 200: + case 206: + { + + const char *content_length_header = http->getheader("Content-Length"); + if (content_length_header) + { + uint64_t new_content_length = _strtoui64(content_length_header, 0, 10); + //InterlockedExchange64((volatile LONGLONG *)&content_length, new_content_length); + content_length = new_content_length; // TODO interlock on win32 + } + connected=true; + + return 0; + } + default: + return -1; + } + } + while (Wait(55) == 0); + return 0; + } + +private: + uint64_t current_position; + volatile uint64_t content_length; + bool end_of_file; + bool stream_disconnected; + bool connected; + char *url; + wchar_t filename[MAX_PATH]; + HANDLE progressive_file_read, progressive_file_write; + ProgressTracker progress_tracker; + HANDLE killswitch; + HANDLE download_thread; + api_httpreceiver *http; + HANDLE thread_abort; +}; + +static DWORD CALLBACK ProgressiveThread(LPVOID param) +{ + ProgressiveReader *reader = (ProgressiveReader *)param; + + if (reader->Connect() == 0) + { + int ret = 0; + while (ret == 0) + { + ret=reader->Wait(10); + if (ret >= 0) + { + char buffer[HTTP_BUFFER_SIZE] = {0}; + reader->DoRead(buffer, sizeof(buffer)); + } + } + } + reader->OnFinish(); + + return 0; +} + +u_int64_t HTTPGetFileLength(void *user) +{ + ProgressiveReader *reader = (ProgressiveReader *)user; + return reader->GetFileLength(); +} + +int HTTPSetPosition(void *user, u_int64_t position) +{ + ProgressiveReader *reader = (ProgressiveReader *)user; + return reader->SetPosition(position); +} + +int HTTPGetPosition(void *user, u_int64_t *position) +{ + ProgressiveReader *reader = (ProgressiveReader *)user; + return reader->GetPosition(position); +} + +size_t HTTPRead(void *user, void *buffer, size_t size) +{ + ProgressiveReader *reader = (ProgressiveReader *)user; + return reader->Read(buffer, size); +} + +size_t HTTPWrite(void *user, void *buffer, size_t size) +{ + return 1; +} + +int HTTPEndOfFile(void *user) +{ + ProgressiveReader *reader = (ProgressiveReader *)user; + return reader->EndOfFile(); +} + +int HTTPClose(void *user) +{ + ProgressiveReader *reader = (ProgressiveReader *)user; + return reader->Close(); +} + +Virtual_IO HTTPIO = +{ + HTTPGetFileLength, + HTTPSetPosition, + HTTPGetPosition, + HTTPRead, + HTTPWrite, + HTTPEndOfFile, + HTTPClose, +}; + +void *CreateReader(const wchar_t *url, HANDLE killswitch) +{ + + if ( WAC_API_DOWNLOADMANAGER ) + { + return new ProgressiveReader(AutoChar(url), killswitch); + } + else + return 0; +} + +void DestroyReader(void *user) +{ + ProgressiveReader *reader = (ProgressiveReader *)user; + delete reader; +} + +void StopReader(void *user) +{ + ProgressiveReader *reader = (ProgressiveReader *)user; + reader->Close(); +} + +/* ----------------------------------- */ + +struct Win32_State +{ + Win32_State() + { + memset(buffer, 0, sizeof(buffer)); + handle=0; + endOfFile=false; + position.QuadPart = 0; + event = CreateEvent(NULL, TRUE, TRUE, NULL); + read_offset=0; + io_active=false; + } + ~Win32_State() + { + if (handle && handle != INVALID_HANDLE_VALUE) + CancelIo(handle); + CloseHandle(event); + } + // void *userData; + HANDLE handle; + bool endOfFile; + LARGE_INTEGER position; + HANDLE event; + OVERLAPPED overlapped; + DWORD read_offset; + bool io_active; + char buffer[16384]; +}; + +static __int64 FileSize64(HANDLE file) +{ + LARGE_INTEGER position; + position.QuadPart=0; + position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart); + + if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) + return INVALID_FILE_SIZE; + else + return position.QuadPart; +} + +u_int64_t UnicodeGetFileLength(void *user) +{ + Win32_State *state = static_cast<Win32_State *>(user); + assert(state->handle); + return FileSize64(state->handle); +} + + + +int UnicodeSetPosition(void *user, u_int64_t position) +{ + Win32_State *state = static_cast<Win32_State *>(user); + assert(state->handle); + __int64 diff = position - state->position.QuadPart; + + if ((diff+state->read_offset) >= sizeof(state->buffer) + || (diff+state->read_offset) < 0) + { + CancelIo(state->handle); + state->io_active = 0; + state->read_offset = 0; + } + else if (diff) + state->read_offset += (DWORD)diff; + + state->position.QuadPart = position; + state->endOfFile = false; + return 0; +} + +int UnicodeGetPosition(void *user, u_int64_t *position) +{ + Win32_State *state = static_cast<Win32_State *>(user); + assert(state->handle); + *position = state->position.QuadPart; + return 0; +} + +static void DoRead(Win32_State *state) +{ + WaitForSingleObject(state->event, INFINITE); + state->overlapped.hEvent = state->event; + state->overlapped.Offset = state->position.LowPart; + state->overlapped.OffsetHigh = state->position.HighPart; + state->read_offset = 0; + ResetEvent(state->event); + ReadFile(state->handle, state->buffer, sizeof(state->buffer), NULL, &state->overlapped); + //int error = GetLastError();//ERROR_IO_PENDING = 997 + state->io_active=true; +} + +size_t UnicodeRead(void *user, void *buffer, size_t size) +{ + Win32_State *state = static_cast<Win32_State *>(user); + assert(state->handle); + size_t totalRead=0; + HANDLE file = state->handle; + if (!state->io_active) + { + DoRead(state); + } + + if (state->read_offset == sizeof(state->buffer)) + { + DoRead(state); + } + + while (size > (sizeof(state->buffer) - state->read_offset)) + { + DWORD bytesRead=0; + BOOL res = GetOverlappedResult(file, &state->overlapped, &bytesRead, TRUE); + if ((res && bytesRead != sizeof(state->buffer)) + || (!res && GetLastError() == ERROR_HANDLE_EOF)) + { + state->endOfFile = true; + } + + if (bytesRead > state->read_offset) + { + size_t bytesToCopy = bytesRead-state->read_offset; + memcpy(buffer, state->buffer + state->read_offset, bytesToCopy); + buffer=(uint8_t *)buffer + bytesToCopy; + totalRead+=bytesToCopy; + size-=bytesToCopy; + + + if (state->endOfFile) + return totalRead; + + state->position.QuadPart += bytesToCopy; + DoRead(state); + } + else + break; + } + + while (1) + { + DWORD bytesRead=0; + BOOL res = GetOverlappedResult(file, &state->overlapped, &bytesRead, FALSE); + if ((res && bytesRead != sizeof(state->buffer)) + || (!res && GetLastError() == ERROR_HANDLE_EOF)) + { + state->endOfFile = true; + } + if (bytesRead >= (size + state->read_offset)) + { + memcpy(buffer, state->buffer + state->read_offset, size); + state->read_offset += size; + totalRead+=size; + state->position.QuadPart += size; + break; + } + + if (state->endOfFile) + break; + + WaitForSingleObject(state->event, 10); // wait 10 milliseconds or when buffer is done, whichever is faster + } + + return totalRead; +} + +size_t UnicodeWrite(void *user, void *buffer, size_t size) +{ + Win32_State *state = static_cast<Win32_State *>(user); + DWORD written = 0; + assert(state->handle); + WriteFile(state->handle, buffer, size, &written, NULL); + return 0; +} + +int UnicodeEndOfFile(void *user) +{ + Win32_State *state = static_cast<Win32_State *>(user); + return state->endOfFile; +} + +int UnicodeClose(void *user) +{ + Win32_State *state = static_cast<Win32_State *>(user); + if (state->handle) + CloseHandle(state->handle); + + state->handle=0; + return 0; +} + +Virtual_IO UnicodeIO = +{ + UnicodeGetFileLength, + UnicodeSetPosition, + UnicodeGetPosition, + UnicodeRead, + UnicodeWrite, + UnicodeEndOfFile, + UnicodeClose, +}; + + +void *CreateUnicodeReader(const wchar_t *filename) +{ + HANDLE fileHandle = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); + if (fileHandle == INVALID_HANDLE_VALUE) + return 0; + + Win32_State *state = new Win32_State; + if (!state) + { + CloseHandle(fileHandle); + return 0; + } + state->endOfFile = false; + state->handle = fileHandle; + return state; +} + +void DestroyUnicodeReader(void *reader) +{ + if (reader) // need to check because of the cast + delete (Win32_State *)reader; +} diff --git a/Src/Plugins/Input/in_mp4/VirtualIO.h b/Src/Plugins/Input/in_mp4/VirtualIO.h new file mode 100644 index 00000000..796eff55 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/VirtualIO.h @@ -0,0 +1,17 @@ +#ifndef NULLSOFT_IN_MP4_VIRTUALIO_H +#define NULLSOFT_IN_MP4_VIRTUALIO_H +#include "main.h" +#include <api/service/svcs/svc_fileread.h> +#include <virtual_io.h> + +void *CreateReader(const wchar_t *url, HANDLE killswitch); +void DestroyReader(void *reader); +void StopReader(void *reader); +extern Virtual_IO HTTPIO; +extern Virtual_IO UnicodeIO; + +void *CreateUnicodeReader(const wchar_t *filename); +void DestroyUnicodeReader(void *reader); +int UnicodeClose(void *user); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/api.cpp b/Src/Plugins/Input/in_mp4/api.cpp new file mode 100644 index 00000000..9014c742 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/api.cpp @@ -0,0 +1,4 @@ +#include "api__in_mp4.h" + +api_downloadManager *WAC_API_DOWNLOADMANAGER =0; +api_threadpool *WASABI_API_THREADPOOL=0;
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/api__in_mp4.h b/Src/Plugins/Input/in_mp4/api__in_mp4.h new file mode 100644 index 00000000..cd83c8ea --- /dev/null +++ b/Src/Plugins/Input/in_mp4/api__in_mp4.h @@ -0,0 +1,22 @@ +#ifndef NULLSOFT_APIH +#define NULLSOFT_APIH + +#include "../Agave/Config/api_config.h" +extern api_config *AGAVE_API_CONFIG; + +#include <api/application/api_application.h> +#define WASABI_API_APP applicationApi + +#include <api/memmgr/api_memmgr.h> +extern api_memmgr *memmgrApi; +#define WASABI_API_MEMMGR memmgrApi + +#include "../Agave/Language/api_language.h" + +#include "../Components/wac_downloadManager/wac_downloadManager_api.h" + +#include "../nu/threadpool/api_threadpool.h" +extern api_threadpool *threadPoolApi; +#define WASABI_API_THREADPOOL threadPoolApi + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/config.cpp b/Src/Plugins/Input/in_mp4/config.cpp new file mode 100644 index 00000000..27750ccb --- /dev/null +++ b/Src/Plugins/Input/in_mp4/config.cpp @@ -0,0 +1,49 @@ +#include "main.h" +#include "api__in_mp4.h" +#include "../nu/AutoChar.h" +#include "resource.h" + +bool config_show_average_bitrate = true; + +INT_PTR CALLBACK ConfigProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) + { + case WM_INITDIALOG: + { + wchar_t exts[1024] = {0}; + GetPrivateProfileStringW(L"in_mp4", L"extensionlist", defaultExtensions, exts, 1024, m_ini); + SetDlgItemTextW(hwndDlg, IDC_EXTENSIONLIST, exts); + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_DEFAULT: + SetDlgItemTextW(hwndDlg, IDC_EXTENSIONLIST, defaultExtensions); + break; + case IDOK: + { + wchar_t exts[1024] = {0}; + GetDlgItemTextW(hwndDlg, IDC_EXTENSIONLIST, exts, 1024); + if (!_wcsicmp(exts, defaultExtensions)) // same as default? + WritePrivateProfileStringW(L"in_mp4", L"extensionlist", 0, m_ini); + else + WritePrivateProfileStringW(L"in_mp4", L"extensionlist", exts, m_ini); + free(mod.FileExtensions); + mod.FileExtensions = BuildExtensions(AutoChar(exts)); + EndDialog(hwndDlg, 0); + } + break; + case IDCANCEL: + EndDialog(hwndDlg, 1); + break; + } + break; + } + return 0; +} +void config(HWND hwndParent) +{ + WASABI_API_DIALOGBOXW(IDD_CONFIG, hwndParent, ConfigProc); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/in_mp4.rc b/Src/Plugins/Input/in_mp4/in_mp4.rc new file mode 100644 index 00000000..2ad421b2 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/in_mp4.rc @@ -0,0 +1,129 @@ +// 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_CONFIG DIALOGEX 0, 0, 215, 50 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "MP4 Configuration" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Extension List (semicolon delimited):",IDC_STATIC,4,4,208,10 + EDITTEXT IDC_EXTENSIONLIST,4,16,207,13,ES_AUTOHSCROLL + PUSHBUTTON "Set to Defaults",IDC_DEFAULT,4,33,60,13 + DEFPUSHBUTTON "OK",IDOK,107,33,50,13 + PUSHBUTTON "Cancel",IDCANCEL,161,33,50,13 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_CONFIG, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 211 + TOPMARGIN, 4 + BOTTOMMARGIN, 46 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_MPEG4_AUDIO_DECODER "Nullsoft MP4 Demuxer v%s" + 65535 "{F30C75C1-D284-4cd5-9CED-2BD9E7869438}" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_MPEG4_AUDIO_DECODER_OLD "Nullsoft MP4 Demuxer" + IDS_CANNOT_UPDATE_THE_FILE "Cannot update the file!" + IDS_ERROR "Error" + IDS_MP4_FILE "MP4 File" + IDS_FAMILY_STRING_M4A "MPEG-4 Audio File Format" + IDS_FAMILY_STRING_MPEG4 "MPEG-4 File Format" + IDS_FAMILY_STRING_M4V "MPEG-4 Video File Format" + IDS_FAMILY_STRING_QUICKTIME "Quicktime Movie" + IDS_FAMILY_STRING_3GPP "3GPP File Format" + IDS_FAMILY_STRING_FLV "Flash MPEG-4 Video" + IDS_ABOUT_TEXT "%s\n© 2005-2023 Winamp SA\n\nBuild date: %s\n\nUses the libmp4v2 library\nfrom the MPEG4IP package\n(http://mpeg4ip.sourceforge.net)" + IDS_AUDIO_INFO "Audio:\n\t%S\n\t%u kbps\n" + IDS_VIDEO_INFO "Video:\n\t%S\n\t%u kbps\n" +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_mp4/in_mp4.sln b/Src/Plugins/Input/in_mp4/in_mp4.sln new file mode 100644 index 00000000..3521de5f --- /dev/null +++ b/Src/Plugins/Input/in_mp4/in_mp4.sln @@ -0,0 +1,67 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29424.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_mp4", "in_mp4.vcxproj", "{9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}" + ProjectSection(ProjectDependencies) = postProject + {F6962367-6A6C-4E7D-B661-B767E904F451} = {F6962367-6A6C-4E7D-B661-B767E904F451} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libmp4v2", "..\libmp4v2\libmp4v2.vcxproj", "{EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "aacdec-mft", "..\aacdec-mft\aacdec-mft.vcxproj", "{F6962367-6A6C-4E7D-B661-B767E904F451}" + ProjectSection(ProjectDependencies) = postProject + {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsutil", "..\nsutil\nsutil.vcxproj", "{DABE6307-F8DD-416D-9DAC-673E2DECB73F}" +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 + {9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}.Debug|Win32.ActiveCfg = Debug|Win32 + {9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}.Debug|Win32.Build.0 = Debug|Win32 + {9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}.Debug|x64.ActiveCfg = Debug|x64 + {9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}.Debug|x64.Build.0 = Debug|x64 + {9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}.Release|Win32.ActiveCfg = Release|Win32 + {9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}.Release|Win32.Build.0 = Release|Win32 + {9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}.Release|x64.ActiveCfg = Release|x64 + {9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}.Release|x64.Build.0 = Release|x64 + {EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}.Debug|Win32.ActiveCfg = Debug|Win32 + {EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}.Debug|Win32.Build.0 = Debug|Win32 + {EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}.Debug|x64.ActiveCfg = Debug|x64 + {EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}.Debug|x64.Build.0 = Debug|x64 + {EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}.Release|Win32.ActiveCfg = Release|Win32 + {EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}.Release|Win32.Build.0 = Release|Win32 + {EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}.Release|x64.ActiveCfg = Release|x64 + {EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}.Release|x64.Build.0 = Release|x64 + {F6962367-6A6C-4E7D-B661-B767E904F451}.Debug|Win32.ActiveCfg = Debug|Win32 + {F6962367-6A6C-4E7D-B661-B767E904F451}.Debug|Win32.Build.0 = Debug|Win32 + {F6962367-6A6C-4E7D-B661-B767E904F451}.Debug|x64.ActiveCfg = Debug|x64 + {F6962367-6A6C-4E7D-B661-B767E904F451}.Debug|x64.Build.0 = Debug|x64 + {F6962367-6A6C-4E7D-B661-B767E904F451}.Release|Win32.ActiveCfg = Release|Win32 + {F6962367-6A6C-4E7D-B661-B767E904F451}.Release|Win32.Build.0 = Release|Win32 + {F6962367-6A6C-4E7D-B661-B767E904F451}.Release|x64.ActiveCfg = Release|x64 + {F6962367-6A6C-4E7D-B661-B767E904F451}.Release|x64.Build.0 = Release|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.ActiveCfg = Debug|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.Build.0 = Debug|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.ActiveCfg = Debug|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.Build.0 = Debug|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.ActiveCfg = Release|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.Build.0 = Release|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.ActiveCfg = Release|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FC646532-2050-40A5-A2AB-F699F1C071C4} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_mp4/in_mp4.vcxproj b/Src/Plugins/Input/in_mp4/in_mp4.vcxproj new file mode 100644 index 00000000..ecaf4b55 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/in_mp4.vcxproj @@ -0,0 +1,301 @@ +<?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>{9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}</ProjectGuid> + <RootNamespace>in_mp4</RootNamespace> + <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" 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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|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> + <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> + <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> + <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>.;..\..\..\external_dependencies\libmp4v2;..\..\..\Wasabi;..\..\..\external_dependencies\libmp4v2\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_DEBUG;WIN32;_WINDOWS;_USRDLL;in_mp4_EXPORTS;UNICODE_INPUT_PLUGIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <BrowseInformation>true</BrowseInformation> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4018;4244;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>winmm.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>libmp4v2.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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>.;..\..\..\libmp4v2;..\..\..\Wasabi;..\..\..\libmp4v2\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_DEBUG;WIN64;_WINDOWS;_USRDLL;in_mp4_EXPORTS;UNICODE_INPUT_PLUGIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <BrowseInformation>true</BrowseInformation> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4018;4244;4267;4312;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>winmm.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>libmp4v2.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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> + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\external_dependencies\libmp4v2;..\..\..\Wasabi;..\..\..\external_dependencies\libmp4v2\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>NDEBUG;WIN32;_WINDOWS;_USRDLL;in_mp4_EXPORTS;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4018;4244;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>winmm.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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> + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\libmp4v2;..\..\..\Wasabi;..\..\..\libmp4v2\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>NDEBUG;WIN64;_WINDOWS;_USRDLL;in_mp4_EXPORTS;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4018;4244;4267;4312;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>winmm.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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> + <ProjectReference Include="..\..\..\external_dependencies\libmp4v2\libmp4v2.vcxproj"> + <Project>{efb9b882-6a8b-463d-a8e3-a2807afc5d9f}</Project> + </ProjectReference> + <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj"> + <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\..\..\nu\bitbuffer.cpp" /> + <ClCompile Include="..\..\..\nu\GaplessRingBuffer.cpp" /> + <ClCompile Include="..\..\..\nu\RingBuffer.cpp" /> + <ClCompile Include="..\..\..\nu\SpillBuffer.cpp" /> + <ClCompile Include="AlbumArt.cpp" /> + <ClCompile Include="api.cpp" /> + <ClCompile Include="config.cpp" /> + <ClCompile Include="ExtendedInfo.cpp" /> + <ClCompile Include="ExtendedRead.cpp" /> + <ClCompile Include="infobox.cpp" /> + <ClCompile Include="Main.cpp" /> + <ClCompile Include="mp4.cpp" /> + <ClCompile Include="mpeg4audio.cpp" /> + <ClCompile Include="mpeg4video.cpp" /> + <ClCompile Include="PlayThread.cpp" /> + <ClCompile Include="RawMediaReader.cpp" /> + <ClCompile Include="Stopper.cpp" /> + <ClCompile Include="VideoThread.cpp" /> + <ClCompile Include="VirtualIO.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\nu\GaplessRingBuffer.h" /> + <ClInclude Include="..\..\..\nu\RingBuffer.h" /> + <ClInclude Include="..\..\..\nu\VideoClock.h" /> + <ClInclude Include="AlbumArt.h" /> + <ClInclude Include="api__in_mp4.h" /> + <ClInclude Include="AudioSample.h" /> + <ClInclude Include="Main.h" /> + <ClInclude Include="mpeg4audio.h" /> + <ClInclude Include="mpeg4video.h" /> + <ClInclude Include="RawMediaReader.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="Stopper.h" /> + <ClInclude Include="VideoThread.h" /> + <ClInclude Include="VirtualIO.h" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_mp4.rc" /> + </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_mp4/in_mp4.vcxproj.filters b/Src/Plugins/Input/in_mp4/in_mp4.vcxproj.filters new file mode 100644 index 00000000..e6d151f1 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/in_mp4.vcxproj.filters @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="AlbumArt.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="api.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="config.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedRead.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="infobox.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="mp4.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="mpeg4audio.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="mpeg4video.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="PlayThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="RawMediaReader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Stopper.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="VideoThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="VirtualIO.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\bitbuffer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\GaplessRingBuffer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\RingBuffer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\SpillBuffer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="AlbumArt.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="api__in_mp4.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="AudioSample.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Main.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="mpeg4audio.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="mpeg4video.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="RawMediaReader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Stopper.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="VideoThread.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="VirtualIO.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\GaplessRingBuffer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\RingBuffer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\VideoClock.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{90f60180-1dcd-423f-9b3c-19e90036737f}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{d93a6e62-1a2b-4fe9-bca8-34c3fb1c77d7}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{5f0d4520-0772-4236-af42-4348bc4c5561}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_mp4.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/infobox.cpp b/Src/Plugins/Input/in_mp4/infobox.cpp new file mode 100644 index 00000000..c9630997 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/infobox.cpp @@ -0,0 +1,20 @@ +#include "main.h" +#include "resource.h" +#include "../winamp/in2.h" +#include "api__in_mp4.h" + +int infoDlg(const wchar_t *fn, HWND hwnd) +{ // this has been made obsolete by the below. + return 0; +} + +extern "C" +{ + // return 1 if you want winamp to show it's own file info dialogue, 0 if you want to show your own (via In_Module.InfoBox) + // if returning 1, remember to implement winampGetExtendedFileInfo("formatinformation")! + __declspec(dllexport) int winampUseUnifiedFileInfoDlg(const wchar_t * fn) + { + return 1; + } + // no advanced pane in this plugin :) +}; diff --git a/Src/Plugins/Input/in_mp4/mp4.cpp b/Src/Plugins/Input/in_mp4/mp4.cpp new file mode 100644 index 00000000..0c615312 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/mp4.cpp @@ -0,0 +1,98 @@ +#include "main.h" +#include "mpeg4audio.h" +#include "api__in_mp4.h" +#include <api/service/waservicefactory.h> +#include <api/service/services.h> +//#include "JPEGDecoder.h" +//#include "MPEG4VideoDecoder.h" +//#include "AVCDecoder.h" +#include "../nu/bitbuffer.h" +#include <assert.h> + +void ConfigureDecoderASC(MP4FileHandle file, MP4TrackId track, MP4AudioDecoder *decoder) +{ + unsigned char *buffer = NULL; + unsigned __int32 buffer_size = 0; + + // TODO: HUGE hack + const char *location = decoder->GetCodecInfoString(); + if (location) + MP4GetTrackBytesProperty(file, track, location , (unsigned __int8 **)&buffer, &buffer_size); + else + MP4GetTrackESConfiguration(file, track, (unsigned __int8 **)&buffer, &buffer_size); + + if (buffer) + { + decoder->AudioSpecificConfiguration(buffer, buffer_size); + MP4Free(buffer); + } +} + +bool CreateDecoder(MP4FileHandle file, MP4TrackId track, MP4AudioDecoder *&decoder, waServiceFactory *&serviceFactory) +{ + const char *media_data_name = MP4GetTrackMediaDataName(file, track); + u_int8_t audioType = MP4GetTrackEsdsObjectTypeId(file, track); + u_int8_t mpeg4Type = MP4GetTrackAudioMpeg4Type(file, track); + if (!media_data_name) + media_data_name = "mp4a"; // let's assume it's AAC if nothing else is said + waServiceFactory *sf = 0; + + int n = 0; + while (sf = mod.service->service_enumService(WaSvc::MP4AUDIODECODER, n++)) + { + MP4AudioDecoder * dec = static_cast<MP4AudioDecoder *>(sf->getInterface()); + if (dec && dec->CanHandleCodec(media_data_name) + && (!audioType || dec->CanHandleType(audioType)) + && (!mpeg4Type || dec->CanHandleMPEG4Type(mpeg4Type)) + /*&& dec->Open() == MP4_SUCCESS*/) + { + //ConfigureDecoderASC(file, track, dec); + decoder = dec; + serviceFactory = sf; + return true; + } + + sf->releaseInterface(dec); + + } + return false; +} + +bool CreateVideoDecoder(MP4FileHandle file, MP4TrackId track, MP4VideoDecoder *&decoder, waServiceFactory *&serviceFactory) +{ + const char *media_data_name = MP4GetTrackMediaDataName(file, track); + // TODO check this is ok to disable... + //u_int8_t audioType = MP4GetTrackEsdsObjectTypeId(file, track); + //u_int8_t profileLevel = MP4GetVideoProfileLevel(file, track); + if (!media_data_name) + return false; + + waServiceFactory *sf = 0; + int n = 0; + while (sf = mod.service->service_enumService(WaSvc::MP4VIDEODECODER, n++)) + { + MP4VideoDecoder *dec = static_cast<MP4VideoDecoder *>(sf->getInterface()); + if (dec && dec->CanHandleCodec(media_data_name) + /*&& dec->Open() == MP4_SUCCESS*/) + { + decoder = dec; + serviceFactory = sf; + return true; + } + sf->releaseInterface(dec); + } + /* + if (!strcmp(media_data_name, "mp4v")) + { + // TODO: write a way to get the binary data out of an atom + uint8_t *buffer; + uint32_t buffer_size = 0; + MP4GetTrackESConfiguration(file, track, &buffer, &buffer_size); + + decoder = new MPEG4VideoDecoder(0,0);//buffer, buffer_size); + return true; + } +*/ + + return false; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/mpeg4audio.cpp b/Src/Plugins/Input/in_mp4/mpeg4audio.cpp new file mode 100644 index 00000000..d8be9a86 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/mpeg4audio.cpp @@ -0,0 +1 @@ +#include "mpeg4audio.h"
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/mpeg4audio.h b/Src/Plugins/Input/in_mp4/mpeg4audio.h new file mode 100644 index 00000000..ff629b16 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/mpeg4audio.h @@ -0,0 +1,208 @@ +#ifndef NULLSOFT_MPEG4AUDIOH +#define NULLSOFT_MPEG4AUDIOH +#include "../external_dependencies/libmp4v2/mp4.h" +#include <bfc/dispatch.h> +#include <bfc/platform/types.h> +#include <api/service/services.h> +enum +{ + MP4_SUCCESS = 0, + MP4_FAILURE = 1, + + MP4_GETOUTPUTPROPERTIES_NEED_MORE_INPUT = 2, + MP4_NEED_MORE_INPUT = 2, + + MP4_GETCURRENTBITRATE_UNKNOWN = 2, // unable to calculate (e.g. VBR for CT's decoder) + + MP4_OUTPUTFRAMESIZE_VARIABLE = 2, // don't know if any codecs do this + + MP4_TYPE_MPEG1_AUDIO = 0x6B, + MP4_TYPE_MPEG2_AUDIO = 0x69, + MP4_TYPE_MPEG2_AAC_MAIN_AUDIO = 0x66, + MP4_TYPE_MPEG2_AAC_LC_AUDIO = 0x67, + MP4_TYPE_MPEG2_AAC_SSR_AUDIO = 0x68, + MP4_TYPE_MPEG4_AUDIO = 0x40, + MP4_TYPE_PRIVATE_AUDIO = 0xC0, + MP4_TYPE_PCM16_LITTLE_ENDIAN_AUDIO = 0xE0, + MP4_TYPE_VORBIS_AUDIO = 0xE1, + MP4_TYPE_AC3_AUDIO = 0xE2, + MP4_TYPE_ALAW_AUDIO = 0xE3, + MP4_TYPE_ULAW_AUDIO = 0xE4, + MP4_TYPE_G723_AUDIO = 0xE5, + MP4_TYPE_PCM16_BIG_ENDIAN_AUDIO = 0xE6, + + /* MP4 MPEG-4 Audio types from 14496-3 Table 1.5.1 */ + MP4_MPEG4_TYPE_AAC_MAIN_AUDIO = 1, + MP4_MPEG4_TYPE_AAC_LC_AUDIO = 2, + MP4_MPEG4_TYPE_AAC_SSR_AUDIO = 3, + MP4_MPEG4_TYPE_AAC_LTP_AUDIO = 4, + MP4_MPEG4_TYPE_AAC_HE_AUDIO = 5, + MP4_MPEG4_TYPE_AAC_SCALABLE_AUDIO = 6, + MP4_MPEG4_TYPE_CELP_AUDIO = 8, + MP4_MPEG4_TYPE_HVXC_AUDIO = 9, + MP4_MPEG4_TYPE_TTSI_AUDIO = 12, + MP4_MPEG4_TYPE_MAIN_SYNTHETIC_AUDIO = 13, + MP4_MPEG4_TYPE_WAVETABLE_AUDIO = 14, + MP4_MPEG4_TYPE_MIDI_AUDIO = 15, + MP4_MPEG4_TYPE_ALGORITHMIC_FX_AUDIO = 16, + MP4_MPEG4_TYPE_PARAMETRIC_STEREO=29, + MP4_MPEG4_ALS_AUDIO=31, + MP4_MPEG4_LAYER1_AUDIO = 32, + MP4_MPEG4_LAYER2_AUDIO= 33, + MP4_MPEG4_LAYER3_AUDIO= 34, + + MP4_MPEG4_SLS_AUDIO=37, +}; + +class MP4AudioDecoder : public Dispatchable +{ +protected: + MP4AudioDecoder() {} + ~MP4AudioDecoder() {} + +public: + static FOURCC getServiceType() { return WaSvc::MP4AUDIODECODER; } + int Open(); + int OpenEx(size_t bits, size_t maxChannels, bool useFloat); + int OpenMP4(MP4FileHandle mp4_file, MP4TrackId mp4_track, size_t output_bits, size_t maxChannels, bool useFloat); + int AudioSpecificConfiguration(void *buffer, size_t buffer_size); // reads ASC block from mp4 file + int GetCurrentBitrate(unsigned int *bitrate); + int OutputFrameSize(size_t *frameSize); // in Frames + int GetOutputProperties(unsigned int *sampleRate, unsigned int *channels, unsigned int *bitsPerSample); // can return an error code for "havn't locked to stream yet" + int GetOutputPropertiesEx(unsigned int *sampleRate, unsigned int *channels, unsigned int *bitsPerSample, bool *isFloat); + int DecodeSample(void *inputBuffer, size_t inputBufferBytes, void *outputBuffer, size_t *outputBufferBytes); + void Flush(); + void Close(); + const char *GetCodecInfoString(); + int CanHandleCodec(const char *codecName); + int CanHandleType(uint8_t type); + int CanHandleMPEG4Type(uint8_t type); + int SetGain(float gain); + int RequireChunks(); // return 1 if your decoder wants to read whole chunks rather than samples + void EndOfStream(); + +public: + DISPATCH_CODES + { + MPEG4_AUDIO_OPEN = 10, + MPEG4_AUDIO_OPEN_EX = 11, + MPEG4_AUDIO_OPENMP4 = 12, + MPEG4_AUDIO_ASC = 20, + MPEG4_AUDIO_BITRATE = 30, + MPEG4_AUDIO_FRAMESIZE = 40, + MPEG4_AUDIO_OUTPUTINFO = 50, + MPEG4_AUDIO_OUTPUTINFO_EX = 51, + MPEG4_AUDIO_DECODE = 60, + MPEG4_AUDIO_FLUSH = 70, + MPEG4_AUDIO_CLOSE = 80, + MPEG4_AUDIO_CODEC_INFO_STRING = 90, + MPEG4_AUDIO_HANDLES_CODEC = 100, + MPEG4_AUDIO_HANDLES_TYPE = 110, + MPEG4_AUDIO_HANDLES_MPEG4_TYPE = 120, + MPEG4_AUDIO_SET_GAIN=130, + MPEG4_AUDIO_REQUIRE_CHUNKS = 140, + MPEG4_END_OF_STREAM = 150, + }; +}; + +inline int MP4AudioDecoder::Open() +{ + return _call(MPEG4_AUDIO_OPEN, (int)MP4_FAILURE); +} + +inline int MP4AudioDecoder::OpenEx(size_t bits, size_t maxChannels, bool useFloat) +{ + void *params[3] = { &bits, &maxChannels, &useFloat}; + int retval; + if (_dispatch(MPEG4_AUDIO_OPEN_EX, &retval, params, 3)) + return retval; + else + return Open(); +} + +inline int MP4AudioDecoder::OpenMP4(MP4FileHandle mp4_file, MP4TrackId mp4_track, size_t output_bits, size_t maxChannels, bool useFloat) +{ + void *params[5] = { &mp4_file, &mp4_track, &output_bits, &maxChannels, &useFloat}; + int retval; + if (_dispatch(MPEG4_AUDIO_OPENMP4, &retval, params, 5)) + return retval; + else + return OpenEx(output_bits, maxChannels, useFloat); +} + +inline int MP4AudioDecoder::AudioSpecificConfiguration(void *buffer, size_t buffer_size) +{ + return _call(MPEG4_AUDIO_ASC, (int)MP4_FAILURE, buffer, buffer_size); +} +inline int MP4AudioDecoder::GetCurrentBitrate(unsigned int *bitrate) +{ + return _call(MPEG4_AUDIO_BITRATE, (int)MP4_FAILURE, bitrate); +} +inline int MP4AudioDecoder::OutputFrameSize(size_t *frameSize) +{ + return _call(MPEG4_AUDIO_FRAMESIZE, (int)MP4_FAILURE, frameSize); +} +inline int MP4AudioDecoder::GetOutputProperties(unsigned int *sampleRate, unsigned int *channels, unsigned int *bitsPerSample) +{ + return _call(MPEG4_AUDIO_OUTPUTINFO, (int)MP4_FAILURE, sampleRate, channels, bitsPerSample); +} +inline int MP4AudioDecoder::GetOutputPropertiesEx(unsigned int *sampleRate, unsigned int *channels, unsigned int *bitsPerSample, bool *useFloat) +{ + void *params[4] = { &sampleRate, &channels, &bitsPerSample, &useFloat}; + int retval; + if (_dispatch(MPEG4_AUDIO_OUTPUTINFO_EX, &retval, params, 4)) + return retval; + else + { + *useFloat=false; + return GetOutputProperties(sampleRate, channels, bitsPerSample); + } +// return _call(MPEG4_AUDIO_OUTPUTINFO_EX, (int)MP4_FAILURE, sampleRate, channels, bitsPerSample); +} +inline int MP4AudioDecoder::DecodeSample(void *inputBuffer, size_t inputBufferBytes, void *outputBuffer, size_t *outputBufferBytes) +{ + return _call(MPEG4_AUDIO_DECODE, (int)MP4_FAILURE, inputBuffer, inputBufferBytes, outputBuffer, outputBufferBytes); +} + +inline void MP4AudioDecoder::Flush() +{ + _voidcall(MPEG4_AUDIO_FLUSH); +} +inline void MP4AudioDecoder::Close() +{ + _voidcall(MPEG4_AUDIO_CLOSE); +} +inline const char *MP4AudioDecoder::GetCodecInfoString() +{ + return _call(MPEG4_AUDIO_CODEC_INFO_STRING, (const char *)0); +} + +inline int MP4AudioDecoder::CanHandleCodec(const char *codecName) +{ + return _call(MPEG4_AUDIO_HANDLES_CODEC, (int)0, codecName); +} + +inline int MP4AudioDecoder::CanHandleType(uint8_t type) +{ + return _call(MPEG4_AUDIO_HANDLES_TYPE, (int)0, type); +} +inline int MP4AudioDecoder::CanHandleMPEG4Type(uint8_t type) +{ + return _call(MPEG4_AUDIO_HANDLES_MPEG4_TYPE, (int)0, type); +} + +inline int MP4AudioDecoder::SetGain(float gain) +{ + return _call(MPEG4_AUDIO_SET_GAIN, (int)MP4_FAILURE, gain); +} + +inline int MP4AudioDecoder::RequireChunks() +{ + return _call(MPEG4_AUDIO_REQUIRE_CHUNKS, (int)0); +} + +inline void MP4AudioDecoder::EndOfStream() +{ + _voidcall(MPEG4_END_OF_STREAM); +} +#endif diff --git a/Src/Plugins/Input/in_mp4/mpeg4video.cpp b/Src/Plugins/Input/in_mp4/mpeg4video.cpp new file mode 100644 index 00000000..aff2c799 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/mpeg4video.cpp @@ -0,0 +1 @@ +#include "mpeg4video.h"
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/mpeg4video.h b/Src/Plugins/Input/in_mp4/mpeg4video.h new file mode 100644 index 00000000..89b56a96 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/mpeg4video.h @@ -0,0 +1,92 @@ +#ifndef NULLSOFT_MPEG4VIDEO_H +#define NULLSOFT_MPEG4VIDEO_H +#include "../external_dependencies/libmp4v2/mp4.h" +#include <bfc/dispatch.h> +#include <api/service/services.h> + +enum +{ + MP4_VIDEO_SUCCESS = 0, + MP4_VIDEO_FAILURE = 1, + MP4_VIDEO_OUTPUT_FORMAT_CHANGED = -1, // succeeded, but call GetOutputFormat again! + MP4_VIDEO_AGAIN = -2, +}; + +class MP4VideoDecoder : public Dispatchable +{ +protected: + MP4VideoDecoder() {} + ~MP4VideoDecoder() {} + +public: + static FOURCC getServiceType() { return WaSvc::MP4VIDEODECODER; } + int Open(MP4FileHandle mp4_file, MP4TrackId mp4_track); + int GetOutputFormat(int *x, int *y, int *color_format, double *aspect_ratio); + int DecodeSample(const void *inputBuffer, size_t inputBufferBytes, MP4Timestamp timestamp); + void Flush(); + void Close(); + int CanHandleCodec(const char *codecName); // return 0 for no, anything else for yes + int GetPicture(void **data, void **decoder_data, MP4Timestamp *timestamp); + void FreePicture(void *data, void *decoder_data); + void HurryUp(int state); + + DISPATCH_CODES + { + MPEG4_VIDEO_OPEN = 11, + MPEG4_VIDEO_GETOUTPUTFORMAT = 21, + MPEG4_VIDEO_DECODE = 31, + MPEG4_VIDEO_FLUSH = 40, + MPEG4_VIDEO_CLOSE = 50, + MPEG4_VIDEO_HANDLES_CODEC = 60, + MPEG4_VIDEO_GET_PICTURE = 70, + MPEG4_VIDEO_FREE_PICTURE = 80, + MPEG4_VIDEO_HURRY_UP = 90, + }; + +}; + +inline int MP4VideoDecoder::Open(MP4FileHandle mp4_file, MP4TrackId mp4_track) +{ + return _call(MPEG4_VIDEO_OPEN, (int)MP4_VIDEO_FAILURE, mp4_file, mp4_track); +} + +inline int MP4VideoDecoder::GetOutputFormat(int *x, int *y, int *color_format, double *aspect_ratio) +{ + return _call(MPEG4_VIDEO_GETOUTPUTFORMAT, (int)MP4_VIDEO_FAILURE, x, y, color_format, aspect_ratio); +} + +inline int MP4VideoDecoder::DecodeSample(const void *inputBuffer, size_t inputBufferBytes, MP4Timestamp timestamp) +{ + return _call(MPEG4_VIDEO_DECODE, (int)MP4_VIDEO_FAILURE, inputBuffer, inputBufferBytes, timestamp); +} + +inline void MP4VideoDecoder::Flush() +{ + _voidcall(MPEG4_VIDEO_FLUSH); +} + +inline void MP4VideoDecoder::Close() +{ + _voidcall(MPEG4_VIDEO_CLOSE); +} + +inline int MP4VideoDecoder::CanHandleCodec(const char *codecName) +{ + return _call(MPEG4_VIDEO_HANDLES_CODEC, (int)0, codecName); +} + +inline int MP4VideoDecoder::GetPicture(void **data, void **decoder_data, MP4Timestamp *timestamp) +{ + return _call(MPEG4_VIDEO_GET_PICTURE, (int)MP4_VIDEO_FAILURE, data, decoder_data, timestamp); +} + +inline void MP4VideoDecoder::FreePicture(void *data, void *decoder_data) +{ + _voidcall(MPEG4_VIDEO_FREE_PICTURE, data, decoder_data); +} + +inline void MP4VideoDecoder::HurryUp(int state) +{ + _voidcall(MPEG4_VIDEO_HURRY_UP, state); +} +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/resource.h b/Src/Plugins/Input/in_mp4/resource.h new file mode 100644 index 00000000..b70efb2b --- /dev/null +++ b/Src/Plugins/Input/in_mp4/resource.h @@ -0,0 +1,61 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by in_mp4.rc +// +#define IDS_NULLSOFT_MPEG4_AUDIO_DECODER_OLD 0 +#define IDS_CANNOT_UPDATE_THE_FILE 1 +#define IDS_ERROR 2 +#define IDS_ABOUT_STRING 3 +#define IDS_ABOUT_TITLE 4 +#define IDS_STRING5 5 +#define IDS_MP4_FILE 5 +#define IDS_FAMILY_STRING_M4A 6 +#define IDS_FAMILY_STRING_MPEG4 7 +#define IDS_FAMILY_STRING_M4V 8 +#define IDS_FAMILY_STRING_QUICKTIME 9 +#define IDS_FAMILY_STRING_3GPP 10 +#define IDS_FAMILY_STRING_FLV 11 +#define IDS_ABOUT_TEXT 12 +#define IDS_AUDIO_INFO 13 +#define IDS_VIDEO_INFO 14 +#define IDD_CONFIG 102 +#define IDC_EDIT1 1000 +#define IDC_CHECK2 1002 +#define IDC_DEINT 1002 +#define IDC_SLIDER1 1003 +#define IDC_DVDCACHE 1005 +#define IDC_CACHE 1006 +#define IDD_NAME 1007 +#define IDC_NAME 1008 +#define IDC_ARTIST 1009 +#define IDC_COMPOSER 1010 +#define IDC_ALBUM 1011 +#define IDC_COMMENTS 1012 +#define IDC_GENRE 1013 +#define IDC_YEAR 1014 +#define IDC_TRACK1 1015 +#define IDC_TRACK2 1016 +#define IDC_DISC1 1017 +#define IDC_DISC2 1018 +#define IDC_ALBUM_ARTIST 1019 +#define IDC_INFOTEXT 1023 +#define IDC_COMPILATION 1024 +#define IDC_BPM 1026 +#define IDC_EDIT2 1027 +#define IDC_TRACK_GAIN 1027 +#define IDC_ALBUM_GAIN 1028 +#define IDC_EXTENSIONLIST 1028 +#define IDC_BUTTON1 1029 +#define IDC_DEFAULT 1029 +#define IDS_NULLSOFT_MPEG4_AUDIO_DECODER 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 15 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1030 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_mp4/version.rc2 b/Src/Plugins/Input/in_mp4/version.rc2 new file mode 100644 index 00000000..a60cbff2 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 2,70,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", "2,70,0,0" + VALUE "InternalName", "Nullsoft MP4 Demuxer" + VALUE "LegalCopyright", "Copyright © 2005-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_mp4.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_nsv/BigLib.cpp b/Src/Plugins/Input/in_nsv/BigLib.cpp new file mode 100644 index 00000000..5e8c541f --- /dev/null +++ b/Src/Plugins/Input/in_nsv/BigLib.cpp @@ -0,0 +1,1428 @@ +#ifdef WINAMPX +#define WIN32_LEAN_AND_MEAN 1 +#include <windows.h> +#include "stdio.h" +#include "proxydt.h" +#include <winsock2.h> +#include ".\ws2tcpip.h" +#include ".\wininet.h" +#include "../jnetlib/jnetlib.h" + +#include <urlmon.h> + + +extern void SendMetadata( char *data, int arg ); +HRESULT JNetLibDownloadToFile(LPVOID lpUnused1, LPSTR lpWPADLocation, LPSTR lpTempFile, LPVOID lpUnused2, LPVOID lpUnused3); +enum { + BK_UNKNOWN = 0, + BK_IE4 = 1, + BK_NETSCAPE4 = 2, + BK_NETSCAPE6 = 3, + BK_MOZILLA = 4, + BK_FIREFOX = 5 +}; + +// browser info struct +typedef struct{ + LPSTR lpName; + BOOL bSupported; +}BK_INFO; + +// browser info +BK_INFO BrowserInfo[] = { + "Unknown", FALSE, + "IE 4.0+", TRUE, + "Netscape 4 or 5", FALSE, + "Netscape 6+", TRUE, + "Mozilla", TRUE, + "Firefox", TRUE +}; + +// Global variables +int gBrowserKind = BK_UNKNOWN; + +int gTryAuto = 1; + +// Exported C functions +extern "C" BOOL ProxyInit(); +extern "C" void ProxyDeInit(); +extern "C" int ResolvProxyFromURL(LPSTR lpURL, LPSTR lpHostname, LPSTR lpDest); + +// Global C functions +BOOL IsIEProxySet(); +int GetIESettings(); +int ResolveURL_IE(LPSTR lpURL, LPSTR lpHostname, LPSTR lpIPAddress, int sizeof_address, int *pnPort); +BOOL IsFirefoxProxySet(); +int GetFirefoxSettings(); +int ResolveURL_Firefox(LPSTR lpURL, LPSTR lpHostname, LPSTR lpIPAddress, int sizeof_address, int *pnPort); +BOOL IsMozillaProxySet(); +int GetMozillaSettings(); +int ResolveURL_Mozilla(LPSTR lpURL, LPSTR lpHostname, LPSTR lpIPAddress, int sizeof_address, int *pnPort); +int GetDefaultBrowser(); +int ReadWPADFile(LPSTR lpWPADLocation, LPSTR pIPAddress, int *pnPort); +int GetFirefoxOrMozillaSettings(BOOL bFirefox); +BOOL IsFirefoxOrMozillaProxySet(BOOL bFirefox); +int ResolveURL_MozillaOrFirefox(BOOL bFirefox, LPSTR lpURL, LPSTR lpHostname, LPSTR lpIPAddress, int sizeof_address, int *pnPort); + +// exported functions +extern "C" BOOL ProxyInit() +{ + BOOL bRet; + + bRet = FALSE; + gBrowserKind = GetDefaultBrowser(); + + switch(gBrowserKind) { + case BK_IE4: + bRet = IsIEProxySet(); + break; + + case BK_MOZILLA: + bRet = IsMozillaProxySet(); + + break; + + case BK_FIREFOX: + bRet = IsFirefoxProxySet(); + break; + } + + return bRet; +} + +extern "C" void ProxyDeInit() +{ +} + +extern "C" int ResolvProxyFromURL(LPSTR lpURL, LPSTR lpHostname, LPSTR lpDest) +{ + // lpURL = URL to resolve + // lpHostname = hostname + // lpDest = where to store the result, such as "www.proxyserver.com:8080" + char szIPAddress[MAX_PATH] = {0}; + int ret, nPort=0; + + lpDest[0]=0; + + if(lpURL && lpHostname && lpDest) { + switch(gBrowserKind) { + case BK_IE4: + ret = ResolveURL_IE(lpURL, lpHostname, szIPAddress, sizeof(szIPAddress), &nPort); + break; + + case BK_MOZILLA: + ret = ResolveURL_Mozilla(lpURL, lpHostname, szIPAddress, sizeof(szIPAddress), &nPort); + break; + + case BK_FIREFOX: + ret = ResolveURL_Firefox(lpURL, lpHostname, szIPAddress, sizeof(szIPAddress), &nPort); + break; + } + + if(ret == 0) { + if ( szIPAddress[0] ) + { + wsprintf(lpDest, "%s:%d", szIPAddress, nPort); + return 1; + } + else return 0; + } + else return 0; + } + else return -1; + +} + +int GetDefaultBrowser() +{ + DWORD dwSize, dwType; + TCHAR valueBuf[MAX_PATH] = {0}; + DWORD valueSize = sizeof(valueBuf); + HKEY hKey; + long lRet; + + + memset(valueBuf, 0, sizeof(valueBuf)); + lRet = RegOpenKeyEx(HKEY_CLASSES_ROOT, "http\\shell\\open\\ddeexec\\Application", 0, KEY_READ, &hKey); + if (lRet == ERROR_SUCCESS) { + dwSize = valueSize; + lRet = RegQueryValueEx(hKey, "", NULL, &dwType, (LPBYTE)valueBuf, &dwSize); + if(lRet == ERROR_SUCCESS && dwType == REG_SZ) { + if (_tcsicmp(_T("NSShell"), valueBuf) == 0) { //NS 4.x + return BK_NETSCAPE4; + } else if (_tcsicmp(_T("IExplore"), valueBuf) == 0) { //IE 4+ + return BK_IE4; + } else if (_tcsicmp(_T("Mozilla"), valueBuf) == 0) { //Mozilla + return BK_MOZILLA; + } else if (_tcsicmp(_T("Firefox"), valueBuf) == 0) { //Firefox + return BK_FIREFOX; + } + } + } + RegCloseKey(hKey); + + lRet = RegOpenKeyEx(HKEY_CLASSES_ROOT, "http\\shell\\open\\command", 0, KEY_READ, &hKey); + if(lRet == ERROR_SUCCESS) { + dwSize = valueSize; + lRet = RegQueryValueEx(hKey, "", NULL, &dwType, (LPBYTE)valueBuf, &dwSize); + if(lRet == ERROR_SUCCESS && dwType == REG_SZ) { + if(strstr(valueBuf, "MOZILLA")) { + return BK_MOZILLA; + } + if(strstr(valueBuf, "NETSCAPE")) { + return BK_MOZILLA; + } + if(strstr(valueBuf, "FIREFOX")) { + return BK_FIREFOX; + } + } + } + RegCloseKey(hKey); + + return BK_UNKNOWN; +} + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// helper functions for JSProxy.dll +DWORD __stdcall ResolveHostName(LPSTR lpszHostName, LPSTR lpszIPAddress, LPDWORD lpdwIPAddressSize); +BOOL __stdcall IsResolvable(LPSTR lpszHost); +DWORD __stdcall GetIPAddress(LPSTR lpszIPAddress, LPDWORD lpdwIPAddressSize); +BOOL __stdcall IsInNet(LPSTR lpszIPAddress, LPSTR lpszDest, LPSTR lpszMask); + +// functions to get IE checkbox state +BOOL GetAutomaticallyDetectSettingsCheckboxState(); +BOOL GetUseAProxyServerForYourLanCheckboxState(); +BOOL GetAutomaticConfigurationScriptCheckboxState(); +BOOL GetBypassProxyServerForLocalAddressesCheckboxState(); + +// functions to actually get an IP address and port # of the proxy server +int GetAutomaticDetectSettings(LPSTR lpIPAddress, int *pnPort); +int GetProxyServerForLanProxySettings(LPSTR lpIPAddress, int *pnPort); +int GetAutoConfigScriptProxySettings(LPSTR lpIPAddress, int *pnPort); + +// various helper functions +BOOL IsDirect(LPSTR proxy); +BOOL IsAProxy(LPSTR proxy); +void reportFuncErr(TCHAR* funcName); +char * strstri(LPSTR lpOne, LPSTR lpTwo); +int GetProxyIP(LPSTR proxy, LPSTR szProxyIP); +int GetProxyPort(LPSTR proxy); + +// some global variables +char gszURL[1025] = {0}; +char gszHost[256] = {0}; + +// returns TRUE if the user has set a proxy in IE +BOOL IsIEProxySet() +{ + BOOL bAutomaticallyDetectSettings = GetAutomaticallyDetectSettingsCheckboxState(); + BOOL bUseAutomaticConfigurationScript = GetAutomaticConfigurationScriptCheckboxState(); + BOOL bUseAProxyServerForYourLan = GetUseAProxyServerForYourLanCheckboxState(); + + if(bAutomaticallyDetectSettings || bUseAutomaticConfigurationScript || bUseAProxyServerForYourLan) { + return TRUE; + } + + return FALSE; +} + +int ResolveURL_IE(LPSTR lpURL, LPSTR lpHostname, LPSTR lpIPAddress, int sizeof_address, int *pnPort) +{ + // get the state of the four checkboxes in the proxy settings dialog for IE + BOOL bAutomaticallyDetectSettings = GetAutomaticallyDetectSettingsCheckboxState(); + BOOL bUseAutomaticConfigurationScript = GetAutomaticConfigurationScriptCheckboxState(); + BOOL bUseAProxyServerForYourLan = GetUseAProxyServerForYourLanCheckboxState(); + //BOOL bBypassProxyServerForLocalAddresses = GetBypassProxyServerForLocalAddressesCheckboxState(); + int ret; + + lstrcpyn(gszURL, lpURL, 1025); + lstrcpyn(gszHost, lpHostname, 256); + + // if nothing checked, return + if(!bAutomaticallyDetectSettings && !bUseAutomaticConfigurationScript && !bUseAProxyServerForYourLan) { + return 0; + } + + // if all three checkboxes on... + if(bAutomaticallyDetectSettings && gTryAuto) + { + // try the automatic configuration next + ret = GetAutomaticDetectSettings(lpIPAddress, pnPort); + if(ret == 0 && *pnPort) { + return 0; + } + gTryAuto = 0; + + } + + if ( bUseAutomaticConfigurationScript) + { + // try the automatic config script method first + ret = GetAutoConfigScriptProxySettings(lpIPAddress, pnPort); + if(ret == 0 && *pnPort ) { + return 0; + } + + } + + if ( bUseAProxyServerForYourLan) + { + // if still no success, try the "Use a proxy server for your lan" settings + ret = GetProxyServerForLanProxySettings(lpIPAddress, pnPort); + if(ret == 0 && *pnPort) { + return 0; + } + } + + + + + + // no success... + return 0; + + +} + +// handles the "Automatically Detect" checkbox +int GetAutomaticDetectSettings(LPSTR lpIPAddress, int *pnPort) +{ + // By not specifying a domain name, Windows uses the local domain name, + // so form an http request to go to http://wpad/wpad.dat, + // store results in szWPADLocation and call URLDownloadToFileA() + if(lpIPAddress && pnPort) { + // download wpad.dat from the URL in szURL + return ReadWPADFile("http://wpad/wpad.dat", lpIPAddress, pnPort); + } + + return -1; +} + +// handles the "Use automatic configuration script" checkbox +int GetAutoConfigScriptProxySettings(LPSTR lpIPAddress, int *pnPort) +{ + DWORD dwType, dwSize; + HKEY hKey; + char szWPADLocation[MAX_PATH] = {0}; + long lRet; + int retval = -1; + + + if(!lpIPAddress) { + return retval; + } + if(!pnPort) { + return retval; + } + + // use the registry read of HKCU\\software\microsoft\windows\current version\internet settings to see if "Use Automatic Configuration Script" is checked + lstrcpyn(szWPADLocation, "", MAX_PATH); + + lRet = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", 0, KEY_READ, &hKey); + if (lRet == ERROR_SUCCESS) { + dwSize = sizeof(szWPADLocation); + lRet = RegQueryValueEx(hKey, "AutoConfigURL", NULL, &dwType, (LPBYTE)szWPADLocation, &dwSize); + if(lRet == ERROR_SUCCESS && dwType == REG_SZ) { + retval = ReadWPADFile(szWPADLocation, lpIPAddress, pnPort); + } + } + RegCloseKey(hKey); + + return retval; //0 = success +} + +// handles the "Use a proxy server for your LAN" checkbox +int GetProxyServerForLanProxySettings(LPSTR lpIPAddress, int *pnPort) +{ + DWORD dwType, dwSize; + HKEY hKey; + BOOL bDirectOrProxy; + char szProxy[MAX_PATH] = {0}; + long lRet; + int retval = -1; + + if(lpIPAddress) { + strcpy(lpIPAddress, ""); + } + if(pnPort) { + *pnPort = 0; + } + lRet = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", 0, KEY_READ, &hKey); + if (lRet == ERROR_SUCCESS) { + dwSize = sizeof(szProxy); + lRet = RegQueryValueEx(hKey, "ProxyServer", NULL, &dwType, (LPBYTE)szProxy, &dwSize); + if(lRet == ERROR_SUCCESS && dwType == REG_SZ) { + retval = 0; + + bDirectOrProxy = FALSE; + if(IsDirect(szProxy)) { + // string is something like "DIRECT" + // It's a 'direct' kind of proxy. + bDirectOrProxy = TRUE; + + // set the 'out' parameters + if(lpIPAddress) { + strcpy(lpIPAddress, ""); + } + if(pnPort) { + *pnPort = 0; + } + } + + if(IsAProxy(szProxy)) { + char szProxyIP[MAX_PATH] = {0}; + + // string is something like "D + bDirectOrProxy = TRUE; + GetProxyIP(szProxy, szProxyIP); + // It's a 'regular' kind of proxy, with an IP of %s and a port of %d\n", szProxyIP, GetProxyPort(szProxy) + + // set the 'out' parameters + if(lpIPAddress) { + strcpy(lpIPAddress, szProxyIP); + } + if(pnPort) { + *pnPort = GetProxyPort(szProxy); + } + } + + if(!bDirectOrProxy) { + // string is something like "10.0.0.1:4543" + LPSTR lpColon = NULL; + + if ( isdigit(szProxy[0]) ) + { + lpColon = strchr(szProxy, ':'); + if(lpColon) { + *lpColon = '\0'; + + // set the 'out' parameters + if(lpIPAddress) { + strcpy(lpIPAddress, szProxy); + } + *lpColon = ':'; + if(pnPort) { + *pnPort = GetProxyPort(szProxy); + } + } + } + else if ( strstr(szProxy,"http=") ) + { + char *p = strstr(szProxy,"http="); + int offset= strlen("http="); + char *semi = strchr(p+offset, ';'); + if(semi) { + *semi= '\0'; + } + lpColon = strchr(p+offset, ':'); + if(lpColon) { + *lpColon = '\0'; + } + // set the 'out' parameters + if(lpIPAddress) { + strcpy(lpIPAddress, p+offset); + } + if (lpColon) + if(pnPort) { + *pnPort = (short)atoi(lpColon+1); + } + if ( !*pnPort ) *pnPort = 80; + + } + else + { + if(lpIPAddress) { + strcpy(lpIPAddress, ""); + } + if(pnPort) { + *pnPort = 0; + } + } + } + } + } + RegCloseKey(hKey); + + return retval; +} + +int ReadWPADFile(LPSTR lpWPADLocation, LPSTR lpIPAddress, int *pnPort) +{ + // Declare function pointers for the three autoproxy functions + pfnInternetInitializeAutoProxyDll pInternetInitializeAutoProxyDll; + pfnInternetDeInitializeAutoProxyDll pInternetDeInitializeAutoProxyDll; + pfnInternetGetProxyInfo pInternetGetProxyInfo; + + // Declare and populate an AutoProxyHelperVtbl structure, and then + // place a pointer to it in a containing AutoProxyHelperFunctions + // structure, which will be passed to InternetInitializeAutoProxyDll: + AutoProxyHelperVtbl Vtbl = {IsResolvable, GetIPAddress, ResolveHostName, IsInNet }; + AutoProxyHelperFunctions HelperFunctions = { &Vtbl }; + HMODULE hModJS; + HRESULT hr; + char szTempPath[MAX_PATH] = {0}; + char szTempFile[MAX_PATH] = {0}; + int retval = 0; + + + if(!(hModJS = LoadLibrary("jsproxy.dll"))) { + reportFuncErr("LoadLibrary"); + return -1; + } + + if(!(pInternetInitializeAutoProxyDll = (pfnInternetInitializeAutoProxyDll) + GetProcAddress(hModJS, "InternetInitializeAutoProxyDll")) || + !(pInternetDeInitializeAutoProxyDll = (pfnInternetDeInitializeAutoProxyDll) + GetProcAddress(hModJS, "InternetDeInitializeAutoProxyDll")) || + !(pInternetGetProxyInfo = (pfnInternetGetProxyInfo) + GetProcAddress(hModJS, "InternetGetProxyInfo"))) { + FreeLibrary(hModJS); + reportFuncErr("GetProcAddress"); + return -1; + } + + if(lpIPAddress) + { + strcpy(lpIPAddress, ""); + } + if(pnPort) { + *pnPort = 0; + } + + GetTempPathA(sizeof(szTempPath)/sizeof(szTempPath[0]), szTempPath); + GetTempFileNameA(szTempPath, "X", 2, szTempFile); + //printf(" Downloading %s ...\n", lpWPADLocation); + hr = JNetLibDownloadToFile(NULL, lpWPADLocation, szTempFile, NULL, NULL); + if(hr == S_OK) { + if(!pInternetInitializeAutoProxyDll(0, szTempFile, NULL, &HelperFunctions, NULL)) { + //printf(" Calling 'InternetInitializeAutoProxyDll' in JSPROXY.DLL failed\n (usually because 'Use Automatic Configuration Script' checkbox is OFF)\n"); + pInternetDeInitializeAutoProxyDll(NULL, 0); + FreeLibrary(hModJS); + retval = -1; + }else{ + // printf("\n InternetInitializeAutoProxyDll returned: %d\n", returnVal); + + // Delete the temporary file + // (or, to examine the auto-config script, comment out the + // file delete and substitute the following printf call) + // printf("\n The auto-config script temporary file is:\n %s\n", szTempFile); + DeleteFileA(szTempFile); + + DWORD dwSize = 0; + LPSTR pProxy = NULL; + if(!pInternetGetProxyInfo((LPSTR)gszURL, sizeof(gszURL), (LPSTR)gszHost, sizeof(gszHost), &pProxy, &dwSize)) { + reportFuncErr("InternetGetProxyInfo"); + retval = -1; + }else{ + // printf("\n Proxy is: %s\n", proxy); + if(IsDirect(pProxy)) { + //printf(" It's a 'direct' kind of proxy.\n"); + + // set the 'out' parameters + if(lpIPAddress) { + strcpy(lpIPAddress, ""); + } + if(pnPort) { + *pnPort = 0; + } + } + + if(IsAProxy(pProxy)) { + char szProxyIP[MAX_PATH] = {0}; + + GetProxyIP(pProxy, szProxyIP); + //printf(" It's a 'regular' kind of proxy, with an IP of %s and a port of %d\n", szProxyIP, GetProxyPort(szProxy)); + + // set the 'out' parameters + if(lpIPAddress) { + strcpy(lpIPAddress, szProxyIP); + } + if(pnPort) { + *pnPort = GetProxyPort(pProxy); + } + } + } + } + }else{ + //printf(" Error downloading %s (hr=0x%X)\n", lpWPADLocation, hr); + // there is no proxy, go direct + if(lpIPAddress) { + strcpy(lpIPAddress, ""); + } + if(pnPort) { + *pnPort = 0; + } + retval = 0; + } + + if(!pInternetDeInitializeAutoProxyDll(NULL, 0)) { + reportFuncErr("InternetDeInitializeAutoProxyDll"); + } + + return retval; // 0 = success +} + + +// Puts "10.0.0.1" into lpDest from a string like "PROXY 10.0.0.1:8088" +// Returns 0 if success, -1 if an error +int GetProxyIP(LPSTR lpProxy, LPSTR lpDest) +{ + LPSTR lpData; + LPSTR lpLastColon; + BOOL bDone; + char szProxy[MAX_PATH] = {0}; + int ret = 0; + + if(lpProxy && lpDest) { + lstrcpyn(szProxy, lpProxy, MAX_PATH); + + // find the last ":" in the string + lpLastColon = NULL; + lpData = szProxy; + while(*lpData) { + if(*lpData == ':') { + lpLastColon = lpData; + } + lpData++; + } + + if(lpLastColon) { + // truncate the string at the last colon + *lpLastColon = '\0'; + + bDone = FALSE; + while(lpData > szProxy && !bDone) { + if(*lpData == ' ') { + bDone = TRUE; + lpData++; + }else{ + lpData--; + } + } + strcpy(lpDest, lpData); + ret = 0; + }else { + strcpy(lpDest, lpProxy); + ret =0; + } + }else{ + ret = -1; + } + + return ret; +} + +// Returns 8088 from a string like "PROXY 10.0.0.1:8088" +// Returns a port # if success, -1 if an error +int GetProxyPort(LPSTR lpProxy) +{ + LPSTR lpData; + LPSTR lpLastColon = NULL; + char szProxy[MAX_PATH] = {0}; + int ret = -1; + + if(lpProxy) { + lstrcpyn(szProxy, lpProxy, MAX_PATH); + + // find the last ":" in the string + lpData = szProxy; + while(*lpData) { + if(*lpData == ':') { + lpLastColon = lpData; + } + lpData++; + } + + // from the last colon to the end of the string is the port number + if ( lpLastColon ) + { + lpLastColon++; + ret = (unsigned short)atoi(lpLastColon); + } + else ret = 80; + } + + return ret; +} + +BOOL IsDirect(LPSTR proxy) +{ + BOOL bRet = FALSE; + + if(proxy) { + if(strstri("DIRECT", proxy)) { + bRet = TRUE; + } + } + + return bRet; +} + +BOOL IsAProxy(LPSTR proxy) +{ + BOOL bRet = FALSE; + + if(proxy) { + if(strstri("PROXY", proxy)) { + bRet = TRUE; + } + } + + return bRet; +} + +// like strstr() but case-insensitive +char * strstri(LPSTR lpOne, LPSTR lpTwo) +{ + unsigned int b; + char szOne[MAX_PATH] = {0}, szTwo[MAX_PATH] = {0}; + + if(lpOne && lpTwo) { + strcpy(szOne, lpOne); + strcpy(szTwo, lpTwo); + + for(b=0; b<strlen(szOne); b++) { + szOne[b] = tolower(szOne[b]); + } + + for(b=0; b<strlen(szTwo); b++) { + szTwo[b] = tolower(szTwo[b]); + } + } + + return strstr(szTwo, szOne); +} + +BOOL GetAutomaticallyDetectSettingsCheckboxState() +{ + DWORD dwSize, dwType; + HKEY hKey; + BOOL bAutomaticallyDetectSettings = FALSE; + long lRet; + + // see if the "Automatically Detect Settings" checkbox is on (I know, it's ugly) + // I noticed that the 9th byte in a binary struct at HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Connections\DefaultConnectionSettings" + // changes a bit to 1 or 0 based on the state of the checkbox. I'm using Windows XP. Not sure what byte to check on other Windows versions. + BYTE Buffer[200] = {0}; + + lRet = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Connections", 0, KEY_READ, &hKey); + if (lRet == ERROR_SUCCESS) { + dwSize = sizeof(Buffer); + lRet = RegQueryValueEx(hKey, "DefaultConnectionSettings", NULL, &dwType, (LPBYTE)&Buffer, &dwSize); + if(lRet == ERROR_SUCCESS && dwType == REG_BINARY) { + if(Buffer[8] & 8) { + bAutomaticallyDetectSettings = TRUE; + } + } + } + RegCloseKey(hKey); + + return bAutomaticallyDetectSettings; +} + +BOOL GetUseAProxyServerForYourLanCheckboxState() +{ + DWORD dwSize, dwValue, dwType; + HKEY hKey; + BOOL bUseAProxyServerForYourLan = FALSE; + long lRet; + + + // see if the "Use a proxy server for your LAN" checkbox is on + lRet = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", 0, KEY_READ, &hKey); + if (lRet == ERROR_SUCCESS) { + dwSize = sizeof(DWORD); + lRet = RegQueryValueEx(hKey, "ProxyEnable", NULL, &dwType, (LPBYTE)&dwValue, &dwSize); + if(lRet == ERROR_SUCCESS && dwType == REG_DWORD) { + bUseAProxyServerForYourLan = dwValue; + } + } + RegCloseKey(hKey); + + return bUseAProxyServerForYourLan; +} + +BOOL GetAutomaticConfigurationScriptCheckboxState() +{ + DWORD dwType, dwSize; + HKEY hKey; + BOOL bUseAutomaticConfigurationScript = FALSE; + char szWPAD[MAX_PATH] = {0}; + long lRet; + + +#if 1 + // use the registry read of HKCU\\software\microsoft\windows\current version\internet settings to see if "Use Automatic Configuration Script" is checked + szWPAD[0] = '\0'; + + lRet = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", 0, KEY_READ, &hKey); + if (lRet == ERROR_SUCCESS) { + dwSize = sizeof(szWPAD); + lRet = RegQueryValueEx(hKey, "AutoConfigURL", NULL, &dwType, (LPBYTE)szWPAD, &dwSize); + if(lRet == ERROR_SUCCESS && dwType == REG_SZ) { + + } + } + RegCloseKey(hKey); +#else + // use DetectAutoProxyURL + if(!DetectAutoProxyUrl(szWPADLocation, sizeof(szWPADLocation), PROXY_AUTO_DETECT_TYPE_DHCP | PROXY_AUTO_DETECT_TYPE_DNS_A)) { + reportFuncErr("DetectAutoProxyUrl"); + } +#endif + + if(strlen(szWPAD)) { + bUseAutomaticConfigurationScript = TRUE; + } + + return bUseAutomaticConfigurationScript; +} + +BOOL GetBypassProxyServerForLocalAddressesCheckboxState() +{ + DWORD dwSize, dwType; + HKEY hKey; + BOOL bBypassProxyServerForLocalAddresses = FALSE; + char szBuffer[MAX_PATH] = {0}; + long lRet; + + dwSize = sizeof(szBuffer); + lRet = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", 0, KEY_READ, &hKey); + if (lRet == ERROR_SUCCESS) { + lRet = RegQueryValueEx(hKey, "ProxyOverride", NULL, &dwType, (LPBYTE)&szBuffer, &dwSize); + if(lRet == ERROR_SUCCESS && dwType == REG_SZ) { + + } + } + RegCloseKey(hKey); + + if(strcmp(szBuffer, "<local>") == 0) { + bBypassProxyServerForLocalAddresses = TRUE; + } + + return bBypassProxyServerForLocalAddresses; +} + + +/* ================================================================== + HELPER FUNCTIONS + ================================================================== */ +// ResolveHostName (a helper function) +DWORD __stdcall ResolveHostName(LPSTR lpszHostName, LPSTR lpszIPAddress, LPDWORD lpdwIPAddressSize) +{ + DWORD dwIPAddressSize; + addrinfo Hints; + LPADDRINFO lpAddrInfo; + LPADDRINFO IPv4Only; + DWORD error; + + // Figure out first whether to resolve a name or an address literal. + // If getaddrinfo() with the AI_NUMERICHOST flag succeeds, then + // lpszHostName points to a string representation of an IPv4 or IPv6 + // address. Otherwise, getaddrinfo() should return EAI_NONAME. + ZeroMemory(&Hints, sizeof(addrinfo)); + Hints.ai_flags = AI_NUMERICHOST; // Only check for address literals. + Hints.ai_family = PF_UNSPEC; // Accept any protocol family. + Hints.ai_socktype = SOCK_STREAM; // Constrain results to stream socket. + Hints.ai_protocol = IPPROTO_TCP; // Constrain results to TCP. + + error = getaddrinfo(lpszHostName, NULL, &Hints, &lpAddrInfo); + if(error != EAI_NONAME) { + if(error != 0) { + error = (error == EAI_MEMORY) ? + ERROR_NOT_ENOUGH_MEMORY : ERROR_INTERNET_NAME_NOT_RESOLVED; + goto quit; + } + freeaddrinfo(lpAddrInfo); + + // An IP address (either v4 or v6) was passed in, so if there is + // room in the lpszIPAddress buffer, copy it back out and return. + dwIPAddressSize = lstrlen(lpszHostName); + + if((*lpdwIPAddressSize < dwIPAddressSize) || (lpszIPAddress == NULL)) { + *lpdwIPAddressSize = dwIPAddressSize + 1; + error = ERROR_INSUFFICIENT_BUFFER; + goto quit; + } + lstrcpy(lpszIPAddress, lpszHostName); + goto quit; + } + + // Call getaddrinfo() again, this time with no flag set. + Hints.ai_flags = 0; + error = getaddrinfo(lpszHostName, NULL, &Hints, &lpAddrInfo); + if(error != 0) { + error = (error == EAI_MEMORY) ? + ERROR_NOT_ENOUGH_MEMORY : ERROR_INTERNET_NAME_NOT_RESOLVED; + goto quit; + } + + // Convert the IP address in addrinfo into a string. + // (the following code only handles IPv4 addresses) + IPv4Only = lpAddrInfo; + while(IPv4Only->ai_family != AF_INET) { + IPv4Only = IPv4Only->ai_next; + if(IPv4Only == NULL) + { + error = ERROR_INTERNET_NAME_NOT_RESOLVED; + goto quit; + } + } + error = getnameinfo(IPv4Only->ai_addr, (socklen_t)IPv4Only->ai_addrlen, lpszIPAddress, *lpdwIPAddressSize, NULL, 0, NI_NUMERICHOST); + if(error != 0) + error = ERROR_INTERNET_NAME_NOT_RESOLVED; + +quit: + return(error); +} + + +// IsResolvable (a helper function) +BOOL __stdcall IsResolvable(LPSTR lpszHost) +{ + char szDummy[255] = {0}; + DWORD dwDummySize = sizeof(szDummy) - 1; + + if(ResolveHostName(lpszHost, szDummy, &dwDummySize)) + return(FALSE); + + return TRUE; +} + + +// GetIPAddress (a helper function) +DWORD __stdcall GetIPAddress(LPSTR lpszIPAddress, LPDWORD lpdwIPAddressSize) +{ + char szHostBuffer[255] = {0}; + + if(gethostname(szHostBuffer, sizeof(szHostBuffer) - 1) != ERROR_SUCCESS) + return(ERROR_INTERNET_INTERNAL_ERROR); + + return(ResolveHostName(szHostBuffer, lpszIPAddress, lpdwIPAddressSize)); +} + + +// IsInNet (a helper function) +BOOL __stdcall IsInNet(LPSTR lpszIPAddress, LPSTR lpszDest, LPSTR lpszMask) +{ + DWORD dwDest; + DWORD dwIpAddr; + DWORD dwMask; + + dwIpAddr = inet_addr(lpszIPAddress); + dwDest = inet_addr(lpszDest); + dwMask = inet_addr(lpszMask); + + if((dwDest == INADDR_NONE) || (dwIpAddr == INADDR_NONE) || ((dwIpAddr & dwMask) != dwDest)) + return(FALSE); + + return(TRUE); +} + + +// reportFuncErr (simple error reporting) +void reportFuncErr(TCHAR* funcName) +{ + //printf(" ERROR: %s failed with error number %d.\n", funcName, GetLastError()); +} + + + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +class CProfileFolder +{ +public: + int GetProfileFolder(LPSTR lpProfileFolder, BOOL bFirefox); + +private: + int GetProfileFolder_9598ME(LPSTR lpProfileFolder, BOOL bFirefox); + int GetProfileFolder_2000XP(LPSTR lpProfileFolder, BOOL bFirefox); +}; + + + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +class CMozSettings +{ +public: + CMozSettings(BOOL bFirefox); + virtual ~CMozSettings(); + int GetPreference(LPSTR lpPreferenceWanted, int *pnDest); + int GetPreference(LPSTR lpPreferenceWanted, LPSTR lpDest, int sizeof_dest); + +private: + CProfileFolder m_pf; + HGLOBAL m_hData; + LPSTR m_lpData; + int m_sizeof_data; +}; + +int CProfileFolder::GetProfileFolder(LPSTR lpProfileFolder, BOOL bFirefox) +{ + // See http://www.mozilla.org/support/firefox/edit for where I got this info: + // On Windows XP/2000, the path is usually %AppData%\Mozilla\Firefox\Profiles\xxxxxxxx.default\, where xxxxxxxx is a random string of characters. Just browse to C:\Documents and Settings\[User Name]\Application Data\Mozilla\Firefox\Profiles\ and the rest should be obvious. + // On Windows 95/98/Me, the path is usually C:\WINDOWS\Application Data\Mozilla\Firefox\Profiles\xxxxxxxx.default\ + // On Linux, the path is usually ~/.mozilla/firefox/xxxxxxxx.default/ + // On Mac OS X, the path is usually ~/Library/Application Support/Firefox/Profiles/xxxxxxxx.default/ + OSVERSIONINFO version; + + ZeroMemory(&version, sizeof(version)); + version.dwOSVersionInfoSize = sizeof(version); + GetVersionEx(&version); + if(version.dwMajorVersion == 3) { + return -1; // NT 3.51 not supported + } + if(version.dwMajorVersion == 4) { + return GetProfileFolder_9598ME(lpProfileFolder, bFirefox); + } + if(version.dwMajorVersion >= 5) { + return GetProfileFolder_2000XP(lpProfileFolder, bFirefox); + } + + return -1; +} + +// private function for GetProfileFolder() +// on my test system, the folder to get is c:\windows\application data\mozilla\profiles\default\y3h9azmi.slt +int CProfileFolder::GetProfileFolder_9598ME(LPSTR lpProfileFolder, BOOL bFirefox) +{ + WIN32_FIND_DATA fd; + HANDLE hFind; + BOOL bDone, bFound; + char szHomePath[MAX_PATH] = {0}; + char szTemp[MAX_PATH] = {0}; + + + if(lpProfileFolder) { + GetEnvironmentVariable("WINDIR", szHomePath, sizeof(szHomePath)); + strcpy(lpProfileFolder, szHomePath); + if(bFirefox) { + strcat(lpProfileFolder, "\\Application Data\\Mozilla\\Firefox\\Profiles\\"); + }else{ + strcat(lpProfileFolder, "\\Application Data\\Mozilla\\Profiles\\default\\"); + } + + // find the first folder in the the path specified in szProfileFolder + lstrcpyn(szTemp, lpProfileFolder, MAX_PATH-4); + strcat(szTemp, "*.*"); + + bDone = FALSE; + bFound = FALSE; + hFind = FindFirstFile(szTemp, &fd); + while(hFind != INVALID_HANDLE_VALUE && !bDone) { + if(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + // we're at a directory. + // make sure it's not "." or ".." + if(fd.cFileName[0] != '.') { + bFound = TRUE; + } + } + + bDone = !FindNextFile(hFind, &fd); + } + FindClose(hFind); + + if(bFound) { + strcat(lpProfileFolder, fd.cFileName); + return 0; + } + } + + return -1; +} + +// private function for GetProfileFolder() +int CProfileFolder::GetProfileFolder_2000XP(LPSTR lpProfileFolder, BOOL bFirefox) +{ + WIN32_FIND_DATA fd; + HANDLE hFind; + BOOL bDone, bFound; + char szHomePath[MAX_PATH] = {0}; + char szTemp[MAX_PATH] = {0}; + + + if(lpProfileFolder) { + GetEnvironmentVariable("APPDATA", szHomePath, sizeof(szHomePath)); + strcpy(lpProfileFolder, szHomePath); + if(bFirefox) { + strcat(lpProfileFolder, "\\Mozilla\\Firefox\\Profiles\\"); + }else{ + strcat(lpProfileFolder, "\\Mozilla\\Profiles\\default\\"); + } + + // find the first folder in the the path specified in szProfileFolder + strcpy(szTemp, lpProfileFolder); + strcat(szTemp, "*.*"); + + bDone = FALSE; + bFound = FALSE; + hFind = FindFirstFile(szTemp, &fd); + while(hFind != INVALID_HANDLE_VALUE && !bDone) { + if(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + // we're at a directory. + // make sure it's not "." or ".." + if(fd.cFileName[0] != '.') { + bFound = TRUE; + } + } + + bDone = !FindNextFile(hFind, &fd); + } + FindClose(hFind); + + if(bFound) { + strcat(lpProfileFolder, fd.cFileName); + return 0; + } + } + + return -1; +} + + + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +CMozSettings::CMozSettings(BOOL bFirefox) +{ + WIN32_FIND_DATA fd; + OFSTRUCT of; + HANDLE hFind; + HFILE hPrefsFile; + char szProfileFolder[MAX_PATH] = {0}; + char szPrefsFile[MAX_PATH] = {0}; + int ret; + + + m_hData = NULL; + m_lpData = NULL; + + ret = m_pf.GetProfileFolder(szProfileFolder, bFirefox); + + + if(ret == 0) { + // We found the folder where prefs.js lives. Read it in. + strcpy(szPrefsFile, szProfileFolder); + strcat(szPrefsFile, "\\prefs.js"); + + // get the size of the file and alloc memory for it + hFind = FindFirstFile(szPrefsFile, &fd); + if(hFind != INVALID_HANDLE_VALUE) { + m_hData = GlobalAlloc(GHND, fd.nFileSizeLow + 256); + if(m_hData) { + m_lpData = (LPSTR)GlobalLock(m_hData); + if(m_lpData) { + hPrefsFile = OpenFile(szPrefsFile, &of, OF_READ); + if(hPrefsFile) { + m_sizeof_data = fd.nFileSizeLow; + _lread(hPrefsFile, m_lpData, m_sizeof_data); + _lclose(hPrefsFile); + hPrefsFile = NULL; + } + } + } + + FindClose(hFind); + } + } +} + +CMozSettings::~CMozSettings() +{ + if(m_lpData) { + GlobalUnlock(m_hData); + m_lpData = NULL; + } + + if(m_hData) { + GlobalFree(m_hData); + m_hData = NULL; + } +} + +int CMozSettings::GetPreference(LPSTR lpPreferenceWanted, LPSTR lpDest, int sizeof_dest) +{ + LPSTR lpPointer, lpPointerEnd, lpData; + LPSTR lpLineStart, lpSearchStart, lpFoundString, lpResult; + BOOL bDone; + int nDoubleQuoteCount, retval; + + + retval = -1; + if(m_lpData) { + if(lpPreferenceWanted) { + if(lpDest) { + *lpDest = '\0'; + bDone = FALSE; + lpPointer = m_lpData; + lpPointerEnd = lpPointer + m_sizeof_data; + + while(lpPointer < lpPointerEnd && !bDone) { + if(strncmp(lpPointer, "user_pref(", 10) == 0) { + lpLineStart = lpPointer; + lpSearchStart = lpLineStart + 11; + if(strncmp(lpSearchStart, lpPreferenceWanted, strlen(lpPreferenceWanted)) == 0) { + lpFoundString = lpSearchStart + strlen(lpPreferenceWanted); + + // lpFoundString almost points to what we want. Skip over the " character it's at now, skip over the " character + // starting the value we want and null-terminate what we want when we find the 3rd " character + lpData = lpFoundString; + nDoubleQuoteCount = 0; + while(nDoubleQuoteCount <= 3 && !bDone && lpData < lpPointerEnd) { + if(*lpData == '"') { + nDoubleQuoteCount++; + if(nDoubleQuoteCount == 2) { + // we're at the starting quote + lpResult = lpData; + lpResult++; + } + if(nDoubleQuoteCount == 3) { + // we're at the ending quote + // null-terminate what we want, and copy it to the dest buffer + *lpData = '\0'; + lstrcpyn(lpDest, lpResult, sizeof_dest); + + bDone = TRUE; + retval = 0; + } + } + lpData++; + } + } + } + + lpPointer++; + } + } + } + } + + return retval; +} + +int CMozSettings::GetPreference(LPSTR lpPreferenceWanted, int *pnDest) +{ + LPSTR lpPointer, lpPointerEnd, lpData; + LPSTR lpLineStart, lpSearchStart, lpFoundString; + BOOL bDone; + int retval; + + + retval = -1; + if(m_lpData) { + if(lpPreferenceWanted) { + if(pnDest) { + bDone = FALSE; + lpPointer = m_lpData; + lpPointerEnd = lpPointer + m_sizeof_data; + + while(lpPointer < lpPointerEnd && !bDone) { + if(strncmp(lpPointer, "user_pref(", 10) == 0) { + lpLineStart = lpPointer; + lpSearchStart = lpLineStart + 11; + if(strncmp(lpSearchStart, lpPreferenceWanted, strlen(lpPreferenceWanted)) == 0) { + lpFoundString = lpSearchStart + strlen(lpPreferenceWanted); + + // lpFoundString almost points to what we want. Skip over the " character it's at now, skip over the "," + // starting the value we want and null-terminate what we want when we find the 3rd " character + lpData = lpFoundString; + while(*lpData != ',' && lpData < lpPointerEnd) { + lpData++; + } + if(*lpData == ',') { + lpData++; + + lpFoundString = lpData; + while(*lpData != ')' && lpData < lpPointerEnd) { + lpData++; + } + if(*lpData == ')') { + // null-terminate what we want, and copy it to the dest buffer + *lpData = '\0'; + *pnDest = atoi(lpFoundString); + bDone = TRUE; + + retval = 0; + } + } + } + } + + lpPointer++; + } + } + } + } + + return retval; +} + +//////////////////////////////////////////////////////////////////////// +BOOL IsFirefoxProxySet() +{ + return IsFirefoxOrMozillaProxySet(TRUE); +} + +BOOL IsMozillaProxySet() +{ + return IsFirefoxOrMozillaProxySet(FALSE); +} + +BOOL IsFirefoxOrMozillaProxySet(BOOL bFirefox) +{ + CMozSettings settings(bFirefox); + int ret, nValue; + + ret = settings.GetPreference("network.proxy.type", &nValue); + if(ret == 0) { + switch(nValue) { + case 0: // shouldn't get here + break; + case 1: // manual configuration + return TRUE; + case 2: // automatic configuration + return TRUE; + case 4: // auto-detect + return TRUE; + default: // don't know + break; + } + } + + return FALSE; +} + +int ResolveURL_Mozilla(LPSTR lpURL, LPSTR lpHostname, LPSTR lpIPAddress, int sizeof_address, int *pnPort) +{ + return ResolveURL_MozillaOrFirefox(FALSE, lpURL, lpHostname, lpIPAddress, sizeof_address, pnPort); +} + +int ResolveURL_Firefox(LPSTR lpURL, LPSTR lpHostname, LPSTR lpIPAddress, int sizeof_address, int *pnPort) +{ + return ResolveURL_MozillaOrFirefox(TRUE, lpURL, lpHostname, lpIPAddress, sizeof_address, pnPort); +} + +int ResolveURL_MozillaOrFirefox(BOOL bFirefox, LPSTR lpURL, LPSTR lpHostname, LPSTR lpIPAddress, int sizeof_address, int *pnPort) +{ + CMozSettings setting(bFirefox); + int ret, nValue; + + + // search for the "network.proxy.http" preference + ret = setting.GetPreference("network.proxy.type", &nValue); + if(ret == 0) { + switch(nValue) { + case 0: + // shouldn't get here + break; + + case 1: + // manual configuration + setting.GetPreference("network.proxy.http", lpIPAddress, sizeof_address); + setting.GetPreference("network.proxy.http_port", pnPort); + break; + + case 2: + // automatic configuration + { + char szWPADLocation[MAX_PATH] = {0}; + + setting.GetPreference("network.proxy.autoconfig_url", szWPADLocation, sizeof(szWPADLocation)); + ret = ReadWPADFile(szWPADLocation, lpIPAddress, pnPort); + } + break; + + case 4: + // Auto-detect proxy settings for this network + ret = ReadWPADFile("http://wpad/wpad.dat", lpIPAddress, pnPort); + break; + + default: + break; + } + } + + return ret; +} + + +// My function that downloads from a URL to a file using the Nullsoft JNetLib library instead of using +// URLDownloadToFile(). Only parameters 2 and 3 are used, to mimick the parameters of URLDownloadToFile(). +HRESULT JNetLibDownloadToFile(LPVOID lpUnused1, LPSTR lpWPADLocation, LPSTR lpTempFile, LPVOID lpUnused2, LPVOID lpUnused3) +{ + api_httpreceiver *http=0; + waServiceFactory *sf=0; + + OFSTRUCT of; + HGLOBAL hData; + HRESULT hRet = S_FALSE; // default return value + LPSTR lpData; + DWORD dwSize; + HFILE hFile; + BOOL bDone; + //JNL jNetLib; + int ret; + + + if(lpWPADLocation && lpTempFile) + { + if (WASABI_API_SVC) + { + sf = WASABI_API_SVC->service_getServiceByGuid(httpreceiverGUID); + if (sf) http = (api_httpreceiver *)sf->getInterface(); + } + if (!http) + return S_FALSE; + + // init the library and open a connection to the URL + http->Open(); + http->Connect(lpWPADLocation); + + // loop until JNetLib gets the data. + // run() returns 0 if OK, -1 if error (call geterrorstr()), or 1 if connection closed. + bDone = FALSE; + while(!bDone) { + ret = http->Run(); + if(ret == -1 || ret == 1) { + bDone = TRUE; + } + Sleep(50); + } + + + dwSize = (DWORD)http->GetContentLength(); + if(dwSize && ret == 1) { + // Got something! + // Allocate memory for it and write it to lpTempFile + hData = GlobalAlloc(GHND, dwSize + 100); + if(hData) { + lpData = (LPSTR)GlobalLock(hData); + if(lpData) { + http->GetBytes(lpData, (int)dwSize); + + // create the output file and write to it + hFile = OpenFile(lpTempFile, &of, OF_CREATE); + if(hFile != HFILE_ERROR) { + _lwrite(hFile, lpData, (UINT)dwSize); + _lclose(hFile); + + hRet = S_OK; // success + } + + GlobalUnlock(hData); + lpData = NULL; + } + + GlobalFree(hData); + hData = NULL; + } + } + } +if (http && sf) +sf->releaseInterface(http); + return hRet; +} +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_nsv/Main.cpp b/Src/Plugins/Input/in_nsv/Main.cpp new file mode 100644 index 00000000..159ee873 --- /dev/null +++ b/Src/Plugins/Input/in_nsv/Main.cpp @@ -0,0 +1,976 @@ +#define PLUGIN_NAME "Nullsoft NSV Decoder" +#define PLUGIN_VERSION L"1.76" + +#include <windows.h> +#include "../Winamp/in2.h" +#include "../nsv/nsvplay/main.h" +#include "resource.h" +#include "../nu/AutoWide.h" +#include "../nu/AutoCharFn.h" +#define NO_IVIDEO_DECLARE +#include "../winamp/wa_ipc.h" +#include "../Winamp/strutil.h" +#include "api.h" +extern In_Module mod; // the output module (filled in near the bottom of this file) + +#define g_hInstance mod.hDllInstance +#define WNDMENU_CAPTION L"Winamp in_nsv" +#define MODAL_ABOUT +#define LOC_MODAL_ABOUT +#include "../nsv/nsvplay/about.h" +#undef g_hInstance + +#include <shlwapi.h> +#include <strsafe.h> +extern int config_precseek; +extern int config_vidoffs; +extern int config_bufms; +extern int config_prebufms; +extern int config_underunbuf; +extern int config_bufms_f; +extern int config_prebufms_f; +extern int config_underunbuf_f; + +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = + { + 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } + }; + + +char lastfn[1024] = {0}; // currently playing file (used for getting info on the current file) +static char statusbuf[1024]; + +char stream_url[1024] = {0}; + +ULONGLONG g_bufferstat; +int m_last_bitrate; +void config_read(); +void config_write(); +void config(HWND hwndParent); + +int file_length = 0; // file length, in bytes +// Used for correcting DSP plug-in pitch changes +int paused = 0; // are we paused? +volatile int seek_needed; // if != -1, it is the point that the decode +// thread should seek to, in ms. + +CRITICAL_SECTION g_decoder_cs; +char g_streaminfobuf[512] = {0}; +int g_streaminfobuf_used = 0; + +char error_string[128] = {0}; + +volatile int killDecodeThread = 0; // the kill switch for the decode thread +HANDLE thread_handle = INVALID_HANDLE_VALUE; // the handle to the decode thread + +int has_opened_outmod = 0; +int m_srate = 0; // seek needs this + +int decoders_initted = 0; + +api_config *AGAVE_API_CONFIG = 0; +api_memmgr *WASABI_API_MEMMGR = 0; +// wasabi based services for localisation support +api_language *WASABI_API_LNG = 0; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; + +void process_url(char *url) +{ + lstrcpynA(stream_url, url, sizeof(stream_url)); + + // if (!strncmp(stream_url,"hTtP",4)) + // { + // DWORD dw; + // SendMessageTimeout(mod.hMainWindow,WM_USER,(WPARAM)0,241,SMTO_NORMAL,500,&dw); + // } // I (Tag) removed this support, its annoying. + DWORD_PTR dw = 0; + if (stream_url[0]) SendMessageTimeout(mod.hMainWindow, WM_USER, (WPARAM)stream_url, 241, SMTO_NORMAL, 500, &dw); +} + +char last_title_sent[256] = {0}; + +void process_metadata(char *data, int len) +{ + if (len && *data) + { + char *ld; + int x; + if (len > 4096) return ; + for (x = 0; x < len; x ++) + if (!data[x]) break; + if (x == len) return ; + while ((ld = strstr(data, "='"))) + { + char * n = data; + ld[0] = 0; + ld += 2; + data = strstr(ld, "';"); + if (data) + { + data[0] = 0; + data += 2; + if (!lstrcmpiA(n, "StreamTitle")) + { + lstrcpynA(last_title_sent, ld, sizeof(last_title_sent)); + last_title_sent[sizeof(last_title_sent) - 1] = 0; + PostMessage(mod.hMainWindow, WM_USER, 0, 243); + } + else if (!lstrcmpiA(n, "StreamUrl")) + { + process_url(ld); + } + } + else break; + } + } +} + +class WA2AudioOutput : public IAudioOutput +{ +public: + WA2AudioOutput(int srate, int nch, int bps) + { + memset(m_stuffbuf, 0, sizeof(m_stuffbuf)); + decode_pos_samples = 0; + m_srate = srate; m_bps = bps; m_nch = nch; + m_open_success = 0; + m_stuffbuf_u = 0; + int maxlat = mod.outMod->Open(srate, nch, bps, -1, -1); + if (maxlat == 0 && strstr(lastfn, "://")) + { + maxlat = -1; + mod.outMod->Close(); // boom + } + if (maxlat >= 0) + { + mod.SetInfo( -1, srate / 1000, nch, 1); + mod.SAVSAInit(maxlat, srate); + mod.VSASetInfo(srate, nch); + mod.outMod->SetVolume( -666); + m_open_success = 1; + has_opened_outmod = 1; + } + } + ~WA2AudioOutput(){} + + int canwrite() + { + int a = mod.outMod->CanWrite(); + if (mod.dsp_isactive() == 1) a /= 2; + return a & ~((m_nch * (m_bps / 8)) - 1); + } // returns bytes writeable + + void write(void *buf, int len) + { + char *b = (char *)buf; + int s = 576 * m_nch * (m_bps / 8); + if (s > sizeof(m_stuffbuf)) s = sizeof(m_stuffbuf); + + while (len > 0) + { + int l = s; + if (!m_stuffbuf_u && len >= s) // straight copy of data + { + int dms = (int) ((decode_pos_samples * (__int64)1000) / (__int64)m_srate); + mod.SAAddPCMData(b, m_nch, m_bps, dms); + mod.VSAAddPCMData(b, m_nch, m_bps, dms); + } + else if (m_stuffbuf_u + len >= s) + { + int dms = (int) (((decode_pos_samples - (m_stuffbuf_u / m_nch / (m_bps / 8))) * (__int64)1000) / (__int64)m_srate); + l = (s - m_stuffbuf_u); + memcpy(m_stuffbuf + m_stuffbuf_u, b, l); + m_stuffbuf_u = 0; + + mod.SAAddPCMData(m_stuffbuf, m_nch, m_bps, dms); + mod.VSAAddPCMData(m_stuffbuf, m_nch, m_bps, dms); + } + else // put all of len into m_stuffbuf + { + memcpy(m_stuffbuf + m_stuffbuf_u, b, len); + m_stuffbuf_u += len; + l = len; + } + + if (l > len)l = len; // this shouldn't happen but we'll leave it here just in case + + decode_pos_samples += (l / m_nch / (m_bps / 8)); + + if (mod.dsp_isactive()) + { + static char sample_buffer[576*2*(16 / 8)*2]; + int spll = l / m_nch / (m_bps / 8); + memcpy(sample_buffer, b, l); + int l2 = l; + if (spll > 0) l2 = mod.dsp_dosamples((short *)sample_buffer, spll, m_bps, m_nch, m_srate) * (m_nch * (m_bps / 8)); + mod.outMod->Write(sample_buffer, l2); + } + else mod.outMod->Write(b, l); + len -= l; + b += l; + } + } + ULONGLONG getwritepos() + { + return (unsigned int) ((decode_pos_samples * 1000) / m_srate); + } + ULONGLONG getpos() + { + if (seek_needed != -1) return seek_needed; + return (unsigned int) ((decode_pos_samples * 1000) / m_srate) + + (mod.outMod->GetOutputTime() - mod.outMod->GetWrittenTime()) - config_vidoffs; + } + void flush(unsigned int newtime) + { + m_stuffbuf_u = 0; + mod.outMod->Flush(newtime); + decode_pos_samples = (((__int64)newtime) * m_srate) / 1000; + } + void pause(int pause) + { + mod.outMod->Pause(pause); + } + int get_open_success() { return m_open_success; } + int isplaying(void) { return mod.outMod->IsPlaying(); } +private: + __int64 decode_pos_samples; // current decoding position, in milliseconds. + int m_nch, m_bps; + int m_open_success; + int m_stuffbuf_u; + char m_stuffbuf[576*2*2]; +}; + +IAudioOutput *PCMOUT_CREATE(unsigned int outfmt[8]) +{ + if (outfmt[1] && outfmt[2] && outfmt[3] && outfmt[0] == NSV_MAKETYPE('P', 'C', 'M', ' ')) + { + WA2AudioOutput *r = new WA2AudioOutput(outfmt[1], outfmt[2], outfmt[3]); + if (r->get_open_success()) return r; + delete r; + } + return NULL; +} + +DWORD WINAPI DecodeThread(LPVOID b); // the decode thread procedure + +void about(HWND hwndParent) +{ + do_about(hwndParent,WASABI_API_LNG_HINST); +} + +void SetFileExtensions(void) +{ + static char fileExtensionsString[1200] = {0}; // "NSV;NSA\0Nullsoft Audio/Video File (*.NSV;*.NSA)\0" + char* end = 0; + StringCchCopyExA(fileExtensionsString, 1200, "NSV;NSA", &end, 0, 0); + StringCchCopyExA(end+1, 1200, WASABI_API_LNGSTRING(IDS_NSA_NSV_FILE), 0, 0, 0); + mod.FileExtensions = fileExtensionsString; +} + +int init() +{ + if (!IsWindow(mod.hMainWindow)) + return IN_INIT_FAILURE; + + waServiceFactory *sf = mod.service->service_getServiceByGuid(AgaveConfigGUID); + if (sf) AGAVE_API_CONFIG = (api_config *)sf->getInterface(); + + sf = mod.service->service_getServiceByGuid(memMgrApiServiceGuid); + if (sf) WASABI_API_MEMMGR = reinterpret_cast<api_memmgr*>(sf->getInterface()); + + // loader so that we can get the localisation service api for use + sf = mod.service->service_getServiceByGuid(languageApiGUID); + if (sf) WASABI_API_LNG = reinterpret_cast<api_language*>(sf->getInterface()); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(mod.hDllInstance,InNSVLangGUID); + + static wchar_t szDescription[256]; + StringCchPrintfW(szDescription,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_NSV_DECODER),PLUGIN_VERSION); + mod.description = (char*)szDescription; + + SetFileExtensions(); + + config_read(); + InitializeCriticalSection(&g_decoder_cs); + return IN_INIT_SUCCESS; +} + +void quit() +{ + Decoders_Quit(); + DeleteCriticalSection(&g_decoder_cs); + waServiceFactory *sf = mod.service->service_getServiceByGuid(AgaveConfigGUID); + if (sf) sf->releaseInterface(AGAVE_API_CONFIG); + + sf = mod.service->service_getServiceByGuid(memMgrApiServiceGuid); + if (sf) sf->releaseInterface(WASABI_API_MEMMGR); +} + +int isourfile(const char *fn) +{ + // used for detecting URL streams.. unused here. + // return !strncmp(fn,"http://",7); to detect HTTP streams, etc + return !_strnicmp( fn, "unsv://", 7 ); +} + +NSVDecoder *m_decoder = 0; +IVideoOutput *m_video_output = 0; + +int g_play_needseek = -1; + +int play(const char *fn) +{ + m_last_bitrate = -1; + g_play_needseek = -1; + last_title_sent[0] = 0; + g_bufferstat = 0; + mod.is_seekable = 0; + has_opened_outmod = 0; + error_string[0] = 0; + if (!decoders_initted) + { + decoders_initted = 1; + char buf[MAX_PATH] = {0}, *p = buf; + GetModuleFileNameA(mod.hDllInstance, buf, sizeof(buf)); + while (p && *p) p++; + while (p && p > buf && *p != '\\') p--; + if (p) *p = 0; + Decoders_Init(buf); + } + + unsigned long thread_id = 0; + + paused = 0; + seek_needed = -1; + EnterCriticalSection(&g_decoder_cs); + if (strstr(fn, "://")) + WASABI_API_LNGSTRING_BUF(IDS_CONNECTING,error_string,128); + else + WASABI_API_LNGSTRING_BUF(IDS_OPENING,error_string,128); + + LeaveCriticalSection(&g_decoder_cs); + + m_video_output = (IVideoOutput *)SendMessage(mod.hMainWindow, WM_USER, 0, IPC_GET_IVIDEOOUTPUT); + if (!m_video_output) return 1; + + m_video_output->open(0, 0, 0, 0, 0); + + m_decoder = new NSVDecoder(fn, m_video_output, NULL); + lstrcpynA(lastfn, fn, sizeof(lastfn)); + + if (strstr(fn, "://") || !strncmp(fn, "\\\\", 2)) + { + m_decoder->SetPreciseSeeking(config_precseek&2); + m_decoder->SetBuffering(config_bufms, config_prebufms, config_underunbuf); + } + else + { + m_decoder->SetPreciseSeeking(config_precseek&1); + m_decoder->SetBuffering(config_bufms_f, config_prebufms_f, config_underunbuf_f); + } + + // launch decode thread + killDecodeThread = 0; + thread_handle = (HANDLE)CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) DecodeThread, NULL, 0, &thread_id); + SetThreadPriority(thread_handle, (int)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + + return 0; +} + +// standard pause implementation +void pause() +{ + paused = 1; + // if (has_opened_outmod) mod.outMod->Pause(1); + // else + if (m_decoder) m_decoder->pause(1); +} +void unpause() +{ + paused = 0; + // if (has_opened_outmod) mod.outMod->Pause(0); + //else + if (m_decoder) m_decoder->pause(0); +} +int ispaused() { return paused; } + +// stop playing. +void stop() +{ + g_play_needseek = -1; + + if (thread_handle != INVALID_HANDLE_VALUE) + { + killDecodeThread = 1; + int nTimes = 0; + const int maxTimes = 1000; + while (WaitForSingleObject(thread_handle, 0) == WAIT_TIMEOUT) + { + MSG msg = {0}; + if (PeekMessage(&msg, NULL, 0, 0, 1)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + else + Sleep(10); + + nTimes++; + if (nTimes == maxTimes) + { +#ifdef WINAMPX + SendStatus( WINAMPX_STATUS_ERROR_KILLING_THREAD, 0 ); +#else + /*MessageBox(mod.hMainWindow, "error asking thread to die!\n", + "error killing decode thread", 0);*/ +#endif + TerminateThread(thread_handle, 0); + break; + } + } + CloseHandle(thread_handle); + thread_handle = INVALID_HANDLE_VALUE; + } + if (has_opened_outmod) mod.outMod->Close(); + g_bufferstat = 0; + has_opened_outmod = 0; + mod.SAVSADeInit(); + + EnterCriticalSection(&g_decoder_cs); + delete(m_decoder); + m_decoder = NULL; +LeaveCriticalSection(&g_decoder_cs); + g_streaminfobuf[0] = 0; +} + +int getlength() +{ + if (m_decoder) + { + int x = m_decoder->getlen(); + if (x != -1) return x; + } + return -1000; +} + +int getoutputtime() +{ + if (g_bufferstat) return (int)g_bufferstat; + EnterCriticalSection(&g_decoder_cs); + if (m_decoder) + { + LeaveCriticalSection(&g_decoder_cs); + return (int)(m_decoder ? m_decoder->getpos() + config_vidoffs : 0); + } + LeaveCriticalSection(&g_decoder_cs); + return 0; +} + +void setoutputtime(int time_in_ms) +{ + seek_needed = time_in_ms; +} + +void setvolume(int volume) { mod.outMod->SetVolume(volume); } + +void setpan(int pan) { mod.outMod->SetPan(pan); } + +int infoDlg(const char *fn, HWND hwnd); + +// this is an odd function. it is used to get the title and/or +// length of a track. +// if filename is either NULL or of length 0, it means you should +// return the info of lastfn. Otherwise, return the information +// for the file in filename. +// if title is NULL, no title is copied into it. +// if length_in_ms is NULL, no length is copied into it. +void getfileinfo(const char *filename, char *title, int *length_in_ms) +{ + if (!filename || !*filename) // currently playing file + { + EnterCriticalSection(&g_decoder_cs); + if (length_in_ms) *length_in_ms = getlength(); + if (title) // get non-path portion.of filename + { + char *p = NULL; + if (m_decoder) + { + p = m_decoder->getTitle(); + } + if (!p) + { + p = lastfn + strlen(lastfn); + while (p && *p != '\\' && p >= lastfn) p--; + p++; + } + while (p && *p == ';') p++; + title[0] = 0; + + if (error_string[0]) + { + StringCchPrintfA(title, GETFILEINFO_TITLE_LENGTH, "[%s] ", error_string); + } + if (!error_string[0] && last_title_sent[0]) + { + StringCchPrintfA(title, GETFILEINFO_TITLE_LENGTH-strlen(title), "%s (%s)", last_title_sent, p); + } + else + lstrcpynA(title + strlen(title), p, FILETITLE_SIZE); + } + LeaveCriticalSection(&g_decoder_cs); + } + else if (1) // some other file + { + if ((length_in_ms || title) && !strstr(filename, "://")) + { + nsv_InBS bs; + nsv_fileHeader hdr = {0, }; + if (length_in_ms) // calculate length + { + *length_in_ms = -1000; // the default is unknown file length (-1000). + } + if (title) // get non path portion of filename + { + const char *p = filename + strlen(filename); + while (p && *p != '\\' && p >= filename) p--; + lstrcpynA(title, ++p, GETFILEINFO_TITLE_LENGTH); + } + + IDataReader *rdr = CreateReader(filename); + + if (rdr) + { + while (!rdr->iseof()) + { + char buf[1024] = {0}; + int l = (int)rdr->read(buf, sizeof(buf)); + if (!l) break; + bs.add(buf, l); + l = nsv_readheader(bs, &hdr); + if (l <= 0) + { + if (!l) + { + if (length_in_ms) *length_in_ms = hdr.file_lenms; + if (title && hdr.metadata) + { + char *t = nsv_getmetadata(hdr.metadata, "TITLE"); + if (t) lstrcpynA(title, t, 1024); + } + } + free(hdr.metadata); + free(hdr.toc); + break; + } + } + delete rdr; + } + // try to parse out lengths + } + } +} + +void eq_set(int on, char data[10], int preamp) +{} + +DWORD WINAPI DecodeThread(LPVOID b) +{ + int last_bpos = -1; + int firstsynch = 0; + ULONGLONG next_status_time = 0; + while (!killDecodeThread) + { + EnterCriticalSection(&g_decoder_cs); + int r = m_decoder->run((int*)&killDecodeThread); + LeaveCriticalSection(&g_decoder_cs); + if (r < 0) + { + if (m_decoder->get_error()) + { + EnterCriticalSection(&g_decoder_cs); + lstrcpynA(error_string, m_decoder->get_error(), sizeof(error_string)); + LeaveCriticalSection(&g_decoder_cs); + PostMessage(mod.hMainWindow, WM_USER, 0, 243); + Sleep(200); + } + break; + } + else if (!r) + { + Sleep(1); + int br = m_decoder->getBitrate() / 1000; + if (br != m_last_bitrate) + { + m_last_bitrate = br; + mod.SetInfo(br, -1, -1, -1); + } + + int bpos = m_decoder->getBufferPos(); + if (bpos > 255) + { + ULONGLONG obuf = g_bufferstat; + g_bufferstat = 0; + if (last_bpos >= 0) + { + EnterCriticalSection(&g_decoder_cs); + error_string[0] = 0; + LeaveCriticalSection(&g_decoder_cs); + PostMessage(mod.hMainWindow, WM_USER, 0, 243); + last_bpos = -1; + int csa = mod.SAGetMode(); + if (csa && obuf) + { + char tempdata[75*2] = {0, }; + mod.SAAdd(tempdata, (int)++obuf, (csa == 3) ? 0x80000003 : csa); + } + } + } + else + { + if (!g_bufferstat) + { + if (!has_opened_outmod) mod.SAVSAInit(10, 44100); + + g_bufferstat = m_decoder->getpos() + 1; + } + + if (bpos != last_bpos) + { + last_bpos = bpos; + EnterCriticalSection(&g_decoder_cs); + StringCchPrintfA(error_string, 128, WASABI_API_LNGSTRING(IDS_BUFFER_X), (bpos*100) / 256); + LeaveCriticalSection(&g_decoder_cs); + int csa = mod.SAGetMode(); + char tempdata[2*75] = {0, }; + int x; + if (csa&1) + { + for (x = 0; x < bpos*75 / 256; x ++) + { + tempdata[x] = x * 16 / 75; + } + } + if (csa&2) + { + int offs = (csa & 1) ? 75 : 0; + x = 0; + while (x < bpos*75 / 256) + { + tempdata[offs + x++] = -6 + x * 14 / 75; + } + while (x < 75) + { + tempdata[offs + x++] = 0; + } + } + if (csa == 4) + { + tempdata[0] = tempdata[1] = (bpos * 127 / 256); + } + + if (csa) mod.SAAdd(tempdata, (int)++g_bufferstat, (csa == 3) ? 0x80000003 : csa); + PostMessage(mod.hMainWindow, WM_USER, 0, 243); + } + } + + if (GetTickCount64() > next_status_time || GetTickCount64() < next_status_time - 5000) + { + char statusbuf[1024] = {0}; + EnterCriticalSection(&g_decoder_cs); + g_streaminfobuf[0] = 0; + + if (g_streaminfobuf_used) + { + char *outp = g_streaminfobuf; + size_t size = 512; + const char *p = m_decoder->getServerHeader("server"); + if (!p) p = m_decoder->getServerHeader("icy-notice2"); + + if (p && strlen(p) < sizeof(g_streaminfobuf) - (outp - g_streaminfobuf) - 32) + StringCchPrintfExA(outp, size, &outp, &size, 0, "%s: %s\r\n", WASABI_API_LNGSTRING(IDS_SERVER), p); + + p = m_decoder->getServerHeader("content-type"); + if (p && strlen(p) < sizeof(g_streaminfobuf) - (outp - g_streaminfobuf) - 32) + StringCchPrintfExA(outp, size, &outp, &size, 0, "%s: %s\r\n", WASABI_API_LNGSTRING(IDS_CONTENT_TYPE), p); + + p = m_decoder->getServerHeader("content-length"); + if (p && strlen(p) < sizeof(g_streaminfobuf) - (outp - g_streaminfobuf) - 32) + StringCchPrintfExA(outp, size, &outp, &size, 0, "%s: %s\r\n", WASABI_API_LNGSTRING(IDS_CONTENT_LENGTH), p); + + p = m_decoder->getServerHeader("icy-name"); + if (p && strlen(p) < sizeof(g_streaminfobuf) - (outp - g_streaminfobuf) - 32) + StringCchPrintfExA(outp, size, &outp, &size, 0, "%s: %s\r\n", WASABI_API_LNGSTRING(IDS_STREAM_NAME), p); + } + + lstrcpynA(statusbuf, "NSV: ", 1024); + { // codecs + char *sb = statusbuf; + size_t size = 1024; + + int l = m_decoder->getlen(); + if (l > 0) + { + l /= 1000; + if (l >= 3600) + { + StringCchPrintfExA(sb, size, &sb, &size, 0, "%d:", l / 3600); + l %= 3600; + StringCchPrintfExA(sb, size, &sb, &size, 0, "%02d:%02d", l / 60, l % 60); + } + else + StringCchPrintfExA(sb, size, &sb, &size, 0, "%d:%02d", l / 60, l % 60); + } + + int a = (m_decoder->getBitrate() + 500) / 1000; + if (a) + { + if (strlen(statusbuf) > 5) + StringCchCatExA(sb, size, " @ ", &sb, &size, 0); + StringCchPrintfExA(sb, size, &sb, &size, 0, "%d%s", a, WASABI_API_LNGSTRING(IDS_KBPS)); + } + + if (strlen(statusbuf) > 5) + StringCchCatExA(sb, size, ", ", &sb, &size, 0); + + char *p = statusbuf + strlen(statusbuf); + m_decoder->getVideoDesc(p); + if (p && !*p) StringCchCopyExA(p, size, "?, ", &p, &size, 0); + else if (!strncmp(p, "NONE", 4)) *p = 0; + else StringCchCatExA(p, size, ", ", &p, &size, 0); + + p = statusbuf + strlen(statusbuf); + m_decoder->getAudioDesc(p); + if (p && !*p) StringCchCopyExA(p, size, "?", &p, &size, 0); + else if (!strncmp(p, "NONE", 4)) *p = 0; + } + LeaveCriticalSection(&g_decoder_cs); + m_video_output->extended(VIDUSER_SET_INFOSTRING, (intptr_t)statusbuf, 0); + + next_status_time = GetTickCount64() + 2500; + } + } + else + { + if (!firstsynch) + { + if (m_decoder->canseek()) + { + mod.is_seekable = 1; + if (g_play_needseek >= 0) seek_needed = g_play_needseek; + g_play_needseek = -1; + } + firstsynch++; + PostMessage(mod.hMainWindow, WM_USER, 0, 243); + } + } + if (seek_needed >= 0) + { + EnterCriticalSection(&g_decoder_cs); + m_decoder->seek(seek_needed); + seek_needed = -1; + LeaveCriticalSection(&g_decoder_cs); + } + } + if (!killDecodeThread) + { + PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + } + m_decoder->CloseVideo(); + return 0; +} + +// module definition. +In_Module mod = + { + IN_VER_RET, // defined in IN2.H + "nullsoft(in_nsv.dll)", + 0, // hMainWindow (filled in by winamp) + 0, // hDllInstance (filled in by winamp) + 0, // this is a double-null limited list. "EXT\0Description\0EXT\0Description\0" etc. + 1, // is_seekable + 1, // uses output plug-in system + config, + about, + init, + quit, + getfileinfo, + infoDlg, + isourfile, + play, + pause, + unpause, + ispaused, + stop, + + getlength, + getoutputtime, + setoutputtime, + + setvolume, + setpan, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, // visualization calls filled in by winamp + + 0, 0, // dsp calls filled in by winamp + + eq_set, + + NULL, // setinfo call filled in by winamp + + 0 // out_mod filled in by winamp + }; + +static FILETIME ftLastWriteTime; + +// is used to determine if the last write time of the file has changed when +// asked to get the metadata for the same cached file so we can update things +BOOL HasFileTimeChanged(const wchar_t *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData = {0}; + if (GetFileAttributesExW(fn, GetFileExInfoStandard, &fileData) == TRUE) + { + if(CompareFileTime(&ftLastWriteTime, &fileData.ftLastWriteTime)) + { + ftLastWriteTime = fileData.ftLastWriteTime; + return TRUE; + } + } + return FALSE; +} + +extern "C" +{ + __declspec( dllexport ) In_Module * winampGetInModule2() + { + return &mod; + } + + wchar_t lastextfn[1024] = {0}; + // Keep track of file timestamp for file system change notification handling + FILETIME last_write_time = {0, 0}; + static int valid; + static nsv_fileHeader hdr; + + bool isFileChanged(const wchar_t* file) + { + WIN32_FIND_DATAW FindFileData = {0}; + HANDLE hFind = FindFirstFileW(file, &FindFileData); + if (hFind == INVALID_HANDLE_VALUE) + return true; + FindClose(hFind); + + if ((last_write_time.dwHighDateTime == + FindFileData.ftLastWriteTime.dwHighDateTime) && + (last_write_time.dwLowDateTime == + FindFileData.ftLastWriteTime.dwLowDateTime)) + { + return false; + } + return true; + } + + __declspec( dllexport ) int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, int destlen) + { + if (!_stricmp(data, "type")) + { + if (!fn || !fn[0] || _wcsicmp(PathFindExtensionW(fn), L".nsa")) // if extension is NOT nsa + lstrcpyn(dest, L"1", destlen); //video + else + lstrcpyn(dest, L"0", destlen); // audio + return 1; + } + + if (!fn || (fn && !fn[0])) + return 0; + + if (!_stricmp(data, "family")) + { + int pID = -1; + LPCWSTR e = PathFindExtensionW(fn); + if (L'.' != *e) return 0; + e++; + DWORD lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); + if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"NSV", -1)) pID = IDS_FAMILY_STRING_NSV; + if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"NSA", -1)) pID = IDS_FAMILY_STRING_NSA; + if (pID != -1 && S_OK == StringCchCopyW(dest, destlen, WASABI_API_LNGSTRINGW(pID))) return 1; + return 0; + } + + //the file name differs from the last file + //name but we need to check the time stamp too + if (_wcsicmp(fn, lastextfn) || isFileChanged(fn) || HasFileTimeChanged(fn)) + { + free(hdr.metadata); + memset(&hdr, 0, sizeof(hdr)); + valid = 0; + + lstrcpyn(lastextfn, fn, ARRAYSIZE(lastextfn)); + + nsv_InBS bs; + + IDataReader *rdr = CreateReader(AutoCharFn(lastextfn)); + + if (rdr) + { + while (!rdr->iseof()) + { + char buf[1024] = {0}; + int l = (int)rdr->read(buf, sizeof(buf)); + if (!l) break; + bs.add(buf, l); + l = nsv_readheader(bs, &hdr); + if (l <= 0) + { + free(hdr.toc); + if (!l) + { + valid = 1; + + //Save time stamp + WIN32_FIND_DATAW FindFileData = {0}; + HANDLE hFind = FindFirstFileW(fn, &FindFileData); + if (hFind == INVALID_HANDLE_VALUE) + { + last_write_time.dwHighDateTime = NULL; + last_write_time.dwLowDateTime = NULL; + } + else + { + last_write_time.dwHighDateTime = FindFileData.ftLastWriteTime.dwHighDateTime; + last_write_time.dwLowDateTime = FindFileData.ftLastWriteTime.dwLowDateTime; + FindClose(hFind); + } + break; + } + break; + } + } + delete rdr; + } + } + + dest[0] = 0; + + if (!valid) + { + return 0; + } + + if (!_stricmp(data, "length")) + { + if (hdr.file_lenms > 0) + { + StringCchPrintfW(dest, destlen, L"%d", hdr.file_lenms); + } + } + else if (hdr.metadata) + { + const char *t = nsv_getmetadata(hdr.metadata, (char*)data); + if (t) lstrcpyn(dest, AutoWide(t), destlen); + } + + return 1; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_nsv/api.h b/Src/Plugins/Input/in_nsv/api.h new file mode 100644 index 00000000..e94bdd9c --- /dev/null +++ b/Src/Plugins/Input/in_nsv/api.h @@ -0,0 +1,16 @@ +#ifndef NULLSOFT_API_H +#define NULLSOFT_API_H + +#include "../Agave/Config/api_config.h" +extern api_config *configApi; +#define AGAVE_API_CONFIG configApi + +#include <api/memmgr/api_memmgr.h> +extern api_memmgr *memmgrApi; +#define WASABI_API_MEMMGR memmgrApi + +#include <api/service/waServiceFactory.h> + +#include "../Agave/Language/api_language.h" + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_nsv/config.cpp b/Src/Plugins/Input/in_nsv/config.cpp new file mode 100644 index 00000000..6c4ada01 --- /dev/null +++ b/Src/Plugins/Input/in_nsv/config.cpp @@ -0,0 +1,197 @@ +#include <windows.h> +#include "api.h" +#include "resource.h" +#include "../Winamp/wa_ipc.h" +#include "../Winamp/in2.h" + +extern In_Module mod; // the output module (filled in near the bottom of this file) + +static char app_name[] = "Nullsoft NSV Decoder2"; + +//char config_http_proxynonport80=1; + +int config_padtag=1024; +int config_bufms=10000; +int config_prebufms=2000; +int config_underunbuf=3000; +int config_bufms_f=1000; +int config_prebufms_f=1000; +int config_underunbuf_f=1000; +int config_vidoffs=0; +int config_precseek=3; +int config_subtitles=1; + +void config_write(); + +char INI_FILE[512] = {0}; + +static int _r_i(char *name, int def) +{ + if (!_strnicmp(name,"config_",7)) name += 7; + return GetPrivateProfileIntA(app_name,name,def,INI_FILE); +} +#define RI(x) (( x ) = _r_i(#x,( x ))) +static void _w_i(char *name, int d) +{ + char str[120] = {0}; + wsprintfA(str,"%d",d); + if (!_strnicmp(name,"config_",7)) name += 7; + WritePrivateProfileStringA(app_name,name,str,INI_FILE); +} +#define WI(x) _w_i(#x,( x )) + +static void _r_s(char *name,char *data, int mlen) +{ + char buf[2048] = {0}; + strncpy(buf, data, 2048); + if (!_strnicmp(name,"config_",7)) name += 7; + GetPrivateProfileStringA(app_name,name,buf,data,mlen,INI_FILE); +} +#define RS(x) (_r_s(#x,x,sizeof(x))) + +static void _w_s(char *name, char *data) +{ + if (!_strnicmp(name,"config_",7)) name += 7; + WritePrivateProfileStringA(app_name,name,data,INI_FILE); +} +#define WS(x) (_w_s(#x,x)) + + + +static void config_init() +{ +char *p; + if (mod.hMainWindow && + (p = (char *)SendMessageA(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETINIFILE)) + && p!= (char *)1) + { + strncpy(INI_FILE, p, MAX_PATH); + } + else + { + GetModuleFileNameA(NULL,INI_FILE,sizeof(INI_FILE)); + p=INI_FILE+strlen(INI_FILE); + while (p >= INI_FILE && *p != '.') p--; + strcpy(++p,"ini"); + } +} + +static INT_PTR CALLBACK configProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + // show config + SetDlgItemInt(hwndDlg,IDC_BUF1,config_bufms,FALSE); + SetDlgItemInt(hwndDlg,IDC_BUF2,config_prebufms,FALSE); + SetDlgItemInt(hwndDlg,IDC_BUF3,config_underunbuf,FALSE); + SetDlgItemInt(hwndDlg,IDC_BUF4,config_bufms_f,FALSE); + SetDlgItemInt(hwndDlg,IDC_BUF5,config_prebufms_f,FALSE); + SetDlgItemInt(hwndDlg,IDC_BUF6,config_underunbuf_f,FALSE); + SetDlgItemInt(hwndDlg,IDC_OFFS,config_vidoffs,TRUE); + SetDlgItemInt(hwndDlg,IDC_TAGPAD,config_padtag,FALSE); + if (config_precseek&1)CheckDlgButton(hwndDlg,IDC_CHECK1,BST_CHECKED); + if (config_precseek&2)CheckDlgButton(hwndDlg,IDC_CHECK2,BST_CHECKED); + if (config_subtitles)CheckDlgButton(hwndDlg,IDC_CHECK3,BST_CHECKED); +// if (!config_http_proxynonport80) CheckDlgButton(hwndDlg,IDC_CHECK5,BST_CHECKED); + return 0; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_CHECK1: + case IDC_CHECK2: + config_precseek=(IsDlgButtonChecked(hwndDlg,IDC_CHECK1)?1:0)| + (IsDlgButtonChecked(hwndDlg,IDC_CHECK2)?2:0); + + break; + case IDC_CHECK3: + config_subtitles=(IsDlgButtonChecked(hwndDlg,IDC_CHECK3)?1:0); + + break; + case IDC_OFFS: + if (HIWORD(wParam) == EN_CHANGE) + { + BOOL t; + config_vidoffs=GetDlgItemInt(hwndDlg,IDC_OFFS,&t,TRUE); + } + break; + case IDCANCEL: EndDialog(hwndDlg,0); break; + case IDOK: + // save config +// config_http_proxynonport80=!IsDlgButtonChecked(hwndDlg,IDC_CHECK5); + { + BOOL t; + config_bufms=GetDlgItemInt(hwndDlg,IDC_BUF1,&t,FALSE); + if (config_bufms < 100) config_bufms=100; + if (config_bufms > 100000) config_bufms=100000; + + config_prebufms=GetDlgItemInt(hwndDlg,IDC_BUF2,&t,FALSE); + if (config_prebufms < 100) config_prebufms=100; + if (config_prebufms > config_bufms) config_prebufms=config_bufms; + + config_underunbuf=GetDlgItemInt(hwndDlg,IDC_BUF3,&t,FALSE); + if (config_underunbuf < 100) config_underunbuf=100; + if (config_underunbuf > config_bufms) config_underunbuf=config_bufms; + + config_bufms_f=GetDlgItemInt(hwndDlg,IDC_BUF4,&t,FALSE); + if (config_bufms_f < 100) config_bufms_f=100; + if (config_bufms_f > 100000) config_bufms_f=100000; + + config_prebufms_f=GetDlgItemInt(hwndDlg,IDC_BUF5,&t,FALSE); + if (config_prebufms_f < 100) config_prebufms_f=100; + if (config_prebufms_f > config_bufms_f) config_prebufms_f=config_bufms_f; + + config_underunbuf_f=GetDlgItemInt(hwndDlg,IDC_BUF6,&t,FALSE); + if (config_underunbuf_f < 100) config_underunbuf_f=100; + if (config_underunbuf_f > config_bufms_f) config_underunbuf_f=config_bufms_f; + + config_vidoffs=GetDlgItemInt(hwndDlg,IDC_OFFS,&t,TRUE); + + config_padtag=GetDlgItemInt(hwndDlg,IDC_TAGPAD,&t,FALSE); + } + config_write(); + EndDialog(hwndDlg,1); + break; + } + break; + } + return 0; +} + +void config(HWND hwndParent) +{ + WASABI_API_DIALOGBOXW(IDD_DIALOG1,hwndParent,configProc); +} + +void config_read() +{ + config_init(); + RI(config_bufms); + RI(config_prebufms); + RI(config_underunbuf); + RI(config_bufms_f); + RI(config_prebufms_f); + RI(config_underunbuf_f); + RI(config_vidoffs); + RI(config_padtag); +// RI(allow_uvox); +// RI(config_http_proxynonport80); + RI(config_precseek); + RI(config_subtitles); +} + +void config_write() +{ + WI(config_bufms); + WI(config_prebufms); + WI(config_underunbuf); + WI(config_bufms_f); + WI(config_prebufms_f); + WI(config_underunbuf_f); + WI(config_vidoffs); + WI(config_padtag); + WI(config_precseek); + WI(config_subtitles); +// WI(allow_uvox); +// WI(config_http_proxynonport80); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_nsv/in_nsv.rc b/Src/Plugins/Input/in_nsv/in_nsv.rc new file mode 100644 index 00000000..55fac0cc --- /dev/null +++ b/Src/Plugins/Input/in_nsv/in_nsv.rc @@ -0,0 +1,294 @@ +// 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 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG1 DIALOGEX 0, 0, 185, 158 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Nullsoft NSV Decoder Configuration" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "Buffering",IDC_STATIC,4,4,177,86 + LTEXT "Stream",IDC_STATIC,83,15,23,8 + LTEXT "Local File",IDC_STATIC,132,15,31,8 + RTEXT "Buffer size:",IDC_STATIC,14,28,66,8 + EDITTEXT IDC_BUF1,83,26,27,12,ES_NUMBER + LTEXT "ms",IDC_STATIC,113,28,10,8 + EDITTEXT IDC_BUF4,132,26,27,12,ES_NUMBER + LTEXT "ms",IDC_STATIC,162,28,10,8 + RTEXT "Prebuffer at start:",IDC_STATIC,14,44,66,8 + EDITTEXT IDC_BUF2,83,42,27,12,ES_NUMBER + LTEXT "ms",IDC_STATIC,113,44,10,8 + EDITTEXT IDC_BUF5,132,42,27,12,ES_NUMBER + LTEXT "ms",IDC_STATIC,162,44,10,8 + LTEXT "Rebuffer on underrun:",IDC_STATIC,8,60,77,8 + EDITTEXT IDC_BUF3,83,58,27,12,ES_NUMBER + LTEXT "ms",IDC_STATIC,113,60,10,8 + EDITTEXT IDC_BUF6,132,58,27,12,ES_NUMBER + LTEXT "ms",IDC_STATIC,162,60,10,8 + LTEXT "Precise seeking:",IDC_STATIC,29,75,55,8 + CONTROL "enabled",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,83,74,41,10 + CONTROL "enabled",IDC_CHECK2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,132,74,41,10 + RTEXT "Offset video from audio by:",IDC_STATIC,4,97,91,8 + EDITTEXT IDC_OFFS,101,95,27,12 + LTEXT "ms",IDC_STATIC,133,97,10,8 + LTEXT "Pad NSV tags with: ",IDC_STATIC,34,113,63,8 + EDITTEXT IDC_TAGPAD,101,111,27,12,ES_NUMBER + LTEXT "bytes",IDC_STATIC,133,113,18,8 + LTEXT "Display Subtitles:",IDC_STATIC,40,128,60,8 + CONTROL "enabled",IDC_CHECK3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,101,127,41,10 + DEFPUSHBUTTON "OK",IDOK,131,141,50,13 +END + +IDD_DIALOG2 DIALOGEX 0, 0, 276, 84 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "NSV Tag Metadata Editor" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LTEXT "Metadata should be in the format of NAME=value. Examples:",IDC_STATIC,7,6,197,8 + LTEXT "TITLE=My Movie (sets the title of the video)",IDC_STATIC,21,17,160,8 + LTEXT "ASPECT=1.0 (sets the aspect ratio of the video)",IDC_STATIC,22,27,186,8 + LTEXT "FRAMERATE=18.79 (overrides the default NSV framerate)",IDC_STATIC,22,37,194,8 + EDITTEXT IDC_EDIT1,7,50,262,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,67,50,13 + PUSHBUTTON "Cancel",IDCANCEL,61,67,50,13 +END + +IDD_DIALOG3 DIALOGEX 0, 0, 355, 204 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "NSV File Info / Tag Editor" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_FN,5,5,345,12,ES_AUTOHSCROLL | ES_READONLY + GROUPBOX "NSV Bitstream Information",IDC_STATIC,5,22,345,30 + EDITTEXT IDC_BSINFO,11,34,333,12,ES_AUTOHSCROLL | ES_READONLY + GROUPBOX "Length/TOC Information",IDC_STATIC,5,56,151,106 + LTEXT "Stream size:",IDC_STATIC,12,70,50,8 + EDITTEXT IDC_LENBYTES,67,68,78,12,ES_AUTOHSCROLL | ES_READONLY | ES_NUMBER | NOT WS_TABSTOP + LTEXT "Stream length:",IDC_STATIC,12,82,50,8 + EDITTEXT IDC_LENMS,67,81,78,12,ES_AUTOHSCROLL | ES_READONLY | ES_NUMBER | NOT WS_TABSTOP + LTEXT "Avg bitrate:",IDC_STATIC,12,96,50,8 + EDITTEXT IDC_AVGBITRATE,67,94,78,12,ES_AUTOHSCROLL | ES_READONLY | ES_NUMBER | NOT WS_TABSTOP + CONTROL "Table of contents (for seeking)",IDC_TOC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,111,112,10 + RTEXT "TOC size:",IDC_STATIC,20,127,40,8 + EDITTEXT IDC_TOCSIZE,67,123,78,12,ES_AUTOHSCROLL | ES_READONLY | ES_NUMBER | NOT WS_TABSTOP + LTEXT "Max TOC size:",IDC_STATIC,15,142,48,8 + EDITTEXT IDC_SETTOCSIZE,67,139,30,12,ES_AUTOHSCROLL | ES_NUMBER + PUSHBUTTON "Analyze",IDC_ANALYZE,105,139,40,12 + CONTROL "Fast tag update (don't shrink tag)",IDC_FASTUPD,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,5,166,119,10 + LTEXT "ttl",IDC_TAG_LEN,5,175,127,8 + GROUPBOX "Metadata",IDC_STATIC,161,56,189,143 + LISTBOX IDC_METADATA,167,67,177,109,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Add item",IDC_ADD,167,180,41,13 + PUSHBUTTON "Delete item",IDC_REM,211,180,49,13 + PUSHBUTTON "Edit item",IDC_EDIT,263,180,40,13 + LTEXT "tml",IDC_METADATA_LEN,307,183,37,8 + DEFPUSHBUTTON "Update tag",IDOK,5,186,46,13 + PUSHBUTTON "Remove tag",IDC_REMTAG,55,186,45,13 + PUSHBUTTON "Cancel",IDCANCEL,102,186,45,13 +END + +IDD_DIALOG4 DIALOGEX 0, 0, 266, 132 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "NSV Stream Info" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_FN,4,5,258,13,ES_AUTOHSCROLL | ES_READONLY + GROUPBOX "Stream Info",IDC_INFOBORDER,4,22,258,89 + EDITTEXT IDC_INFO,9,32,246,75,ES_MULTILINE | ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + DEFPUSHBUTTON "Close",IDCANCEL,4,115,45,13 +END + +IDD_ABOUT DIALOGEX 0, 0, 184, 54 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Nullsoft NSV Decoder" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + CONTROL 102,IDC_STATIC,"Static",SS_BITMAP,7,7,67,39 + CTEXT "",IDC_VERSION,74,7,103,8 + CTEXT "© 2003-2023 Winamp SA",IDC_STATIC,74,16,103,8 + DEFPUSHBUTTON "OK",IDOK,105,34,50,13 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_DIALOG1, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 181 + TOPMARGIN, 4 + BOTTOMMARGIN, 154 + END + + IDD_DIALOG2, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 269 + TOPMARGIN, 6 + BOTTOMMARGIN, 80 + END + + IDD_DIALOG3, DIALOG + BEGIN + LEFTMARGIN, 5 + RIGHTMARGIN, 350 + TOPMARGIN, 5 + BOTTOMMARGIN, 199 + END + + IDD_DIALOG4, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 262 + TOPMARGIN, 5 + BOTTOMMARGIN, 128 + END + + IDD_ABOUT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 177 + TOPMARGIN, 7 + BOTTOMMARGIN, 47 + END +END +#endif // APSTUDIO_INVOKED + + +#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 + + +///////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + +IDB_BITMAP1 BITMAP "nsv_logo.bmp" + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_NSV_DECODER "Nullsoft NSV Decoder v%s" + 65535 "{11B847DB-29A7-47ac-B386-43B40385B817}" +END + +STRINGTABLE +BEGIN + IDS_TOTAL_TAG_SIZE "Total tag size: 0 bytes" + IDS_NO_METADATA "No metadata" + IDS_TOTAL_TAG_SIZE_X_BYTES "Total tag size: %d bytes" + IDS_NO_TAG "No tag" + IDS_ERROR_WRITING_STRING_TO_TAG + "Error writing string to tag (string too complex!) - string may be corrupted with a !\n" + IDS_NSV_TAG_EDITOR_ERROR "NSV Tag Editor Error" + IDS_LARGE_DATA "[LARGE DATA] - " + IDS_NO_VALID_NSV_BITSTREAM_FOUND "No valid NSV bitstream found\n" + IDS_VIDEO_X_AUDIO_X "Video: %s %dx%d@%d.%02dfps, Audio: %s" + IDS_METADATA_STRING_MUST_CONTAIN_ONE_EQUAL_SIGN + "Metadata string must contain at least one equal sign (=)." + IDS_NSV_TAG_EDITOR "NSV Tag Editor" + IDS_CANNOT_CREATE_TEMPFILE_CANNOT_UPDATE_TAG + "Cannot create tempfile - cannot update tag!" + IDS_ERROR_COPYING_SOURCE "Error copying source - cannot update tag!" + IDS_COPYING_X_BYTES "Copying %d bytes..." + IDS_ERROR_SIZE_MISMATCH "Error size mismatch - cannot update tag!" +END + +STRINGTABLE +BEGIN + IDS_ERROR_RENAMING_SOURCE "Error renaming source - cannot update tag!" + IDS_ERROR_RENAMING_NEW_FILE "Error renaming new file - cannot update tag!" + IDS_READING_X_FRAMES "Reading %d frames..." + IDS_NSV_ANALYSIS_FAILED "NSV analysis failed" + IDS_METADATA_ITEM_CONTAINS_LARGE_AMOUNT_DATA + "This metadata item appear to contain a large amount of data.\nA binary metadata editor is coming soon, until then, you can\ndelete it if you wish to create something new." + IDS_CONNECTING "Connecting" + IDS_OPENING "Opening" + IDS_BUFFER_X "Buffer: %d%%" + IDS_SERVER "Server" + IDS_CONTENT_TYPE "Content Type" + IDS_CONTENT_LENGTH "Content Length" + IDS_STREAM_NAME "Stream name" + IDS_NSA_NSV_FILE "Nullsoft Audio/Video File (*.NSV;*.NSA)" + IDS_KBPS "kbps" + IDS_FPS "fps" +END + +STRINGTABLE +BEGIN + IDS_FAMILY_STRING_NSV "Nullsoft Streaming Video File" + IDS_FAMILY_STRING_NSA "Nullsoft Streaming Audio File" +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_nsv/in_nsv.sln b/Src/Plugins/Input/in_nsv/in_nsv.sln new file mode 100644 index 00000000..f06edb48 --- /dev/null +++ b/Src/Plugins/Input/in_nsv/in_nsv.sln @@ -0,0 +1,30 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29609.76 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_nsv", "in_nsv.vcxproj", "{9E4B1D43-137A-4AE1-A05A-92EEE0028BF0}" +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 + {9E4B1D43-137A-4AE1-A05A-92EEE0028BF0}.Debug|Win32.ActiveCfg = Debug|Win32 + {9E4B1D43-137A-4AE1-A05A-92EEE0028BF0}.Debug|Win32.Build.0 = Debug|Win32 + {9E4B1D43-137A-4AE1-A05A-92EEE0028BF0}.Debug|x64.ActiveCfg = Debug|x64 + {9E4B1D43-137A-4AE1-A05A-92EEE0028BF0}.Debug|x64.Build.0 = Debug|x64 + {9E4B1D43-137A-4AE1-A05A-92EEE0028BF0}.Release|Win32.ActiveCfg = Release|Win32 + {9E4B1D43-137A-4AE1-A05A-92EEE0028BF0}.Release|Win32.Build.0 = Release|Win32 + {9E4B1D43-137A-4AE1-A05A-92EEE0028BF0}.Release|x64.ActiveCfg = Release|x64 + {9E4B1D43-137A-4AE1-A05A-92EEE0028BF0}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {382549BF-5E52-402C-8150-3E8BFCEF1439} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_nsv/in_nsv.vcxproj b/Src/Plugins/Input/in_nsv/in_nsv.vcxproj new file mode 100644 index 00000000..1f3c68a4 --- /dev/null +++ b/Src/Plugins/Input/in_nsv/in_nsv.vcxproj @@ -0,0 +1,274 @@ +<?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>{9E4B1D43-137A-4AE1-A05A-92EEE0028BF0}</ProjectGuid> + <RootNamespace>in_nsv</RootNamespace> + <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" 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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|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> + <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>_DEBUG;WIN32;_WINDOWS;_USRDLL;IN_NSV_EXPORTS;WINAMP_PLUGIN;_CRT_SECURE_NO_WARNINGS;NO_ABOUT_EGG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;winmm.lib;wsock32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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>_DEBUG;WIN64;_WINDOWS;_USRDLL;IN_NSV_EXPORTS;WINAMP_PLUGIN;_CRT_SECURE_NO_WARNINGS;NO_ABOUT_EGG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;winmm.lib;wsock32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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> + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>NDEBUG;WIN32;_WINDOWS;_USRDLL;IN_NSV_EXPORTS;WINAMP_PLUGIN;_CRT_SECURE_NO_WARNINGS;NO_ABOUT_EGG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;winmm.lib;wsock32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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> + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>NDEBUG;WIN64;_WINDOWS;_USRDLL;IN_NSV_EXPORTS;WINAMP_PLUGIN;_CRT_SECURE_NO_WARNINGS;NO_ABOUT_EGG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;winmm.lib;wsock32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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> + <ClInclude Include="..\..\..\nsv\nsvbs.h" /> + <ClInclude Include="..\..\..\nsv\nsvlib.h" /> + <ClInclude Include="api.h" /> + <ClInclude Include="resource.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\..\..\nsv\nsvlib.cpp" /> + <ClCompile Include="..\..\..\nsv\nsvplay\decoders.cpp" /> + <ClCompile Include="..\..\..\nsv\nsvplay\nsvdecode.cpp" /> + <ClCompile Include="..\..\..\nsv\nsvplay\readers.cpp" /> + <ClCompile Include="..\..\..\nsv\nsvplay\subtitles.cpp" /> + <ClCompile Include="config.cpp" /> + <ClCompile Include="infodlg.cpp" /> + <ClCompile Include="Main.cpp" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_nsv.rc" /> + </ItemGroup> + <ItemGroup> + <Image Include="nsv_logo.bmp" /> + </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_nsv/in_nsv.vcxproj.filters b/Src/Plugins/Input/in_nsv/in_nsv.vcxproj.filters new file mode 100644 index 00000000..12bf6b29 --- /dev/null +++ b/Src/Plugins/Input/in_nsv/in_nsv.vcxproj.filters @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="Main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="infodlg.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="config.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsv\nsvplay\decoders.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsv\nsvplay\nsvdecode.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsv\nsvlib.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsv\nsvplay\readers.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsv\nsvplay\subtitles.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="api.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsv\nsvbs.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsv\nsvlib.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{a867382b-03bf-4021-989d-458eb1ef9db6}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{6ccfd425-b17c-4d1c-985a-d58134034d50}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{17af3ae5-8365-4ae7-aab5-737661ec6b5a}</UniqueIdentifier> + </Filter> + <Filter Include="Image Files"> + <UniqueIdentifier>{78b43273-fca3-401e-9ca5-bbf4cfe27a2c}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <Image Include="nsv_logo.bmp"> + <Filter>Image Files</Filter> + </Image> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_nsv.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_nsv/infodlg.cpp b/Src/Plugins/Input/in_nsv/infodlg.cpp new file mode 100644 index 00000000..99a99342 --- /dev/null +++ b/Src/Plugins/Input/in_nsv/infodlg.cpp @@ -0,0 +1,1089 @@ +#include <windows.h> +#include "api.h" +#include "resource.h" + +#include "../Winamp/in2.h" + +extern In_Module mod; // the output module (filled in near the bottom of this file) + +extern "C" { extern wchar_t lastextfn[1024]; }; + +#define MAX_EDITABLE_METASTRING 8192 + +#include "../nsv/nsvlib.h" + static int isplaying; + static int timems; +extern char lastfn[1024]; +extern int g_play_needseek; + + static void restartPlayback() + { + if (isplaying) + { + SendMessage(mod.hMainWindow,WM_USER,0,3007); // disable stats updating + SendMessage(mod.hMainWindow,WM_COMMAND,40045,0); + if (timems) + { + g_play_needseek=timems; +// SendMessage(mod.hMainWindow,WM_USER,timems,106); + } + if (isplaying & 2) + { + SendMessage(mod.hMainWindow,WM_COMMAND,40046,0); + } + SendMessage(mod.hMainWindow,WM_USER,1,3007); // enable stats updating + } + } + + static void stopPlayback(const char *fn) + { + isplaying=0; + timems=0; + if (!_stricmp(lastfn,fn)) + { + isplaying= (int)SendMessage(mod.hMainWindow,WM_USER,0,104); + if (isplaying) + { + timems= (int)SendMessage(mod.hMainWindow,WM_USER,0,105); + SendMessage(mod.hMainWindow,WM_COMMAND,40047,0); + } + } + } + + +extern int config_padtag; +int fillBs(HANDLE hFile, nsv_InBS &bs, int lenbytes); + static nsv_fileHeader m_set_lhdr; + static char m_set_lfile[1024]; + + static void closeset() + { + free(m_set_lhdr.toc); + free(m_set_lhdr.metadata); + memset(&m_set_lhdr,0,sizeof(m_set_lhdr)); + m_set_lfile[0]=0; + } + +extern int config_bufms, config_prebufms, config_underunbuf; + +extern "C" +{ + + __declspec( dllexport ) int winampSetExtendedFileInfo(const char *fn, const char *data, char *val) + { + if(!fn || !fn[0]) return 0; + + //muahaha, <3 hacks + if(!_stricmp(data,"setHttpConfigValues")) + { + config_bufms=atoi(val); + char *p=strstr(val,","); + if(!p) return 0; + config_prebufms=atoi(p+1); + p=strstr(p+1,","); + if(!p) return 0; + config_underunbuf=atoi(p+1); + return 1; + } + + if(strcmpi(fn,m_set_lfile)) + { + closeset(); + + lstrcpynA(m_set_lfile,fn,sizeof(m_set_lfile)); + + HANDLE hFile = CreateFileA(fn,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + nsv_InBS bs; + for (;;) + { + int ret=nsv_readheader(bs,&m_set_lhdr); + if (ret <= 0 || fillBs(hFile,bs,ret)) break; + } + CloseHandle(hFile); + } + } + + char *p=(char*)m_set_lhdr.metadata; + unsigned int pos=0; + + int omdl=m_set_lhdr.metadata_len; + if (p) while (pos < m_set_lhdr.metadata_len) + { + // scan for = + while (pos < m_set_lhdr.metadata_len && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) { pos++; p++; } + if (pos >= m_set_lhdr.metadata_len) break; + + char *lp=p; + while (pos < m_set_lhdr.metadata_len && *p != '=') { pos++; p++; } + if (pos >= m_set_lhdr.metadata_len) break; + + // skip = + p++; if (++pos >= m_set_lhdr.metadata_len) break; + + // get delim char + char divc=*p++; if (++pos >= m_set_lhdr.metadata_len) break; + + // scan for new delim char + while (pos < m_set_lhdr.metadata_len && *p != divc) { pos++; p++; } + + p++; // advance over our delim char + if (++pos > m_set_lhdr.metadata_len) break; + + + if (!strncmp(lp,data,strlen(data)) && lp[strlen(data)]=='=') + { + if (pos >= m_set_lhdr.metadata_len) + { + m_set_lhdr.metadata_len = (unsigned int)(lp - (char*)m_set_lhdr.metadata); + } + else + { + memcpy(lp,p,m_set_lhdr.metadata_len - (p-(char*)m_set_lhdr.metadata)); + m_set_lhdr.metadata_len -= (unsigned int)(p-lp); + } + break; + } + } + + if (val && *val) + { + unsigned char divc; //fucko + int x; + for (x = 1; x < 256 && strchr(val,x); x ++); + if (x == 256) return 1; + divc=(unsigned char)x; + + int nmdl= (int)(m_set_lhdr.metadata_len + 5 + strlen(data) + strlen(val)); + + if (!m_set_lhdr.metadata || omdl<nmdl) + m_set_lhdr.metadata=realloc(m_set_lhdr.metadata,nmdl); + memcpy((char*)m_set_lhdr.metadata + m_set_lhdr.metadata_len,data,strlen(data)); + m_set_lhdr.metadata_len+= (int)strlen(data); + memcpy((char*)m_set_lhdr.metadata + m_set_lhdr.metadata_len,"=",1); + m_set_lhdr.metadata_len++; + memcpy((char*)m_set_lhdr.metadata + m_set_lhdr.metadata_len,&divc,1); + m_set_lhdr.metadata_len++; + + memcpy((char*)m_set_lhdr.metadata + m_set_lhdr.metadata_len,val,strlen(val)); + m_set_lhdr.metadata_len+= (int)strlen(val); + + memcpy((char*)m_set_lhdr.metadata + m_set_lhdr.metadata_len,&divc,1); + m_set_lhdr.metadata_len++; + + memcpy((char*)m_set_lhdr.metadata + m_set_lhdr.metadata_len," ",1); //space to be ghey + m_set_lhdr.metadata_len++; + } + + return 1; + } + + + __declspec( dllexport ) int winampWriteExtendedFileInfo() + { + lastextfn[0]=0; // flush cache + + if (m_set_lfile[0]) + { + + + + HANDLE hFile = CreateFileA(m_set_lfile,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL); + if (hFile == INVALID_HANDLE_VALUE) + { +// MessageBox(mod.hMainWindow,"Error opening NSV file for update","NSV Tag Error",MB_OK); + return 0; + } + + nsv_OutBS bs; + int osize=m_set_lhdr.header_size; + nsv_writeheader(bs,&m_set_lhdr,osize); + + int hdrlen; + char *hdr=(char*)bs.get(&hdrlen); + + if (hdr && hdrlen == (int)osize) // fast update of header + { + DWORD dw = 0; + SetFilePointer(hFile,0,NULL,SEEK_SET); + WriteFile(hFile,hdr,hdrlen,&dw,NULL); + CloseHandle(hFile); + } + else + { + if (hdr && config_padtag>0) // enlarge header by config_padtag bytes =) + { + bs.clear(); + nsv_writeheader(bs,&m_set_lhdr,config_padtag+m_set_lhdr.header_size); + hdr=(char*)bs.get(&hdrlen); // update + } + + char tmpfn[1024+8]; + char tmpfn2[1024+8]; + wsprintfA(tmpfn,"%s.new",m_set_lfile); + wsprintfA(tmpfn2,"%s.old",m_set_lfile); + + HANDLE hTempFile=CreateFileA(tmpfn,GENERIC_READ|GENERIC_WRITE,0,NULL,CREATE_ALWAYS,0,NULL); + if (hTempFile == INVALID_HANDLE_VALUE) + { + // MessageBox(mod.hMainWindow,"Can't create tempfile - can't update tag!","NSV Tag Editor Error",MB_ICONSTOP|MB_OK); + CloseHandle(hFile); + return 0; + } + SetFilePointer(hFile,osize,NULL,SEEK_SET); + if (hdrlen) + { + DWORD dw = 0; + if (!WriteFile(hTempFile,hdr,hdrlen,&dw,NULL) || (int)dw != hdrlen) + { + CloseHandle(hTempFile); + CloseHandle(hFile); + DeleteFileA(tmpfn); + // MessageBox(mod.hMainWindow,"Error copying source - can't update tag!","NSV Tag Editor Error",MB_ICONSTOP|MB_OK); + return 0; + } + } + + for (;;) + { + char buf[8192] = {0}; + DWORD dw = 0; + BOOL r1=ReadFile(hFile,buf,sizeof(buf),&dw,NULL); + if (r1 && !dw) break; + DWORD dwout = 0; + if (!r1 || !WriteFile(hTempFile,buf,dw,&dwout,NULL) || dwout < dw) + { + CloseHandle(hTempFile); + DeleteFileA(tmpfn); + // MessageBox(mod.hMainWindow,"Error copying source - can't update tag!","NSV Tag Editor Error",MB_ICONSTOP|MB_OK); + return 0; + } + } + if (GetFileSize(hFile,NULL)-osize != GetFileSize(hTempFile,NULL)-hdrlen) + { + CloseHandle(hTempFile); + CloseHandle(hFile); + DeleteFileA(tmpfn); + // MessageBox(mod.hMainWindow,"Error size mismatch - can't update tag!","NSV Tag Editor Error",MB_ICONSTOP|MB_OK); + return 0; + } + CloseHandle(hFile); + CloseHandle(hTempFile); + + stopPlayback(m_set_lfile); + + if (!MoveFileA(m_set_lfile,tmpfn2)) + { + DeleteFileA(tmpfn); + restartPlayback(); + // MessageBox(mod.hMainWindow,"Error renaming source - can't update tag!","NSV Tag Editor Error",MB_ICONSTOP|MB_OK); + return 0; + } + if (!MoveFileA(tmpfn,m_set_lfile)) + { + MoveFileA(tmpfn2,m_set_lfile); + DeleteFileA(tmpfn); + restartPlayback(); + // MessageBox(mod.hMainWindow,"Error renaming new file - can't update tag!","NSV Tag Editor Error",MB_ICONSTOP|MB_OK); + return 0; + } + DeleteFileA(tmpfn2); + restartPlayback(); + } + } + closeset(); + lastextfn[0]=0; + return 1; + } +} + + + +const char *g_lastfile; +HANDLE g_hFile=INVALID_HANDLE_VALUE; +nsv_fileHeader g_filehdr={0,~0,~0,}; +unsigned int g_oldtag_size; +unsigned int *g_toc_save, *g_toc_save_ex, g_toc_savesize; + +void enableControls(HWND hwndDlg, int en) +{ + EnableWindow(GetDlgItem(hwndDlg,IDC_SETTOCSIZE),en); + EnableWindow(GetDlgItem(hwndDlg,IDC_TOC),en); + EnableWindow(GetDlgItem(hwndDlg,IDC_ANALYZE),en); + EnableWindow(GetDlgItem(hwndDlg,IDOK),en); + EnableWindow(GetDlgItem(hwndDlg,IDC_REMTAG),en); + EnableWindow(GetDlgItem(hwndDlg,IDC_ADD),en); + EnableWindow(GetDlgItem(hwndDlg,IDC_REM),en); + EnableWindow(GetDlgItem(hwndDlg,IDC_EDIT),en); + EnableWindow(GetDlgItem(hwndDlg,IDC_FASTUPD),en); +} + +void closeNsv(HWND hwndDlg) +{ + if (g_hFile != INVALID_HANDLE_VALUE) + { + CloseHandle(g_hFile); + g_hFile=INVALID_HANDLE_VALUE; + } + free(g_filehdr.metadata); + g_filehdr.metadata=0; + g_filehdr.metadata_len=0; + free(g_filehdr.toc); + g_filehdr.toc=0; + g_filehdr.toc_ex=0; + free(g_toc_save); + g_toc_save=0; + g_toc_save_ex=0; + + g_toc_savesize=0; + g_filehdr.toc_size=0; + g_filehdr.toc_alloc=0; + g_filehdr.file_lenbytes=~0; + g_filehdr.file_lenms=~0; + g_filehdr.header_size=0; + SetDlgItemTextA(hwndDlg,IDC_LENBYTES,""); + SetDlgItemTextA(hwndDlg,IDC_LENMS,""); + SetDlgItemTextA(hwndDlg,IDC_AVGBITRATE,""); + CheckDlgButton(hwndDlg,IDC_TOC,0); + SetDlgItemTextA(hwndDlg,IDC_TOCSIZE,""); + SetDlgItemTextA(hwndDlg,IDC_SETTOCSIZE,""); + SetDlgItemTextA(hwndDlg,IDC_FN,""); + int x; + int cnt= (int)SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_GETCOUNT,0,0); + for ( x= 0; x < cnt; x ++) + { + void *v=(void *)SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_GETITEMDATA,x,0); + if (v) free(v); + SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_SETITEMDATA,x,0); + } + SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_RESETCONTENT,0,0); + SetDlgItemTextA(hwndDlg,IDC_TAG_LEN,WASABI_API_LNGSTRING(IDS_TOTAL_TAG_SIZE)); + SetDlgItemTextA(hwndDlg,IDC_METADATA_LEN,WASABI_API_LNGSTRING(IDS_NO_METADATA)); + + enableControls(hwndDlg,0); +} + +int fillBs(HANDLE hFile, nsv_InBS &bs, int lenbytes) +{ + while (lenbytes > 0) + { + DWORD r=0; + char buf[8192] = {0}; + BOOL ret=ReadFile(hFile,buf,sizeof(buf),&r,NULL); + lenbytes-=r; + bs.add(buf,r); + if (!ret || !r) return 1; + } + return lenbytes > 0; +} + +void makeNewHeaderInfo(HWND hwndDlg) +{ + nsv_OutBS bs; + g_filehdr.toc_alloc=0; + nsv_writeheader(bs,&g_filehdr,0); + EnableWindow(GetDlgItem(hwndDlg,IDC_FASTUPD),g_filehdr.header_size <= g_oldtag_size); + char buf[128] = {0}; + if (g_filehdr.header_size) wsprintfA(buf,WASABI_API_LNGSTRING(IDS_TOTAL_TAG_SIZE_X_BYTES),g_filehdr.header_size); + else WASABI_API_LNGSTRING_BUF(IDS_NO_TAG,buf,128); + SetDlgItemTextA(hwndDlg,IDC_TAG_LEN,buf); +} + +void populateInfo(HWND hwndDlg) +{ + if (g_filehdr.file_lenbytes != ~0) + { + wchar_t buf[128] = {0}; + unsigned int low=g_filehdr.file_lenbytes; + SetDlgItemTextW(hwndDlg,IDC_LENBYTES,WASABI_API_LNG->FormattedSizeString(buf, 128, low)); + } + else SetDlgItemTextA(hwndDlg,IDC_LENBYTES,"?"); + + if (g_filehdr.file_lenms != ~0) + { + char buf[128] = {0}; + unsigned int timems=g_filehdr.file_lenms; + if (timems < 1000) wsprintfA(buf,"%ums",timems); + else if (timems < 1000*60) wsprintfA(buf,"%02u.%03us",timems/1000,timems%1000); + else if (timems < 1000*60*60) wsprintfA(buf,"%02u:%02u.%03us",timems/60000,(timems/1000)%60,timems%1000); + else wsprintfA(buf,"%u:%02u:%02u.%03us",timems/3600000,(timems/60000)%60,(timems/1000)%60,timems%1000); + + SetDlgItemTextA(hwndDlg,IDC_LENMS,buf); + } + else SetDlgItemTextA(hwndDlg,IDC_LENMS,"?"); + CheckDlgButton(hwndDlg,IDC_TOC,g_filehdr.toc_size?BST_CHECKED:BST_UNCHECKED); + if (g_filehdr.toc_size) + { + char buf[128] = {0}; + wsprintfA(buf,"%d%s",g_filehdr.toc_size,g_filehdr.toc_ex ? " (TOC 2.0)":""); + SetDlgItemTextA(hwndDlg,IDC_TOCSIZE,buf); + SetDlgItemInt(hwndDlg,IDC_SETTOCSIZE,g_filehdr.toc_size,FALSE); + } + else + { + SetDlgItemTextA(hwndDlg,IDC_TOCSIZE,""); + SetDlgItemTextA(hwndDlg,IDC_SETTOCSIZE,"4096"); + } + + if (g_filehdr.file_lenms != ~0 && g_filehdr.file_lenbytes != ~0) + { + unsigned int bitrate = g_filehdr.file_lenms ? MulDiv(g_filehdr.file_lenbytes,8000,g_filehdr.file_lenms) : 0; + char buf[1024] = {0}; + wsprintfA(buf,"%u %s",bitrate/1000,WASABI_API_LNGSTRING(IDS_KBPS)); + SetDlgItemTextA(hwndDlg,IDC_AVGBITRATE,buf); + } + else SetDlgItemTextA(hwndDlg,IDC_AVGBITRATE,"?"); + + char buf[128] = {0}; + if (g_filehdr.header_size) wsprintfA(buf,WASABI_API_LNGSTRING(IDS_TOTAL_TAG_SIZE_X_BYTES),g_filehdr.header_size); + else WASABI_API_LNGSTRING_BUF(IDS_NO_TAG,buf,128); + SetDlgItemTextA(hwndDlg,IDC_TAG_LEN,buf); +} + +void updateMetaData(HWND hwndDlg) +{ + free(g_filehdr.metadata); + g_filehdr.metadata=0; + g_filehdr.metadata_len=0; + int n= (int)SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_GETCOUNT,0,0); + int x; + int total_size=0; + + for (x = 0; x < n; x ++) + { + int l; + char *bigstr = (char *)SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_GETITEMDATA,x,0); + if (bigstr) l = (int)strlen((char *)bigstr)+2; + else l= (int)SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_GETTEXTLEN,x,0); + total_size+=l+2; // text+ two chars + if (x) total_size++; // space + } + + g_filehdr.metadata=malloc(total_size+1); + char *metaout=(char*)g_filehdr.metadata; + for (x = 0; x < n; x ++) + { + if (x) *metaout++=' '; + + const char *bigstr = (const char *)SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_GETITEMDATA,x,0); + + char *this_text; + if (bigstr) this_text=_strdup((const char *)bigstr); + else + { + int l= (int)SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_GETTEXTLEN,x,0); + this_text=(char*)malloc(l+1); + SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_GETTEXT,x,(LPARAM)this_text); + } + char *p=this_text; + while (p && *p != '=' && *p) *metaout++=*p++; + if (p && *p) + { + *metaout++=*p++; + int x; + for (x = 1; x < 256 && strchr(p,x); x ++); + + if (x == 256) + { + char title[64] = {0}; + MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_ERROR_WRITING_STRING_TO_TAG), + WASABI_API_LNGSTRING_BUF(IDS_NSV_TAG_EDITOR_ERROR,title,64), + MB_OK|MB_ICONSTOP); + x=1; + } + + *metaout++=x; + while (p && *p) + { + int a=*p++; + if (a == x) a = '!'; + *metaout++=a; + } + *metaout++=x; + } + free(this_text); + } + g_filehdr.metadata_len= unsigned int(metaout - (char*)g_filehdr.metadata); + *metaout=0; +} + +void populateMetaData(HWND hwndDlg) +{ + wchar_t buf[128] = {0}; + if (g_filehdr.metadata_len) WASABI_API_LNG->FormattedSizeString(buf,128,g_filehdr.metadata_len); + else WASABI_API_LNGSTRINGW_BUF(IDS_NO_METADATA, buf, 128); + SetDlgItemTextW(hwndDlg,IDC_METADATA_LEN,buf); + + int x; + int cnt= (int)SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_GETCOUNT,0,0); + for ( x= 0; x < cnt; x ++) + { + void *v=(void *)SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_GETITEMDATA,x,0); + if (v) free(v); + SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_SETITEMDATA,x,0); + } + SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_RESETCONTENT,0,0); + + if (g_filehdr.metadata) + { + char *p=(char*)g_filehdr.metadata; + for (;;) + { + while (p && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++; + if (!p || !*p) break; + + char *this_name=p; + + // advance to next item + while (p && *p && *p != '=') p++; + char *end_name=p; + + if (!*p++) break; + if (!*p) break; + + char c=*p++; + char *begin_value=p; + while (p && *p && *p != c) p++; + char *end_value=p; + if (*p) p++; + + char *name=(char *)malloc(end_name-this_name + 1 + end_value-begin_value + 1); + char *tmp=name; + memcpy(tmp,this_name,end_name-this_name); tmp+=end_name-this_name; + *tmp++='='; + memcpy(tmp,begin_value,end_value-begin_value); tmp+=end_value-begin_value; + *tmp=0; + + if (strlen(name) < MAX_EDITABLE_METASTRING) + { + LRESULT a=SendDlgItemMessageA(hwndDlg,IDC_METADATA,LB_ADDSTRING,0,(LPARAM)name); + SendDlgItemMessageA(hwndDlg,IDC_METADATA,LB_SETITEMDATA,(WPARAM)a,(LPARAM)0); + free(name); + } + else + { + char buf[512] = {0}; + WASABI_API_LNGSTRING_BUF(IDS_LARGE_DATA,buf,512); + lstrcpynA(buf+strlen(buf),name,128); + strcpy(buf+strlen(buf),"..."); + LRESULT a=SendDlgItemMessageA(hwndDlg,IDC_METADATA,LB_ADDSTRING,0,(LPARAM)buf); + SendDlgItemMessageA(hwndDlg,IDC_METADATA,LB_SETITEMDATA,(WPARAM)a,(LPARAM)name); + } + } + } +} + +void openNsv(HWND hwndDlg) +{ + closeNsv(hwndDlg); + g_hFile = CreateFileA(g_lastfile,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL); + SetDlgItemTextA(hwndDlg,IDC_FN,g_lastfile); + + if (g_hFile == INVALID_HANDLE_VALUE) return; + enableControls(hwndDlg,1); + CheckDlgButton(hwndDlg,IDC_FASTUPD,BST_CHECKED); + // try to read existing tag. + nsv_InBS bs; + for (;;) + { + int ret=nsv_readheader(bs,&g_filehdr); + if (ret <= 0 || fillBs(g_hFile,bs,ret)) break; + } + nsv_Unpacketer unpacket; + char infobuf[256] = {0}; + WASABI_API_LNGSTRING_BUF(IDS_NO_VALID_NSV_BITSTREAM_FOUND,infobuf,256); + for (;;) + { + int ret=unpacket.unpacket(bs); + if (ret < 0) break; + if (ret > 0 && fillBs(g_hFile,bs,ret)) break; + if (!ret) + { + char vfmt[32] = {0}; + char afmt[5] = {0}; + + int fr=(int)(unpacket.getFrameRate()*100.0); + + if (unpacket.getVidFmt()!=NSV_MAKETYPE('V','L','B',' ')) nsv_type_to_string(unpacket.getVidFmt(),vfmt); + else strcpy(vfmt,"Dolby AAC"); + + nsv_type_to_string(unpacket.getAudFmt(),afmt); + + wsprintfA(infobuf,WASABI_API_LNGSTRING(IDS_VIDEO_X_AUDIO_X), + vfmt,unpacket.getWidth(), + unpacket.getHeight(), + fr/100,fr%100,afmt); + break; + } + } + SetDlgItemTextA(hwndDlg,IDC_BSINFO,infobuf); + + g_oldtag_size=g_filehdr.header_size; + populateInfo(hwndDlg); + populateMetaData(hwndDlg); +} + +static int g_metaitem_edit; + +INT_PTR CALLBACK EditProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) +{ + if (uMsg == WM_INITDIALOG) + { + if (g_metaitem_edit>=0) + { + HWND ctl=GetDlgItem(GetParent(hwndDlg),IDC_METADATA); + int x= (int)SendMessage(ctl,LB_GETTEXTLEN,g_metaitem_edit,0); + if (x != LB_ERR) + { + char *t=(char*)malloc(x+1); + if (SendMessage(ctl,LB_GETTEXT,g_metaitem_edit,(LPARAM)t) != LB_ERR) + { + SetDlgItemTextA(hwndDlg,IDC_EDIT1,t); + } + free(t); + } + } + } + if (uMsg == WM_CLOSE) EndDialog(hwndDlg,0); + if (uMsg == WM_COMMAND) + { + if (LOWORD(wParam) == IDCANCEL) EndDialog(hwndDlg,0); + if (LOWORD(wParam) == IDOK) + { + int x= (int)SendDlgItemMessage(hwndDlg,IDC_EDIT1,WM_GETTEXTLENGTH,0,0); + char *t=(char*)malloc(x+3); + GetDlgItemTextA(hwndDlg,IDC_EDIT1,t,x+2); + if (!strstr(t,"=")) + { + char title[32] = {0}; + free(t); + MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_METADATA_STRING_MUST_CONTAIN_ONE_EQUAL_SIGN), + WASABI_API_LNGSTRING_BUF(IDS_NSV_TAG_EDITOR,title,32),MB_OK|MB_ICONINFORMATION); + } + else + { + HWND ctl=GetDlgItem(GetParent(hwndDlg),IDC_METADATA); + LRESULT added; + if (g_metaitem_edit>=0) + { + SendMessage(ctl,LB_DELETESTRING,g_metaitem_edit,0); + added=SendMessage(ctl,LB_INSERTSTRING,g_metaitem_edit,(LPARAM)t); + } + else added=SendMessage(ctl,LB_ADDSTRING,0,(LPARAM)t); + SendMessage(ctl,LB_SETITEMDATA,(WPARAM)added,0); + free(t); + EndDialog(hwndDlg,1); + } + } + } + return 0; + +} + +int doTagWrite(HWND hwndDlg, int writeheader) +{ + nsv_OutBS bs; + if (writeheader) + { + g_filehdr.toc_alloc=0; + nsv_writeheader(bs,&g_filehdr,IsDlgButtonChecked(hwndDlg,IDC_FASTUPD)?g_oldtag_size:0); + } + + int hdrlen; + char *hdr=(char*)bs.get(&hdrlen); + + if (hdr && writeheader && hdrlen == (int)g_oldtag_size) // fast update of header + { + DWORD dw = 0; + SetFilePointer(g_hFile,0,NULL,SEEK_SET); + WriteFile(g_hFile,hdr,hdrlen,&dw,NULL); + } + else if (writeheader || g_oldtag_size) + { + if (hdr && writeheader && config_padtag>0) // enlarge header by config_padtag bytes =) + { + bs.clear(); + g_filehdr.toc_alloc=0; + nsv_writeheader(bs,&g_filehdr,config_padtag+g_filehdr.header_size); + hdr=(char*)bs.get(&hdrlen); // update + } + + char tmpfn[1024+8] = {0}; + char tmpfn2[1024+8] = {0}; + wsprintfA(tmpfn,"%s.new",g_lastfile); + wsprintfA(tmpfn2,"%s.old",g_lastfile); + + HANDLE hTempFile=CreateFileA(tmpfn,GENERIC_READ|GENERIC_WRITE,0,NULL,CREATE_ALWAYS,0,NULL); + if (hTempFile == INVALID_HANDLE_VALUE) + { + char title[64] = {0}; + MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_CANNOT_CREATE_TEMPFILE_CANNOT_UPDATE_TAG), + WASABI_API_LNGSTRING_BUF(IDS_NSV_TAG_EDITOR_ERROR,title,64), + MB_ICONSTOP|MB_OK); + return 1; + } + SetFilePointer(g_hFile,g_oldtag_size,NULL,SEEK_SET); + if (hdrlen) + { + DWORD dw = 0; + if (!WriteFile(hTempFile,hdr,hdrlen,&dw,NULL) || (int)dw != hdrlen) + { + char title[64] = {0}; + CloseHandle(hTempFile); + DeleteFileA(tmpfn); + MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_ERROR_COPYING_SOURCE), + WASABI_API_LNGSTRING_BUF(IDS_NSV_TAG_EDITOR_ERROR,title,64), + MB_ICONSTOP|MB_OK); + return 1; + } + } + + unsigned int bytes=0; + for (;;) + { + DWORD dw = 0; + char buf[8192] = {0}; + BOOL r1=ReadFile(g_hFile,buf,sizeof(buf),&dw,NULL); + if (r1 && !dw) break; + DWORD dwout = 0; + if (!r1 || !WriteFile(hTempFile,buf,dw,&dwout,NULL) || dwout < dw) + { + char title[64] = {0}; + CloseHandle(hTempFile); + DeleteFileA(tmpfn); + MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_ERROR_COPYING_SOURCE), + WASABI_API_LNGSTRING_BUF(IDS_NSV_TAG_EDITOR_ERROR,title,64), + MB_ICONSTOP|MB_OK); + return 1; + } + bytes+=dwout; + wsprintfA(buf,WASABI_API_LNGSTRING(IDS_COPYING_X_BYTES),bytes); + SetDlgItemTextA(hwndDlg,IDC_TAG_LEN,buf); + } + if (GetFileSize(g_hFile,NULL)-g_oldtag_size != GetFileSize(hTempFile,NULL)-hdrlen) + { + char title[64] = {0}; + CloseHandle(hTempFile); + DeleteFileA(tmpfn); + MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_ERROR_SIZE_MISMATCH), + WASABI_API_LNGSTRING_BUF(IDS_NSV_TAG_EDITOR_ERROR,title,64), + MB_ICONSTOP|MB_OK); + return 1; + } + CloseHandle(g_hFile); + CloseHandle(hTempFile); + stopPlayback(g_lastfile); + if (!MoveFileA(g_lastfile,tmpfn2)) + { + char title[64] = {0}; + DeleteFileA(tmpfn); + restartPlayback(); + MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_ERROR_RENAMING_SOURCE), + WASABI_API_LNGSTRING_BUF(IDS_NSV_TAG_EDITOR_ERROR,title,64), + MB_ICONSTOP|MB_OK); + return 1; + } + if (!MoveFileA(tmpfn,g_lastfile)) + { + char title[64] = {0}; + MoveFileA(tmpfn2,g_lastfile); + DeleteFileA(tmpfn); + restartPlayback(); + MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_ERROR_RENAMING_NEW_FILE), + WASABI_API_LNGSTRING_BUF(IDS_NSV_TAG_EDITOR_ERROR,title,64), + MB_ICONSTOP|MB_OK); + return 1; + } + g_hFile = CreateFileA(g_lastfile,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL); + + DeleteFileA(tmpfn2); + restartPlayback(); + } + return 0; +} + +void analyzeFile(HWND hwndDlg) +{ + unsigned int nframes=0; + nsv_Unpacketer unpack; + nsv_InBS bs; + GrowBuf framePos; + unsigned int lastPos=0; + SetFilePointer(g_hFile,g_oldtag_size,NULL,SEEK_SET); + for (;;) + { + int ret=unpack.unpacket(bs); + if (ret) + { + if (ret<0) break; + if (fillBs(g_hFile,bs,ret>0?ret:8)) unpack.setEof(); + } + else + { + if (unpack.isSynchFrame()) + { + framePos.add(&lastPos,4); + framePos.add(&nframes,4); + } + lastPos=SetFilePointer(g_hFile,0,NULL,SEEK_CUR) - (unsigned int)(bs.avail()+7)/8 - g_oldtag_size; + bs.compact(); + nframes++; + char buf[128] = {0}; + wsprintfA(buf,WASABI_API_LNGSTRING(IDS_READING_X_FRAMES),nframes); + SetDlgItemTextA(hwndDlg,IDC_TAG_LEN,buf); + } + } + if (unpack.isValid() && nframes) + { + g_filehdr.file_lenbytes=lastPos; + g_filehdr.file_lenms = (int) (nframes * 1000.0 / unpack.getFrameRate()); + if (IsDlgButtonChecked(hwndDlg,IDC_TOC)) + { + BOOL t; + DWORD d=GetDlgItemInt(hwndDlg,IDC_SETTOCSIZE,&t,FALSE); + if (d && t) + { + g_filehdr.toc_size=d; + g_filehdr.toc_alloc=0; + free(g_filehdr.toc); + + unsigned int x; + unsigned int *in=(unsigned int *)framePos.get(); + unsigned int tf=(unsigned int)framePos.getlen()/8; + g_filehdr.toc=(unsigned int *)malloc(g_filehdr.toc_size * sizeof(unsigned int) * 2); + g_filehdr.toc_ex=g_filehdr.toc + g_filehdr.toc_size; + + if (tf < g_filehdr.toc_size) // we can store all keyframes without dropping any + { + g_filehdr.toc_size=tf; + for (x = 0; x < tf; x ++) + { + g_filehdr.toc[x]=in[x*2]; + g_filehdr.toc_ex[x]=in[x*2+1]; + } + } + else // drop keyframes to fit + { + double pos=0.0; + double dpos = (double) tf / (double) g_filehdr.toc_size; + for (x = 0; x < g_filehdr.toc_size; x ++) + { + unsigned int ipos=(unsigned int)pos; + if (ipos >= tf) + { + g_filehdr.toc_size=x; + break; + } + g_filehdr.toc[x]=in[ipos*2]; + g_filehdr.toc_ex[x]=in[ipos*2+1]; + pos+=dpos; + } + } + } + } + } + else + { + char title[64] = {0}; + MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_NSV_ANALYSIS_FAILED), + WASABI_API_LNGSTRING_BUF(IDS_NSV_TAG_EDITOR_ERROR,title,64), MB_OK); + } +} + +INT_PTR CALLBACK StreamProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) +{ + extern int g_streaminfobuf_used; + switch (uMsg) + { + case WM_INITDIALOG: + SetDlgItemTextA(hwndDlg,IDC_FN,g_lastfile); + g_streaminfobuf_used=1; + SetTimer(hwndDlg,1,1000,NULL); + SendMessage(hwndDlg,WM_TIMER,1,0); + return TRUE; + + case WM_CLOSE: + g_streaminfobuf_used=0; + KillTimer(hwndDlg,1); + EndDialog(hwndDlg,1); + return 0; + + case WM_TIMER: + if (wParam == 1) + { + extern char lastfn[]; + extern char g_streaminfobuf[]; + extern CRITICAL_SECTION g_decoder_cs; + if (!lstrcmpiA(g_lastfile,lastfn) && g_streaminfobuf[0]) + { + int start = -1, end = 0; + EnterCriticalSection(&g_decoder_cs); + SendDlgItemMessage(hwndDlg, IDC_INFO, EM_GETSEL, (WPARAM)&start, (LPARAM)&end); + SetDlgItemTextA(hwndDlg,IDC_INFO,g_streaminfobuf); + SendDlgItemMessage(hwndDlg, IDC_INFO, EM_SETSEL, start, end); + LeaveCriticalSection(&g_decoder_cs); + EnableWindow(GetDlgItem(hwndDlg,IDC_INFO),1); + EnableWindow(GetDlgItem(hwndDlg,IDC_INFOBORDER),1); + } + else + { + SetDlgItemTextA(hwndDlg,IDC_INFO,""); + EnableWindow(GetDlgItem(hwndDlg,IDC_INFO),0); + EnableWindow(GetDlgItem(hwndDlg,IDC_INFOBORDER),0); + } + } + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + case IDCANCEL: + g_streaminfobuf_used=0; + KillTimer(hwndDlg,1); + EndDialog(hwndDlg,1); + return 0; + } + return 0; + } + return 0; +} + +INT_PTR CALLBACK MainProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + openNsv(hwndDlg); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDCANCEL: + closeNsv(hwndDlg); + EndDialog(hwndDlg,1); + return 0; + + case IDOK: + case IDC_REMTAG: + if (!doTagWrite(hwndDlg,LOWORD(wParam) == IDOK)) + { + closeNsv(hwndDlg); + lastextfn[0]=0; + EndDialog(hwndDlg,0); + } + else lastextfn[0]=0; + return 0; + + case IDC_ANALYZE: + analyzeFile(hwndDlg); + populateInfo(hwndDlg); + makeNewHeaderInfo(hwndDlg); + return 0; + + case IDC_ADD: + g_metaitem_edit=-1; + if (WASABI_API_DIALOGBOXW(IDD_DIALOG2,hwndDlg,EditProc)) + { + updateMetaData(hwndDlg); + populateMetaData(hwndDlg); + makeNewHeaderInfo(hwndDlg); + } + return 0; + + case IDC_METADATA: + if (HIWORD(wParam) != LBN_DBLCLK) return 0; + + case IDC_EDIT: + { + DWORD res=(DWORD)SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_GETCURSEL,0,0); + if (res != LB_ERR) + { + int ptr= (int)SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_GETITEMDATA,res,0); + if (ptr) + { + char title[32] = {0}; + MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_METADATA_ITEM_CONTAINS_LARGE_AMOUNT_DATA), + WASABI_API_LNGSTRING_BUF(IDS_NSV_TAG_EDITOR,title,32), MB_OK); + } + else + { + g_metaitem_edit=res; + if (WASABI_API_DIALOGBOXW(IDD_DIALOG2,hwndDlg,EditProc)) + { + updateMetaData(hwndDlg); + populateMetaData(hwndDlg); + makeNewHeaderInfo(hwndDlg); + SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_SETCURSEL,(WPARAM)res,0); + } + } + } + } + return 0; + + case IDC_REM: + { + DWORD res=(DWORD)SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_GETCURSEL,0,0); + if (res != LB_ERR) + { + void *ptr=(void *)SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_GETITEMDATA,res,0); + if (ptr) free(ptr); + SendDlgItemMessage(hwndDlg,IDC_METADATA,LB_DELETESTRING,res,0); + updateMetaData(hwndDlg); + populateMetaData(hwndDlg); + makeNewHeaderInfo(hwndDlg); + } + } + return 0; + + case IDC_TOC: + if (HIWORD(wParam) == BN_CLICKED) + { + int ch=!!IsDlgButtonChecked(hwndDlg,IDC_TOC); + if (ch) + { + if (g_toc_save) + { + g_filehdr.toc=g_toc_save; + g_filehdr.toc_ex=g_toc_save_ex; + g_filehdr.toc_size=g_toc_savesize; + g_toc_save=0; + g_toc_save_ex=0; + g_toc_savesize=0; + makeNewHeaderInfo(hwndDlg); + } + } + else + { + if (!g_toc_save) + { + g_toc_save=g_filehdr.toc; + g_toc_save_ex=g_filehdr.toc_ex; + g_toc_savesize=g_filehdr.toc_size; + g_filehdr.toc=0; + g_filehdr.toc_size=0; + g_filehdr.toc_ex=0; + makeNewHeaderInfo(hwndDlg); + } + } + } + return 0; + } + return 0; + + case WM_CLOSE: + closeNsv(hwndDlg); + EndDialog(hwndDlg,1); + return 0; + } + return 0; +} + +int infoDlg(const char *fn, HWND hwnd) +{ + g_lastfile=fn; + if (strstr(fn,"://")) return (int)WASABI_API_DIALOGBOXW(IDD_DIALOG4,hwnd,StreamProc); + else return (int)WASABI_API_DIALOGBOXW(IDD_DIALOG3,hwnd,MainProc); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_nsv/nsv_logo.bmp b/Src/Plugins/Input/in_nsv/nsv_logo.bmp Binary files differnew file mode 100644 index 00000000..bcdeaf9d --- /dev/null +++ b/Src/Plugins/Input/in_nsv/nsv_logo.bmp diff --git a/Src/Plugins/Input/in_nsv/proxydt.h b/Src/Plugins/Input/in_nsv/proxydt.h new file mode 100644 index 00000000..8802075d --- /dev/null +++ b/Src/Plugins/Input/in_nsv/proxydt.h @@ -0,0 +1,16 @@ +// proxydt.h +#ifndef PROXYDT_H +#define PROXYDT_H + +#include <string> +#include <stdio.h> +#include <atlbase.h> + +char* detectBrowserProxy(); + +char* DetectIEProxy(); + +char* DetectNS4Proxy(); +char* DetectNS6Proxy(); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_nsv/resource.h b/Src/Plugins/Input/in_nsv/resource.h new file mode 100644 index 00000000..108d7bbc --- /dev/null +++ b/Src/Plugins/Input/in_nsv/resource.h @@ -0,0 +1,86 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by res.rc +// +#define IDS_TOTAL_TAG_SIZE 1 +#define IDS_NO_METADATA 2 +#define IDS_TOTAL_TAG_SIZE_X_BYTES 3 +#define IDS_NO_TAG 4 +#define IDS_ERROR_WRITING_STRING_TO_TAG 5 +#define IDS_NSV_TAG_EDITOR_ERROR 6 +#define IDS_LARGE_DATA 7 +#define IDS_NO_VALID_NSV_BITSTREAM_FOUND 8 +#define IDS_VIDEO_X_AUDIO_X 9 +#define IDS_METADATA_STRING_MUST_CONTAIN_ONE_EQUAL_SIGN 10 +#define IDS_NSV_TAG_EDITOR 11 +#define IDS_CANNOT_CREATE_TEMPFILE_CANNOT_UPDATE_TAG 12 +#define IDS_ERROR_COPYING_SOURCE 13 +#define IDS_COPYING_X_BYTES 14 +#define IDS_ERROR_SIZE_MISMATCH 15 +#define IDS_ERROR_RENAMING_SOURCE 16 +#define IDS_ERROR_RENAMING_NEW_FILE 17 +#define IDS_READING_X_FRAMES 18 +#define IDS_NSV_ANALYSIS_FAILED 19 +#define IDS_METADATA_ITEM_CONTAINS_LARGE_AMOUNT_DATA 20 +#define IDS_CONNECTING 21 +#define IDS_OPENING 22 +#define IDS_BUFFER_X 23 +#define IDS_SERVER 24 +#define IDS_CONTENT_TYPE 25 +#define IDS_CONTENT_LENGTH 26 +#define IDS_STREAM_NAME 27 +#define IDS_NSA_NSV_FILE 28 +#define IDS_KBPS 29 +#define IDS_FPS 31 +#define IDS_FAMILY_STRING_NSV 32 +#define IDS_FAMILY_STRING_NSA 33 +#define IDD_DIALOG1 101 +#define IDB_BITMAP1 102 +#define IDD_DIALOG2 103 +#define IDD_DIALOG3 104 +#define IDD_DIALOG4 105 +#define IDD_ABOUT 106 +#define IDC_ANALYZE 1000 +#define IDC_VERSION 1000 +#define IDC_BUF1 1001 +#define IDC_LENBYTES 1001 +#define IDC_BUF2 1002 +#define IDC_LENMS 1002 +#define IDC_BUF3 1003 +#define IDC_TOC 1003 +#define IDC_OFFS 1004 +#define IDC_TOCSIZE 1004 +#define IDC_SETTOCSIZE 1005 +#define IDC_TAGPAD 1005 +#define IDC_METADATA_LEN 1006 +#define IDC_BUF4 1006 +#define IDC_TAG_LEN 1007 +#define IDC_BUF5 1007 +#define IDC_METADATA 1008 +#define IDC_BUF6 1008 +#define IDC_ADD 1009 +#define IDC_REM 1010 +#define IDC_EDIT 1011 +#define IDC_REMTAG 1012 +#define IDC_FASTUPD 1013 +#define IDC_EDIT1 1014 +#define IDC_AVGBITRATE 1014 +#define IDC_FN 1015 +#define IDC_INFO 1016 +#define IDC_BSINFO 1016 +#define IDC_INFOBORDER 1017 +#define IDC_CHECK1 1018 +#define IDC_CHECK3 1019 +#define IDC_CHECK2 1020 +#define IDS_NULLSOFT_NSV_DECODER 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 107 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1019 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_nsv/version.rc2 b/Src/Plugins/Input/in_nsv/version.rc2 new file mode 100644 index 00000000..51e28c40 --- /dev/null +++ b/Src/Plugins/Input/in_nsv/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,76,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", "1,76,0,0" + VALUE "InternalName", "Nullsoft NSV Decoder" + VALUE "LegalCopyright", "Copyright © 2003-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_nsv.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_swf/ExtendedFileInfo.cpp b/Src/Plugins/Input/in_swf/ExtendedFileInfo.cpp new file mode 100644 index 00000000..be0efd14 --- /dev/null +++ b/Src/Plugins/Input/in_swf/ExtendedFileInfo.cpp @@ -0,0 +1,41 @@ +#include "main.h" +#include "resource.h" +#include <shlwapi.h> +#include <strsafe.h> +#include "api.h" + +#define TESTKEYWORD(__keyword, __string)\ + (CSTR_EQUAL == CompareStringA(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT),\ + NORM_IGNORECASE, (__keyword), -1, (__string), -1)) + +extern "C" __declspec( dllexport ) int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, int destlen) +{ + if (TESTKEYWORD("type", data)) + { + if (NULL != dest) + { + int index = 0; + if (destlen > 1) + dest[index++] = L'1'; + dest[index] = L'\0'; + } + return 1; + } + else if (TESTKEYWORD("family", data)) + { + LPCWSTR e, p(NULL); + e = PathFindExtensionW(fn); + if (L'.' != *e) return 0; + e++; + + if (CSTR_EQUAL == CompareStringW(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), + NORM_IGNORECASE, e, -1, L"SWF", -1)) + { + if (S_OK == StringCchCopyW(dest, destlen, WASABI_API_LNGSTRINGW(IDS_FAMILY_STRING))) return 1; + //p = L"Shockwave Flash"; + } + //if (p && S_OK == StringCchCopyW(dest, destlen, p)) return 1; + return 0; + } + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_swf/FLVExternalInterface.cpp b/Src/Plugins/Input/in_swf/FLVExternalInterface.cpp new file mode 100644 index 00000000..817215d6 --- /dev/null +++ b/Src/Plugins/Input/in_swf/FLVExternalInterface.cpp @@ -0,0 +1,75 @@ +#include "FLVExternalInterface.h" +#include "../xml/obj_xml.h" +#include "api.h" +#include "SWFParameters.h" +#include "../Winamp/wa_ipc.h" +#include "main.h" +#include <api/service/waServiceFactory.h> +#include <strsafe.h> + +BSTR FLVExternalInterface::ExternalInterface_call(BSTR xml) +{ + obj_xml *parser=0; + waServiceFactory *parserFactory = plugin.service->service_getServiceByGuid(obj_xmlGUID); + if (parserFactory) + parser = (obj_xml *)parserFactory->getInterface(); + + if (parser) + { + { // artificial scope for SWFParameters + SWFParameters parameters(parser); + parser->xmlreader_open(); + parser->xmlreader_setEncoding(L"UTF-16"); + parser->xmlreader_feed(xml, wcslen(xml)*sizeof(*xml)); + parser->xmlreader_feed(0, 0); + parser->xmlreader_close(); + if (parameters.functionName) + { + if (!wcscmp(parameters.functionName, L"Benski")) + { + } + else if (!wcscmp(parameters.functionName, L"Ready")) + { + unsigned int width, height; + if (parameters.GetUnsigned(0, &width) && parameters.GetUnsigned(1, &height)) + videoOutput->open(width, height, 0, 1.0f /*(double)x/(double)y*/, VIDEO_MAKETYPE('N','O','N','E')); + // TODO: + // Play (if not paused during buffering) + } + else if (!wcscmp(parameters.functionName, L"Complete")) + { + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + } + else if (!wcscmp(parameters.functionName, L"Metadata")) + { + // MessageBox(NULL, xml, L"Flash ExternalInterface.call()", MB_OK); + double duration; + if (parameters.GetDouble(0, &duration)) + playLength = (int)(duration * 1000.0); + + } + else if (!wcscmp(parameters.functionName, L"Buffering")) + { + Nullsoft::Utility::AutoLock autolock(statusGuard); + StringCchCopy(status, 256, L"buffering"); + PostMessage(plugin.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + } + else if (!wcscmp(parameters.functionName, L"Playing")) + { + Nullsoft::Utility::AutoLock autolock(statusGuard); + status[0]=0; + PostMessage(plugin.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + } + else if (!wcscmp(parameters.functionName, L"Playhead")) + { + double playhead; + if (parameters.GetDouble(0, &playhead)) + playPosition = (int)(playhead * 1000.0); + } + } + } + parserFactory->releaseInterface(parser); + } + + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_swf/FLVExternalInterface.h b/Src/Plugins/Input/in_swf/FLVExternalInterface.h new file mode 100644 index 00000000..01c79934 --- /dev/null +++ b/Src/Plugins/Input/in_swf/FLVExternalInterface.h @@ -0,0 +1,8 @@ +#pragma once +#include "FlashDispInterface.h" + +class FLVExternalInterface : public FlashDispInterface +{ +private: + BSTR ExternalInterface_call(BSTR xml); +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_swf/FlashDispInterface.h b/Src/Plugins/Input/in_swf/FlashDispInterface.h new file mode 100644 index 00000000..9cd3a600 --- /dev/null +++ b/Src/Plugins/Input/in_swf/FlashDispInterface.h @@ -0,0 +1,7 @@ +#pragma once +#include <wtypes.h> +class FlashDispInterface +{ +public: + virtual BSTR ExternalInterface_call(BSTR xml) = 0; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_swf/SWFContainer.cpp b/Src/Plugins/Input/in_swf/SWFContainer.cpp new file mode 100644 index 00000000..ac920248 --- /dev/null +++ b/Src/Plugins/Input/in_swf/SWFContainer.cpp @@ -0,0 +1,574 @@ +#include "SWFContainer.h" + +#include <strsafe.h> + +// --------------------------------------------------------------- +IConnectionPoint *SWFContainer::GetConnectionPoint (REFIID riid) +{ + IUnknown *punk = getUnknown (); + if (!punk) + return 0; + + IConnectionPointContainer *pcpc; + IConnectionPoint *pcp = 0; + + HRESULT hr = punk->QueryInterface (IID_IConnectionPointContainer, (void **) & pcpc); + if (SUCCEEDED (hr)) + { + pcpc->FindConnectionPoint (riid, &pcp); + pcpc->Release(); + } + punk->Release(); + return pcp; +} + +void SWFContainer::SyncSizeToWindow(HWND hwnd) +{ + RECT rect; + GetClientRect(hwnd, &rect); + int height = (rect.bottom - rect.top); + + // if we get a null height then hide the html control (after limiting to 1px) + // and also hide it's parent window - is mainly for ml_wire to prevent display + // glitches when resizing the bottom segment all the way to the bottom + //ShowWindow(m_hwnd,height?SW_SHOWNA:SW_HIDE); + //ShowWindow(hwnd,height?SW_SHOWNA:SW_HIDE); + setLocation(0, 0, rect.right - rect.left, height?height:1); +} + + SWFContainer::SWFContainer(HWND hwnd) + : flash (0), m_cRefs(1), m_hwnd(hwnd), m_punk(NULL), + externalInterface(0) + { + + bInitialized = (S_OK == CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)) ? true : false; + + memset(&m_rect, 0, sizeof(m_rect)); + add(ShockwaveFlashObjects::CLSID_ShockwaveFlash); + + IUnknown *punk = getUnknown(); + if (punk) + { + + if (SUCCEEDED(punk->QueryInterface (ShockwaveFlashObjects::IID_IShockwaveFlash, (void **) & flash))) + { + IConnectionPoint *icp = GetConnectionPoint(ShockwaveFlashObjects::DIID__IShockwaveFlashEvents); + if (icp) + { + m_dwCookie = 0; + HRESULT hr = icp->Advise(static_cast<IDispatch *>(this), &m_dwCookie); + icp->Release(); + } + } + else + flash=0; + punk->Release(); + } + } + +SWFContainer::~SWFContainer() +{ + close(); + + if (bInitialized) CoUninitialize(); +} + +void SWFContainer::close() +{ + IOleObject *pioo; + if ( m_punk ) + { + HRESULT hr = m_punk->QueryInterface(IID_IOleObject, (PVOID *) & pioo); + if (SUCCEEDED(hr)) + { + pioo->Close(OLECLOSE_NOSAVE); + pioo->Release(); + } + } + + if (m_punk) + { + m_punk->Release(); + m_punk = NULL; + } + + if (flash) + { + flash->Stop(); + flash->Release(); + flash = 0; + } +} + +STDMETHODIMP SWFContainer::QueryInterface(REFIID riid, PVOID *ppvObject) +{ + if (!ppvObject) + return E_POINTER; + + if (IsEqualIID(riid, IID_IOleClientSite)) + *ppvObject = (IOleClientSite *)this; + else if (IsEqualIID(riid, IID_IOleInPlaceSite)) + *ppvObject = (IOleInPlaceSite *)this; + else if (IsEqualIID(riid, IID_IOleInPlaceFrame)) + *ppvObject = (IOleInPlaceFrame *)this; + else if (IsEqualIID(riid, IID_IOleInPlaceUIWindow)) + *ppvObject = (IOleInPlaceUIWindow *)this; +// else if (IsEqualIID(riid, IID_IOleControlSite)) +// *ppvObject = (IOleControlSite *)this; + else if (IsEqualIID(riid, IID_IOleWindow)) + *ppvObject = this; + else if (IsEqualIID(riid, IID_IDispatch)) + *ppvObject = (IDispatch *)this; + else if (IsEqualIID(riid, IID_IUnknown)) + *ppvObject = this; + else if (IsEqualIID(riid, __uuidof(ShockwaveFlashObjects::_IShockwaveFlashEvents))) + *ppvObject = (IDispatch *)this; + else + { + *ppvObject = NULL; + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; +} + +ULONG SWFContainer::AddRef(void) +{ + return ++m_cRefs; +} + +ULONG SWFContainer::Release(void) +{ + if (--m_cRefs) + return m_cRefs; + +// PostQuitMessage(0); + delete this; + return 0; +} + +HRESULT SWFContainer::SaveObject() +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, LPMONIKER * ppMk) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::GetContainer(LPOLECONTAINER * ppContainer) +{ + return E_NOINTERFACE; +} + +HRESULT SWFContainer::ShowObject() +{ + return S_OK; +} + +HRESULT SWFContainer::OnShowWindow(BOOL fShow) +{ + return S_OK; +} + +HRESULT SWFContainer::RequestNewObjectLayout() +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::GetWindow(HWND * lphwnd) +{ + if (!IsWindow(m_hwnd)) + return S_FALSE; + + *lphwnd = m_hwnd; + return S_OK; +} + +HRESULT SWFContainer::ContextSensitiveHelp(BOOL fEnterMode) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::CanInPlaceActivate(void) +{ + return S_OK; +} + +HRESULT SWFContainer::OnInPlaceActivate(void) +{ + return S_OK; +} + +HRESULT SWFContainer::OnUIActivate(void) +{ + return S_OK; +} + +HRESULT SWFContainer::GetWindowContext(IOleInPlaceFrame ** ppFrame, IOleInPlaceUIWindow ** ppIIPUIWin, + LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo) +{ + *ppFrame = (IOleInPlaceFrame *)this; + *ppIIPUIWin = NULL; + + RECT rect; + GetClientRect(m_hwnd, &rect); + lprcPosRect->left = 0; + lprcPosRect->top = 0; + lprcPosRect->right = rect.right; + lprcPosRect->bottom = rect.bottom; + + CopyRect(lprcClipRect, lprcPosRect); + + lpFrameInfo->cb = sizeof(OLEINPLACEFRAMEINFO); + lpFrameInfo->fMDIApp = FALSE; + lpFrameInfo->hwndFrame = m_hwnd; + lpFrameInfo->haccel = 0; + lpFrameInfo->cAccelEntries = 0; + + (*ppFrame)->AddRef(); + return S_OK; +} + +HRESULT SWFContainer::Scroll(SIZE scrollExtent) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::OnUIDeactivate(BOOL fUndoable) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::OnInPlaceDeactivate(void) +{ + return S_OK; +} + +HRESULT SWFContainer::DiscardUndoState(void) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::DeactivateAndUndo(void) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::OnPosRectChange(LPCRECT lprcPosRect) +{ + return S_OK; +} + +HRESULT SWFContainer::InsertMenus(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::SetMenu(HMENU hmenuShared, HOLEMENU holemenu, HWND hwndActiveObject) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::RemoveMenus(HMENU hmenuShared) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::SetStatusText(LPCOLESTR pszStatusText) +{ + return S_OK; +} + +HRESULT SWFContainer::TranslateAccelerator(LPMSG lpmsg, WORD wID) +{ + return S_OK; +} + +HRESULT SWFContainer::EnableModeless(BOOL fEnable) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::OnControlInfoChanged() +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::LockInPlaceActive(BOOL fLock) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::GetExtendedControl(IDispatch **ppDisp) +{ + if (ppDisp == NULL) + return E_INVALIDARG; + + *ppDisp = (IDispatch *)this; + (*ppDisp)->AddRef(); + + return S_OK; +} + +HRESULT SWFContainer::TransformCoords(POINTL *pptlHimetric, POINTF *pptfContainer, DWORD dwFlags) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::TranslateAccelerator(LPMSG pMsg, DWORD grfModifiers) +{ + return S_FALSE; +} + +HRESULT SWFContainer::OnFocus(BOOL fGotFocus) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::ShowPropertyFrame(void) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid) +{ + *rgdispid = DISPID_UNKNOWN; + return DISP_E_UNKNOWNNAME; +} + +HRESULT SWFContainer::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::GetTypeInfoCount(unsigned int FAR * pctinfo) +{ + return E_NOTIMPL; +} + +#define GET_SAFE_DISP_BSTR(_val) ((_val.pvarVal && VT_BSTR == _val.pvarVal->vt) ? _val.pvarVal->bstrVal : NULL) +#define GET_SAFE_DISP_I4(_val) ((_val.pvarVal && VT_I4 == _val.pvarVal->vt) ? _val.pvarVal->intVal : 0) + + + +void SWFContainer::add(CLSID clsid) +{ + HRESULT hr; // return code + + CoCreateInstance(clsid, + NULL, + CLSCTX_INPROC_SERVER/* | CLSCTX_LOCAL_SERVER*/, + IID_IUnknown, + (PVOID *)&m_punk); + + if (!m_punk) + return ; + + IOleObject *pioo; + hr = m_punk->QueryInterface(IID_IOleObject, (PVOID *) & pioo); + if (FAILED(hr)) + return ; + + pioo->SetClientSite(this); + pioo->Release(); + + IPersistStreamInit *ppsi; + hr = m_punk->QueryInterface(IID_IPersistStreamInit, (PVOID *) & ppsi); + if (SUCCEEDED(hr)) + { + ppsi->InitNew(); + ppsi->Release(); + } +} + +void SWFContainer::remove() +{ + if (!m_punk) + return ; + + HRESULT hr; + IOleObject *pioo; + IOleInPlaceObject *pipo; + + /* + benski> enabling this makes everything lock up! + IConnectionPoint *icp = GetConnectionPoint(DIID_DWebBrowserEvents2); + if (icp) + { +// m_dwCookie = 0; + HRESULT hr = icp->Unadvise(m_dwCookie); + icp->Release(); + } + */ + + hr = m_punk->QueryInterface(IID_IOleObject, (PVOID *) & pioo); + if (SUCCEEDED(hr)) + { + pioo->Close(OLECLOSE_NOSAVE); + pioo->SetClientSite(NULL); + pioo->Release(); + } + + hr = m_punk->QueryInterface(IID_IOleInPlaceObject, (PVOID *) & pipo); + if (SUCCEEDED(hr)) + { + pipo->UIDeactivate(); + pipo->InPlaceDeactivate(); + pipo->Release(); + } + + m_punk->Release(); + m_punk = NULL; +} + + +void SWFContainer::setLocation(int x, int y, int width, int height) +{ + m_rect.left = x; + m_rect.top = y; + m_rect.right = x + width; + m_rect.bottom = y + height; + + if (!m_punk) + return ; + + HRESULT hr; + IOleInPlaceObject *pipo; + + hr = m_punk->QueryInterface(IID_IOleInPlaceObject, (PVOID *) & pipo); + if (FAILED(hr)) + return ; + + pipo->SetObjectRects(&m_rect, &m_rect); + pipo->Release(); +} + +HRESULT SWFContainer::GetBorder(LPRECT lprectBorder) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::RequestBorderSpace(LPCBORDERWIDTHS lpborderwidths) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::SetBorderSpace(LPCBORDERWIDTHS lpborderwidths) +{ + return E_NOTIMPL; +} + +HRESULT SWFContainer::SetActiveObject(IOleInPlaceActiveObject * pActiveObject, LPCOLESTR lpszObjName) +{ + return E_NOTIMPL; +} + +void SWFContainer::setVisible(BOOL fVisible) +{ + if (!m_punk) + return ; + + HRESULT hr; + IOleObject *pioo; + + hr = m_punk->QueryInterface(IID_IOleObject, (PVOID *) & pioo); + if (FAILED(hr)) + return ; + + if (fVisible) + { + pioo->DoVerb(OLEIVERB_INPLACEACTIVATE, NULL, this, 0, m_hwnd, &m_rect); + pioo->DoVerb(OLEIVERB_SHOW, NULL, this, 0, m_hwnd, &m_rect); + } + else + pioo->DoVerb(OLEIVERB_HIDE, NULL, this, 0, m_hwnd, NULL); + + pioo->Release(); +} + +void SWFContainer::setFocus(BOOL fFocus) +{ + if (!m_punk) + return ; + + HRESULT hr; + IOleObject *pioo; + + if (fFocus) + { + hr = m_punk->QueryInterface(IID_IOleObject, (PVOID *) & pioo); + if (FAILED(hr)) + return ; + + pioo->DoVerb(OLEIVERB_UIACTIVATE, NULL, this, 0, m_hwnd, &m_rect); + pioo->Release(); + } +} + +bool SWFContainer::translateKey(LPMSG pMsg) +{ + if (!m_punk) + return false; + + HRESULT hr; + IOleInPlaceActiveObject *pao; + + hr = m_punk->QueryInterface(IID_IOleInPlaceActiveObject, (PVOID *) & pao); + if (FAILED(hr)) + return false; + + HRESULT res = pao->TranslateAccelerator(pMsg); + pao->Release(); + return res == S_OK; +} +enum +{ + FLASH_DISPID_EXTERNALINTERFACE_CALL = 197, +}; + +HRESULT SWFContainer::Invoke( + /* [in] */ DISPID dispIdMember, + /* [in] */ REFIID riid, + /* [in] */ LCID lcid, + /* [in] */ WORD wFlags, + /* [out][in] */ DISPPARAMS *pDispParams, + /* [out] */ VARIANT *pVarResult, + /* [out] */ EXCEPINFO *pExcepInfo, + /* [out] */ UINT *puArgErr) +{ + switch (dispIdMember) + { + case 0x000007a6 : // OnProgress + break; + + case 0x00000096 : // FSCommand + break; + + case FLASH_DISPID_EXTERNALINTERFACE_CALL : // ExternalInterface.call() + { + if (externalInterface) + externalInterface->ExternalInterface_call(pDispParams->rgvarg[0].bstrVal); + } + break; + } + return DISP_E_MEMBERNOTFOUND; +} + + +/************************************************************************** + +* adContainer::getUnknown() + +**************************************************************************/ + +IUnknown * SWFContainer::getUnknown() +{ + if (!m_punk) + return NULL; + + m_punk->AddRef(); + return m_punk; +} diff --git a/Src/Plugins/Input/in_swf/SWFContainer.h b/Src/Plugins/Input/in_swf/SWFContainer.h new file mode 100644 index 00000000..2591bc0c --- /dev/null +++ b/Src/Plugins/Input/in_swf/SWFContainer.h @@ -0,0 +1,144 @@ +#ifndef NULLSOFT_HTMLCONTAINERH +#define NULLSOFT_HTMLCONTAINERH + +#include <oleidl.h> +#include <ocidl.h> +//#import <system32/macromed/Flash/Flash9e.ocx> /*no_namespace, */named_guids, raw_interfaces_only, exclude("IServiceProvider") +#include "Flash9e.tlh" +#include "FlashDispInterface.h" +//#include <shlobj.h> + +/************************************************************************** + class definitions +**************************************************************************/ + + +#ifndef DOCHOSTUIFLAG_HOST_NAVIGATES +#define DOCHOSTUIFLAG_HOST_NAVIGATES 0x02000000 +#endif +#ifndef DOCHOSTUIFLAG_ENABLE_REDIRECT_NOTIFICATION +#define DOCHOSTUIFLAG_ENABLE_REDIRECT_NOTIFICATION 0x04000000 +#endif +#ifndef DOCHOSTUIFLAG_USE_WINDOWLESS_SELECTCONTROL +#define DOCHOSTUIFLAG_USE_WINDOWLESS_SELECTCONTROL 0x08000000 +#endif +#ifndef DOCHOSTUIFLAG_USE_WINDOWED_SELECTCONTROL +#define DOCHOSTUIFLAG_USE_WINDOWED_SELECTCONTROL 0x10000000 +#endif +#ifndef DOCHOSTUIFLAG_ENABLE_ACTIVEX_INACTIVATE_MODE +#define DOCHOSTUIFLAG_ENABLE_ACTIVEX_INACTIVATE_MODE 0x20000000 +#endif + + +class SWFContainer : public IOleClientSite, + public IOleInPlaceSite, + public IOleInPlaceFrame, + public IOleControlSite, + public IDispatch +{ +protected: + ULONG m_cRefs; // ref count + + IUnknown *m_punk; // IUnknown of contained object + RECT m_rect; // size of control + + bool bInitialized; +public: + HWND m_hwnd; // window handle of the container + SWFContainer(HWND hwnd); + virtual ~SWFContainer(); + +public: + // *** IUnknown Methods *** + STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject); + STDMETHOD_(ULONG, AddRef)(void); + STDMETHOD_(ULONG, Release)(void); + + // *** IOleInPlaceUIWindow Methods *** + STDMETHOD (GetBorder)(LPRECT lprectBorder); + STDMETHOD (RequestBorderSpace)(LPCBORDERWIDTHS lpborderwidths); + STDMETHOD (SetBorderSpace)(LPCBORDERWIDTHS lpborderwidths); + STDMETHOD (SetActiveObject)(IOleInPlaceActiveObject * pActiveObject, + LPCOLESTR lpszObjName); + // *** IOleClientSite Methods *** + STDMETHOD (SaveObject)(); + STDMETHOD (GetMoniker)(DWORD dwAssign, DWORD dwWhichMoniker, LPMONIKER *ppMk); + STDMETHOD (GetContainer)(LPOLECONTAINER *ppContainer); + STDMETHOD (ShowObject)(); + STDMETHOD (OnShowWindow)(BOOL fShow); + STDMETHOD (RequestNewObjectLayout)(); + + // *** IOleWindow Methods *** + STDMETHOD (GetWindow) (HWND * phwnd); + STDMETHOD (ContextSensitiveHelp) (BOOL fEnterMode); + + // *** IOleInPlaceSite Methods *** + STDMETHOD (CanInPlaceActivate) (void); + STDMETHOD (OnInPlaceActivate) (void); + STDMETHOD (OnUIActivate) (void); + STDMETHOD (GetWindowContext) (IOleInPlaceFrame ** ppFrame, IOleInPlaceUIWindow ** ppDoc, LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo); + STDMETHOD (Scroll) (SIZE scrollExtent); + STDMETHOD (OnUIDeactivate) (BOOL fUndoable); + STDMETHOD (OnInPlaceDeactivate) (void); + STDMETHOD (DiscardUndoState) (void); + STDMETHOD (DeactivateAndUndo) (void); + STDMETHOD (OnPosRectChange) (LPCRECT lprcPosRect); + + + // *** IOleInPlaceFrame Methods *** + STDMETHOD (InsertMenus)(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths); + STDMETHOD (SetMenu)(HMENU hmenuShared, HOLEMENU holemenu, HWND hwndActiveObject); + STDMETHOD (RemoveMenus)(HMENU hmenuShared); + STDMETHOD (SetStatusText)(LPCOLESTR pszStatusText); + STDMETHOD (EnableModeless)(BOOL fEnable); + STDMETHOD (TranslateAccelerator)(LPMSG lpmsg, WORD wID); + + // *** IOleControlSite Methods *** + STDMETHOD (OnControlInfoChanged)(void); + STDMETHOD (LockInPlaceActive)(BOOL fLock); + STDMETHOD (GetExtendedControl)(IDispatch **ppDisp); + STDMETHOD (TransformCoords)(POINTL *pptlHimetric, POINTF *pptfContainer, DWORD dwFlags); + STDMETHOD (TranslateAccelerator)(LPMSG pMsg, DWORD grfModifiers); + STDMETHOD (OnFocus)(BOOL fGotFocus); + STDMETHOD (ShowPropertyFrame)(void); + + // *** IDispatch Methods *** + STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid); + STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo); + STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo); + STDMETHOD (Invoke) ( + /* [in] */ DISPID dispIdMember, + /* [in] */ REFIID riid, + /* [in] */ LCID lcid, + /* [in] */ WORD wFlags, + /* [out][in] */ DISPPARAMS *pDispParams, + /* [out] */ VARIANT *pVarResult, + /* [out] */ EXCEPINFO *pExcepInfo, + /* [out] */ UINT *puArgErr); + + +public: + void add(CLSID clsid); + void remove(); + + void setLocation(int x, int y, int width, int height); + void setVisible(BOOL fVisible); + void setFocus(BOOL fFocus); + void setStatusWindow(HWND hwndStatus); + bool translateKey(LPMSG pMsg); + + + void close(); + + IUnknown * getUnknown(); + void SyncSizeToWindow(HWND window); + IConnectionPoint *GetConnectionPoint(REFIID riid); + DWORD m_dwCookie; + struct ShockwaveFlashObjects::IShockwaveFlash *flash; + FlashDispInterface *externalInterface; +private: + +}; + + +#endif diff --git a/Src/Plugins/Input/in_swf/SWFParameters.cpp b/Src/Plugins/Input/in_swf/SWFParameters.cpp new file mode 100644 index 00000000..9f272e6b --- /dev/null +++ b/Src/Plugins/Input/in_swf/SWFParameters.cpp @@ -0,0 +1,104 @@ +#include "SWFParameters.h" +#include "../xml/obj_xml.h" +#include <locale.h> + +/* +example: +<invoke name="Benski" returntype="xml"> +<arguments> +</arguments> +</invoke> +*/ + +SWFParameters::SWFParameters(obj_xml *_parser) +{ + parser = _parser; + parser->xmlreader_setCaseSensitive(); + parser->xmlreader_registerCallback(L"invoke", this); + parser->xmlreader_registerCallback(L"invoke\farguments\f*", this); + parser->xmlreader_registerCallback(L"invoke\farguments\f*", ¤tParameter); + functionName=0; + C_locale = _create_locale(LC_NUMERIC, "C"); +} + +SWFParameters::~SWFParameters() +{ + for (ArgumentList::iterator itr=arguments.begin();itr!=arguments.end();itr++) + { + SWFArgument *argument = *itr; + free(argument->type); + free(argument->value); + free(argument); + } + arguments.clear(); + parser->xmlreader_unregisterCallback(this); + parser->xmlreader_unregisterCallback(¤tParameter); +} + +void SWFParameters::StartTag(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params) +{ + if (!wcscmp(xmlpath, L"invoke")) + { + const wchar_t *name = params->getItemValue(L"name"); + if (name) + functionName = _wcsdup(name); + } +} + +void SWFParameters::EndTag(const wchar_t *xmlpath, const wchar_t *xmltag) +{ + if (!wcsncmp(xmlpath, L"invoke\farguments\f", 6 /*invoke*/+ 1/*\f*/ + 9/*arguments*/ + 1/*\f*/)) + { + SWFArgument *argument = new SWFArgument; + argument->type = _wcsdup(xmltag); + const wchar_t *value = currentParameter.GetString(); + if (value) + argument->value = _wcsdup(value); + else + argument->value = 0; + arguments.push_back(argument); + } +} + +bool SWFParameters::GetUnsigned(size_t index, unsigned int *value) +{ + if (index < arguments.size()) + { + SWFArgument *argument = arguments[index]; + if (argument && argument->type && !wcscmp(argument->type, L"number")) + { + const wchar_t *val = argument->value; + if (val) + { + *value = wcstoul(val, 0, 10); + return true; + } + } + } + return false; +} + +bool SWFParameters::GetDouble(size_t index, double *value) +{ + if (index < arguments.size()) + { + SWFArgument *argument = arguments[index]; + if (argument && argument->type && !wcscmp(argument->type, L"number")) + { + const wchar_t *val = argument->value; + if (val) + { + *value = _wtof_l(val, C_locale); + return true; + } + } + } + return false; +} + +#define CBCLASS SWFParameters +START_DISPATCH; +VCB(ONSTARTELEMENT, StartTag) +VCB(ONENDELEMENT, EndTag) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_swf/SWFParameters.h b/Src/Plugins/Input/in_swf/SWFParameters.h new file mode 100644 index 00000000..ca9b68e0 --- /dev/null +++ b/Src/Plugins/Input/in_swf/SWFParameters.h @@ -0,0 +1,37 @@ +#pragma once +#include "../xml/ifc_xmlreadercallback.h" +#include "XMLString.h" +#include <vector> +#include <crtdefs.h> + +class obj_xml; + +struct SWFArgument +{ + wchar_t *type; + wchar_t *value; +}; + +class SWFParameters : public ifc_xmlreadercallback +{ +public: + typedef std::vector<SWFArgument*> ArgumentList; + + SWFParameters(obj_xml *_parser); + ~SWFParameters(); + + wchar_t *functionName; + + bool GetUnsigned(size_t index, unsigned int *value); + bool GetDouble(size_t index, double *value); +private: + void StartTag(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params); + void EndTag(const wchar_t *xmlpath, const wchar_t *xmltag); + XMLString currentParameter; + ArgumentList arguments; + + _locale_t C_locale; + obj_xml *parser; +protected: + RECVS_DISPATCH; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_swf/SWFThread.cpp b/Src/Plugins/Input/in_swf/SWFThread.cpp new file mode 100644 index 00000000..510cb8b1 --- /dev/null +++ b/Src/Plugins/Input/in_swf/SWFThread.cpp @@ -0,0 +1,26 @@ +#include "main.h" +#include "api.h" +#include "SWFContainer.h" + +SWFContainer *activeContainer=0; +WNDPROC oldVidProc=0; +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + PAINTSTRUCT ps; + HDC hdc; + + switch (message) + { + case WM_SIZE: + activeContainer->SyncSizeToWindow(hWnd); + break; + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + hdc = BeginPaint(hWnd, &ps); + EndPaint(hWnd, &ps); + break; + } + return CallWindowProc(oldVidProc, hWnd, message, wParam, lParam); +} + diff --git a/Src/Plugins/Input/in_swf/XMLString.cpp b/Src/Plugins/Input/in_swf/XMLString.cpp new file mode 100644 index 00000000..09d893e2 --- /dev/null +++ b/Src/Plugins/Input/in_swf/XMLString.cpp @@ -0,0 +1,52 @@ +/** (c) Nullsoft, Inc. C O N F I D E N T I A L + ** Filename: + ** Project: + ** Description: + ** Author: Ben Allison benski@nullsoft.com + ** Created: + **/ +#include "main.h" +#include "XMLString.h" +#include <strsafe.h> + +XMLString::XMLString() +{ + data[0]=0; +} + +void XMLString::Reset() +{ + data[0]=0; +} + +const wchar_t *XMLString::GetString() +{ + return data; +} + +void XMLString::StartTag(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params) +{ + data[0]=0; +} + + +void XMLString::TextHandler(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *str) +{ + StringCchCatW(data, XMLSTRING_SIZE, str); +} + + +void XMLString::ManualSet(const wchar_t *string) +{ +StringCchCatW(data, XMLSTRING_SIZE, string); +} + +#ifdef CBCLASS +#undef CBCLASS +#endif + +#define CBCLASS XMLString +START_DISPATCH; +VCB(ONSTARTELEMENT, StartTag) +VCB(ONCHARDATA, TextHandler) +END_DISPATCH; diff --git a/Src/Plugins/Input/in_swf/XMLString.h b/Src/Plugins/Input/in_swf/XMLString.h new file mode 100644 index 00000000..c26eaee0 --- /dev/null +++ b/Src/Plugins/Input/in_swf/XMLString.h @@ -0,0 +1,29 @@ +#ifndef NULLSOFT_WINAMP_XMLSTRING_H +#define NULLSOFT_WINAMP_XMLSTRING_H + + +#include "../xml/ifc_xmlreadercallback.h" +/* +this one is an xml callback that just saves the last encountered string +*/ + +#define XMLSTRING_SIZE 1024 +class XMLString : public ifc_xmlreadercallback +{ +public: + XMLString(); + void Reset(); + const wchar_t *GetString(); + void ManualSet(const wchar_t *string); +private: + /* XML callbacks */ + void StartTag(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params); + void EndTag(const wchar_t *xmlpath, const wchar_t *xmltag); + void TextHandler(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *str); + + wchar_t data[XMLSTRING_SIZE]; // for now, we'll make it dynamic later + + RECVS_DISPATCH; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_swf/api.h b/Src/Plugins/Input/in_swf/api.h new file mode 100644 index 00000000..0d864017 --- /dev/null +++ b/Src/Plugins/Input/in_swf/api.h @@ -0,0 +1,6 @@ +#pragma once + +#include <api/application/api_application.h> +#define WASABI_API_APP applicationApi + +#include "../Agave/Language/api_language.h"
\ No newline at end of file diff --git a/Src/Plugins/Input/in_swf/flash9e.tlh b/Src/Plugins/Input/in_swf/flash9e.tlh new file mode 100644 index 00000000..d2d4d9f4 --- /dev/null +++ b/Src/Plugins/Input/in_swf/flash9e.tlh @@ -0,0 +1,347 @@ +// Created by Microsoft (R) C/C++ Compiler Version 13.10.6030 (0c25145a). +// +// e:\nullsoft\in_swf\release\flash9e.tlh +// +// C++ source equivalent of Win32 type library C:/windows/system32/macromed/Flash/Flash9e.ocx +// compiler-generated file created 04/17/08 at 15:29:08 - DO NOT EDIT! + +#pragma once +#pragma pack(push, 8) + +#include <comdef.h> + +namespace ShockwaveFlashObjects { + +// +// Forward references and typedefs +// + +struct __declspec(uuid("d27cdb6b-ae6d-11cf-96b8-444553540000")) +/* LIBID */ __ShockwaveFlashObjects; +struct __declspec(uuid("d27cdb6c-ae6d-11cf-96b8-444553540000")) +/* dual interface */ IShockwaveFlash; +struct __declspec(uuid("d27cdb6d-ae6d-11cf-96b8-444553540000")) +/* dispinterface */ _IShockwaveFlashEvents; +struct /* coclass */ ShockwaveFlash; +struct __declspec(uuid("d27cdb70-ae6d-11cf-96b8-444553540000")) +/* interface */ IFlashFactory; +struct __declspec(uuid("d27cdb72-ae6d-11cf-96b8-444553540000")) +/* interface */ IFlashObjectInterface; +struct __declspec(uuid("a6ef9860-c720-11d0-9337-00a0c90dcaa9")) +/* interface */ IDispatchEx; +struct /* coclass */ FlashObjectInterface; + +// +// Smart pointer typedef declarations +// + +_COM_SMARTPTR_TYPEDEF(IShockwaveFlash, __uuidof(IShockwaveFlash)); +_COM_SMARTPTR_TYPEDEF(_IShockwaveFlashEvents, __uuidof(_IShockwaveFlashEvents)); +_COM_SMARTPTR_TYPEDEF(IFlashFactory, __uuidof(IFlashFactory)); +_COM_SMARTPTR_TYPEDEF(IDispatchEx, __uuidof(IDispatchEx)); +_COM_SMARTPTR_TYPEDEF(IFlashObjectInterface, __uuidof(IFlashObjectInterface)); + +// +// Type library items +// + +struct __declspec(uuid("d27cdb6c-ae6d-11cf-96b8-444553540000")) +IShockwaveFlash : IDispatch +{ + // + // Raw methods provided by interface + // + + virtual HRESULT __stdcall get_ReadyState ( + /*[out,retval]*/ long * pVal ) = 0; + virtual HRESULT __stdcall get_TotalFrames ( + /*[out,retval]*/ long * pVal ) = 0; + virtual HRESULT __stdcall get_Playing ( + /*[out,retval]*/ VARIANT_BOOL * pVal ) = 0; + virtual HRESULT __stdcall put_Playing ( + /*[in]*/ VARIANT_BOOL pVal ) = 0; + virtual HRESULT __stdcall get_Quality ( + /*[out,retval]*/ int * pVal ) = 0; + virtual HRESULT __stdcall put_Quality ( + /*[in]*/ int pVal ) = 0; + virtual HRESULT __stdcall get_ScaleMode ( + /*[out,retval]*/ int * pVal ) = 0; + virtual HRESULT __stdcall put_ScaleMode ( + /*[in]*/ int pVal ) = 0; + virtual HRESULT __stdcall get_AlignMode ( + /*[out,retval]*/ int * pVal ) = 0; + virtual HRESULT __stdcall put_AlignMode ( + /*[in]*/ int pVal ) = 0; + virtual HRESULT __stdcall get_BackgroundColor ( + /*[out,retval]*/ long * pVal ) = 0; + virtual HRESULT __stdcall put_BackgroundColor ( + /*[in]*/ long pVal ) = 0; + virtual HRESULT __stdcall get_Loop ( + /*[out,retval]*/ VARIANT_BOOL * pVal ) = 0; + virtual HRESULT __stdcall put_Loop ( + /*[in]*/ VARIANT_BOOL pVal ) = 0; + virtual HRESULT __stdcall get_Movie ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_Movie ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_FrameNum ( + /*[out,retval]*/ long * pVal ) = 0; + virtual HRESULT __stdcall put_FrameNum ( + /*[in]*/ long pVal ) = 0; + virtual HRESULT __stdcall SetZoomRect ( + /*[in]*/ long left, + /*[in]*/ long top, + /*[in]*/ long right, + /*[in]*/ long bottom ) = 0; + virtual HRESULT __stdcall Zoom ( + /*[in]*/ int factor ) = 0; + virtual HRESULT __stdcall Pan ( + /*[in]*/ long x, + /*[in]*/ long y, + /*[in]*/ int mode ) = 0; + virtual HRESULT __stdcall Play ( ) = 0; + virtual HRESULT __stdcall Stop ( ) = 0; + virtual HRESULT __stdcall Back ( ) = 0; + virtual HRESULT __stdcall Forward ( ) = 0; + virtual HRESULT __stdcall Rewind ( ) = 0; + virtual HRESULT __stdcall StopPlay ( ) = 0; + virtual HRESULT __stdcall GotoFrame ( + /*[in]*/ long FrameNum ) = 0; + virtual HRESULT __stdcall CurrentFrame ( + /*[out,retval]*/ long * FrameNum ) = 0; + virtual HRESULT __stdcall IsPlaying ( + /*[out,retval]*/ VARIANT_BOOL * Playing ) = 0; + virtual HRESULT __stdcall PercentLoaded ( + /*[out,retval]*/ long * percent ) = 0; + virtual HRESULT __stdcall FrameLoaded ( + /*[in]*/ long FrameNum, + /*[out,retval]*/ VARIANT_BOOL * loaded ) = 0; + virtual HRESULT __stdcall FlashVersion ( + /*[out,retval]*/ long * version ) = 0; + virtual HRESULT __stdcall get_WMode ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_WMode ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_SAlign ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_SAlign ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_Menu ( + /*[out,retval]*/ VARIANT_BOOL * pVal ) = 0; + virtual HRESULT __stdcall put_Menu ( + /*[in]*/ VARIANT_BOOL pVal ) = 0; + virtual HRESULT __stdcall get_Base ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_Base ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_Scale ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_Scale ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_DeviceFont ( + /*[out,retval]*/ VARIANT_BOOL * pVal ) = 0; + virtual HRESULT __stdcall put_DeviceFont ( + /*[in]*/ VARIANT_BOOL pVal ) = 0; + virtual HRESULT __stdcall get_EmbedMovie ( + /*[out,retval]*/ VARIANT_BOOL * pVal ) = 0; + virtual HRESULT __stdcall put_EmbedMovie ( + /*[in]*/ VARIANT_BOOL pVal ) = 0; + virtual HRESULT __stdcall get_BGColor ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_BGColor ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_Quality2 ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_Quality2 ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall LoadMovie ( + /*[in]*/ int layer, + /*[in]*/ BSTR url ) = 0; + virtual HRESULT __stdcall TGotoFrame ( + /*[in]*/ BSTR target, + /*[in]*/ long FrameNum ) = 0; + virtual HRESULT __stdcall TGotoLabel ( + /*[in]*/ BSTR target, + /*[in]*/ BSTR label ) = 0; + virtual HRESULT __stdcall TCurrentFrame ( + /*[in]*/ BSTR target, + /*[out,retval]*/ long * FrameNum ) = 0; + virtual HRESULT __stdcall TCurrentLabel ( + /*[in]*/ BSTR target, + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall TPlay ( + /*[in]*/ BSTR target ) = 0; + virtual HRESULT __stdcall TStopPlay ( + /*[in]*/ BSTR target ) = 0; + virtual HRESULT __stdcall SetVariable ( + /*[in]*/ BSTR name, + /*[in]*/ BSTR value ) = 0; + virtual HRESULT __stdcall GetVariable ( + /*[in]*/ BSTR name, + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall TSetProperty ( + /*[in]*/ BSTR target, + /*[in]*/ int property, + /*[in]*/ BSTR value ) = 0; + virtual HRESULT __stdcall TGetProperty ( + /*[in]*/ BSTR target, + /*[in]*/ int property, + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall TCallFrame ( + /*[in]*/ BSTR target, + /*[in]*/ int FrameNum ) = 0; + virtual HRESULT __stdcall TCallLabel ( + /*[in]*/ BSTR target, + /*[in]*/ BSTR label ) = 0; + virtual HRESULT __stdcall TSetPropertyNum ( + /*[in]*/ BSTR target, + /*[in]*/ int property, + /*[in]*/ double value ) = 0; + virtual HRESULT __stdcall TGetPropertyNum ( + /*[in]*/ BSTR target, + /*[in]*/ int property, + /*[out,retval]*/ double * pVal ) = 0; + virtual HRESULT __stdcall TGetPropertyAsNumber ( + /*[in]*/ BSTR target, + /*[in]*/ int property, + /*[out,retval]*/ double * pVal ) = 0; + virtual HRESULT __stdcall get_SWRemote ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_SWRemote ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_FlashVars ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_FlashVars ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_AllowScriptAccess ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_AllowScriptAccess ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_MovieData ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_MovieData ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_InlineData ( + /*[out,retval]*/ IUnknown * * ppIUnknown ) = 0; + virtual HRESULT __stdcall put_InlineData ( + /*[in]*/ IUnknown * ppIUnknown ) = 0; + virtual HRESULT __stdcall get_SeamlessTabbing ( + /*[out,retval]*/ VARIANT_BOOL * pVal ) = 0; + virtual HRESULT __stdcall put_SeamlessTabbing ( + /*[in]*/ VARIANT_BOOL pVal ) = 0; + virtual HRESULT __stdcall EnforceLocalSecurity ( ) = 0; + virtual HRESULT __stdcall get_Profile ( + /*[out,retval]*/ VARIANT_BOOL * pVal ) = 0; + virtual HRESULT __stdcall put_Profile ( + /*[in]*/ VARIANT_BOOL pVal ) = 0; + virtual HRESULT __stdcall get_ProfileAddress ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_ProfileAddress ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_ProfilePort ( + /*[out,retval]*/ long * pVal ) = 0; + virtual HRESULT __stdcall put_ProfilePort ( + /*[in]*/ long pVal ) = 0; + virtual HRESULT __stdcall CallFunction ( + /*[in]*/ BSTR request, + /*[out,retval]*/ BSTR * response ) = 0; + virtual HRESULT __stdcall SetReturnValue ( + /*[in]*/ BSTR returnValue ) = 0; + virtual HRESULT __stdcall DisableLocalSecurity ( ) = 0; + virtual HRESULT __stdcall get_AllowNetworking ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_AllowNetworking ( + /*[in]*/ BSTR pVal ) = 0; + virtual HRESULT __stdcall get_AllowFullScreen ( + /*[out,retval]*/ BSTR * pVal ) = 0; + virtual HRESULT __stdcall put_AllowFullScreen ( + /*[in]*/ BSTR pVal ) = 0; +}; + +struct __declspec(uuid("d27cdb6d-ae6d-11cf-96b8-444553540000")) +_IShockwaveFlashEvents : IDispatch +{}; + +struct __declspec(uuid("d27cdb6e-ae6d-11cf-96b8-444553540000")) +ShockwaveFlash; + // [ default ] interface IShockwaveFlash + // [ default, source ] dispinterface _IShockwaveFlashEvents + +struct __declspec(uuid("d27cdb70-ae6d-11cf-96b8-444553540000")) +IFlashFactory : IUnknown +{}; + +struct __declspec(uuid("a6ef9860-c720-11d0-9337-00a0c90dcaa9")) +IDispatchEx : IDispatch +{ + // + // Raw methods provided by interface + // + + virtual HRESULT __stdcall GetDispID ( + /*[in]*/ BSTR bstrName, + /*[in]*/ unsigned long grfdex, + /*[out]*/ long * pid ) = 0; + virtual HRESULT __stdcall RemoteInvokeEx ( + /*[in]*/ long id, + /*[in]*/ unsigned long lcid, + /*[in]*/ unsigned long dwFlags, + /*[in]*/ struct DISPPARAMS * pdp, + /*[out]*/ VARIANT * pvarRes, + /*[out]*/ struct EXCEPINFO * pei, + /*[in]*/ struct IServiceProvider * pspCaller, + /*[in]*/ unsigned int cvarRefArg, + /*[in]*/ unsigned int * rgiRefArg, + /*[in,out]*/ VARIANT * rgvarRefArg ) = 0; + virtual HRESULT __stdcall DeleteMemberByName ( + /*[in]*/ BSTR bstrName, + /*[in]*/ unsigned long grfdex ) = 0; + virtual HRESULT __stdcall DeleteMemberByDispID ( + /*[in]*/ long id ) = 0; + virtual HRESULT __stdcall GetMemberProperties ( + /*[in]*/ long id, + /*[in]*/ unsigned long grfdexFetch, + /*[out]*/ unsigned long * pgrfdex ) = 0; + virtual HRESULT __stdcall GetMemberName ( + /*[in]*/ long id, + /*[out]*/ BSTR * pbstrName ) = 0; + virtual HRESULT __stdcall GetNextDispID ( + /*[in]*/ unsigned long grfdex, + /*[in]*/ long id, + /*[out]*/ long * pid ) = 0; + virtual HRESULT __stdcall GetNameSpaceParent ( + /*[out]*/ IUnknown * * ppunk ) = 0; +}; + +struct __declspec(uuid("d27cdb72-ae6d-11cf-96b8-444553540000")) +IFlashObjectInterface : IDispatchEx +{}; + +struct __declspec(uuid("d27cdb71-ae6d-11cf-96b8-444553540000")) +FlashObjectInterface; + // [ default ] interface IFlashObjectInterface + +// +// Named GUID constants initializations +// + +extern "C" const GUID __declspec(selectany) LIBID_ShockwaveFlashObjects = + {0xd27cdb6b,0xae6d,0x11cf,{0x96,0xb8,0x44,0x45,0x53,0x54,0x00,0x00}}; +extern "C" const GUID __declspec(selectany) IID_IShockwaveFlash = + {0xd27cdb6c,0xae6d,0x11cf,{0x96,0xb8,0x44,0x45,0x53,0x54,0x00,0x00}}; +extern "C" const GUID __declspec(selectany) DIID__IShockwaveFlashEvents = + {0xd27cdb6d,0xae6d,0x11cf,{0x96,0xb8,0x44,0x45,0x53,0x54,0x00,0x00}}; +extern "C" const GUID __declspec(selectany) CLSID_ShockwaveFlash = + {0xd27cdb6e,0xae6d,0x11cf,{0x96,0xb8,0x44,0x45,0x53,0x54,0x00,0x00}}; +extern "C" const GUID __declspec(selectany) IID_IFlashFactory = + {0xd27cdb70,0xae6d,0x11cf,{0x96,0xb8,0x44,0x45,0x53,0x54,0x00,0x00}}; +extern "C" const GUID __declspec(selectany) IID_IDispatchEx = + {0xa6ef9860,0xc720,0x11d0,{0x93,0x37,0x00,0xa0,0xc9,0x0d,0xca,0xa9}}; +extern "C" const GUID __declspec(selectany) IID_IFlashObjectInterface = + {0xd27cdb72,0xae6d,0x11cf,{0x96,0xb8,0x44,0x45,0x53,0x54,0x00,0x00}}; +extern "C" const GUID __declspec(selectany) CLSID_FlashObjectInterface = + {0xd27cdb71,0xae6d,0x11cf,{0x96,0xb8,0x44,0x45,0x53,0x54,0x00,0x00}}; + +} // namespace ShockwaveFlashObjects + +#pragma pack(pop) diff --git a/Src/Plugins/Input/in_swf/in_swf.rc b/Src/Plugins/Input/in_swf/in_swf.rc new file mode 100644 index 00000000..66ac58e9 --- /dev/null +++ b/Src/Plugins/Input/in_swf/in_swf.rc @@ -0,0 +1,83 @@ +// 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 + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_SWF "Nullsoft Flash Control Playback v%s" + 65535 "{2430A7AC-317D-4d64-B33C-E1452A6384A2}" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_SWF_OLD "Nullsoft Flash Control Playback" + IDS_SWF_FILES "Shockwave Flash Files" + IDS_FAMILY_STRING "Shockwave Flash" + IDS_ABOUT_TEXT "%s\n© 2008-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_swf/in_swf.sln b/Src/Plugins/Input/in_swf/in_swf.sln new file mode 100644 index 00000000..de471f76 --- /dev/null +++ b/Src/Plugins/Input/in_swf/in_swf.sln @@ -0,0 +1,30 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29609.76 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_swf", "in_swf.vcxproj", "{2B5561EC-78EC-4FA2-A76E-BEBEF3830E80}" +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 + {2B5561EC-78EC-4FA2-A76E-BEBEF3830E80}.Debug|Win32.ActiveCfg = Debug|Win32 + {2B5561EC-78EC-4FA2-A76E-BEBEF3830E80}.Debug|Win32.Build.0 = Debug|Win32 + {2B5561EC-78EC-4FA2-A76E-BEBEF3830E80}.Debug|x64.ActiveCfg = Debug|x64 + {2B5561EC-78EC-4FA2-A76E-BEBEF3830E80}.Debug|x64.Build.0 = Debug|x64 + {2B5561EC-78EC-4FA2-A76E-BEBEF3830E80}.Release|Win32.ActiveCfg = Release|Win32 + {2B5561EC-78EC-4FA2-A76E-BEBEF3830E80}.Release|Win32.Build.0 = Release|Win32 + {2B5561EC-78EC-4FA2-A76E-BEBEF3830E80}.Release|x64.ActiveCfg = Release|x64 + {2B5561EC-78EC-4FA2-A76E-BEBEF3830E80}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C5DFAD3B-75E6-460A-A5E4-65E1C3027572} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_swf/in_swf.vcxproj b/Src/Plugins/Input/in_swf/in_swf.vcxproj new file mode 100644 index 00000000..fca590c7 --- /dev/null +++ b/Src/Plugins/Input/in_swf/in_swf.vcxproj @@ -0,0 +1,258 @@ +<?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>{2B5561EC-78EC-4FA2-A76E-BEBEF3830E80}</ProjectGuid> + <RootNamespace>in_swf</RootNamespace> + <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" 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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|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> + <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_SWF_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_DCOM;%(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> + <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\ +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_SWF_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_DCOM;%(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> + <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\ +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> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;IN_SWF_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_DCOM;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <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> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;IN_SWF_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_DCOM;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <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> + <ClInclude Include="..\..\..\Winamp\strutil.h" /> + <ClInclude Include="api.h" /> + <ClInclude Include="FlashDispInterface.h" /> + <ClInclude Include="FLVExternalInterface.h" /> + <ClInclude Include="main.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="SWFContainer.h" /> + <ClInclude Include="SWFParameters.h" /> + <ClInclude Include="XMLString.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\..\..\Winamp\strutil.cpp" /> + <ClCompile Include="ExtendedFileInfo.cpp" /> + <ClCompile Include="FLVExternalInterface.cpp" /> + <ClCompile Include="main.cpp" /> + <ClCompile Include="SWFContainer.cpp" /> + <ClCompile Include="SWFParameters.cpp" /> + <ClCompile Include="SWFThread.cpp" /> + <ClCompile Include="XMLString.cpp" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_swf.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_swf/in_swf.vcxproj.filters b/Src/Plugins/Input/in_swf/in_swf.vcxproj.filters new file mode 100644 index 00000000..821c542a --- /dev/null +++ b/Src/Plugins/Input/in_swf/in_swf.vcxproj.filters @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="ExtendedFileInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="FLVExternalInterface.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="SWFContainer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="SWFParameters.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="SWFThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="XMLString.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\Winamp\strutil.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="XMLString.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="SWFParameters.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="SWFContainer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="main.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FLVExternalInterface.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FlashDispInterface.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="api.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Winamp\strutil.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{0f49deee-ec41-4975-9692-7c014e9aa506}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{3afcd986-d90d-4cb4-bbea-d20056ada52e}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{3008776d-78c5-48f4-ade1-2aa45acd6338}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_swf.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_swf/main.cpp b/Src/Plugins/Input/in_swf/main.cpp new file mode 100644 index 00000000..8da97055 --- /dev/null +++ b/Src/Plugins/Input/in_swf/main.cpp @@ -0,0 +1,447 @@ +#include "main.h" +#include "api.h" +#include "../Winamp/wa_ipc.h" +#include "../Winamp/strutil.h" +#include <shlwapi.h> +#include "FLVExternalInterface.h" +#include <api/service/waServiceFactory.h> +#include <strsafe.h> +#include "resource.h" + +#define SWF_PLUGIN_VERSION L"1.15" + +FLVExternalInterface flashExternalInterface; +IVideoOutput *videoOutput=0; +int playPosition=0; +int playLength=-1000; +api_application *WASABI_API_APP = 0; +api_language *WASABI_API_LNG = 0; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; +wchar_t pluginName[256] = {0}, status[256] = {0}; +Nullsoft::Utility::LockGuard statusGuard; + +template <class api_T> +static 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> +static void ServiceRelease(api_T *api_t, GUID factoryGUID_t) +{ + if (plugin.service) + { + waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t); + if (factory) + factory->releaseInterface(api_t); + } + api_t = NULL; +} + +void SetFileExtensions(void) +{ + static char fileExtensionsString[1200] = {0}; // "SWF\0Shockwave Flash Files\0" + char* end = 0; + StringCchCopyExA(fileExtensionsString, 1200, "SWF", &end, 0, 0); + StringCchCopyExA(end+1, 1200, WASABI_API_LNGSTRING(IDS_SWF_FILES), 0, 0, 0); + plugin.FileExtensions = fileExtensionsString; +} + +int Init() +{ + if (!IsWindow(plugin.hMainWindow)) + return IN_INIT_FAILURE; + + ServiceBuild(WASABI_API_APP, applicationApiServiceGuid); + ServiceBuild(WASABI_API_LNG, languageApiGUID); + + WASABI_API_START_LANG(plugin.hDllInstance,InSwfLangGUID); + StringCchPrintfW(pluginName,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_SWF),SWF_PLUGIN_VERSION); + plugin.description = (char*)pluginName; + SetFileExtensions(); + + return IN_INIT_SUCCESS; +} + +void Quit() +{ + ServiceRelease(WASABI_API_APP, applicationApiServiceGuid); + ServiceRelease(WASABI_API_LNG, languageApiGUID); +} + +void GetFileInfo(const in_char *file, in_char *title, int *length_in_ms) +{ + if (length_in_ms) + { + if (file && *file) + *length_in_ms=-1000; + else + *length_in_ms = playLength; + } + + if (title) + { + if (file && *file) + *title=0; + else + { + Nullsoft::Utility::AutoLock autolock(statusGuard); + if (status[0]) + StringCchPrintf(title, GETFILEINFO_TITLE_LENGTH, L"[%s]", status); + else + *title=0; + } + } +} + +int InfoBox(const in_char *file, HWND hwndParent) +{ + return INFOBOX_UNCHANGED; +} + +int IsOurFile(const in_char *fn) +{ + if (!_wcsnicmp(fn, L"rtmp://", 7)) + return 1; + return 0; +} + +static bool isFLV = false; +static int PlaySWF(BSTR filename) +{ +#ifdef WIN64 + if (!activeContainer || (unsigned long long)activeContainer < 65536) + { + isFLV = false; + return 1; + } +#else + if (!activeContainer || (unsigned long)activeContainer < 65536) + { + isFLV = false; + return 1; +} +#endif + + isFLV = false; + activeContainer->externalInterface = &flashExternalInterface; + activeContainer->flash->DisableLocalSecurity(); + activeContainer->flash->put_BackgroundColor(0); + activeContainer->flash->put_EmbedMovie(FALSE); + activeContainer->flash->put_Scale(L"showAll"); + activeContainer->flash->put_AllowScriptAccess(L"always"); + + HRESULT hr = activeContainer->flash->LoadMovie(0, filename); + + activeContainer->setVisible(TRUE); + + plugin.is_seekable = 0; // not seekable to start, we'll find out after opening if it's really seekable or not + return 0; +} + +static int PlayFLV(const wchar_t *filename) +{ +#ifdef WIN64 + if (!activeContainer || (unsigned long long)activeContainer < 65536) + { + isFLV = false; + return 1; +} +# else + if (!activeContainer || (unsigned long)activeContainer < 65536) + { + isFLV = false; + return 1; + } +#endif // + +// if (!activeContainer || (unsigned long)activeContainer < 65536) +// { +// isFLV = false; +// return 1; +// } + + isFLV = true; + activeContainer->externalInterface = &flashExternalInterface; + activeContainer->flash->DisableLocalSecurity(); + activeContainer->flash->put_BackgroundColor(0); + activeContainer->flash->put_EmbedMovie(FALSE); + activeContainer->flash->put_Scale(L"showAll"); + activeContainer->flash->put_AllowScriptAccess(L"always"); + + static wchar_t pluginPath[MAX_PATH] = {0}, swfPath[MAX_PATH+7] = {0}; + if (!pluginPath[0] && !swfPath[0]) + { + lstrcpynW(pluginPath, (wchar_t*)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GETPLUGINDIRECTORYW), MAX_PATH); + PathAppend(pluginPath, L"winampFLV.swf"); + for (wchar_t *itr = pluginPath; *itr; itr++) + { + if (*itr == '\\') + *itr = '/'; + } + StringCchPrintf(swfPath, MAX_PATH+7, L"file://%s", pluginPath); + } + + HRESULT hr = activeContainer->flash->LoadMovie(0, swfPath); + + activeContainer->setVisible(TRUE); + + // TODO: make filename XML-safe + wchar_t funcCall[1024] = {0}; + StringCchPrintf(funcCall, 1024, L"<invoke name=\"PlayFLV\" returntype=\"xml\"><arguments><string>%s</string></arguments></invoke>", filename); + BSTR bstr_ret = 0; + activeContainer->flash->CallFunction(funcCall, &bstr_ret); + SetVolume(volume); + SetPan(pan); + + plugin.is_seekable = 1; // not seekable to start, we'll find out after opening if it's really seekable or not + return 0; +} + +int Play(const in_char *filename) +{ + status[0]=0; + playPosition=0; + playLength=-1000; + + if (!filename || !*filename) + return 1; + + if (!videoOutput) + videoOutput = (IVideoOutput *)SendMessage(plugin.hMainWindow,WM_WA_IPC,0,IPC_GET_IVIDEOOUTPUT); + + if (!videoOutput) + return 1; + + HWND videoWnd = (HWND)videoOutput->extended(VIDUSER_GET_VIDEOHWND, 0, 0); // ask for the video hwnd + + wchar_t *mangledFilename = 0; + if (PathIsURL(filename)) + mangledFilename = const_cast<wchar_t *>(filename); + else + { + mangledFilename = (wchar_t *)malloc((MAX_PATH + 7)*sizeof(wchar_t)); + StringCchPrintf(mangledFilename, MAX_PATH+7, L"file://%s", filename); + } + videoOutput->open(0, 0, 0, 1.0, 'ENON'); + activeContainer = new SWFContainer(videoWnd); + if (!activeContainer->flash) + { + delete activeContainer; + activeContainer=0; + if (mangledFilename != filename) + free(mangledFilename); + return 1; // failed + } + + oldVidProc = (WNDPROC)(LONG_PTR)SetWindowLongPtr(videoWnd, GWLP_WNDPROC, (LONG)(LONG_PTR)WndProc); + + wchar_t ext[16]=L""; + extension_exW(filename, ext, 16); + if (!_wcsicmp(ext, L"swf")) + { + if (PlaySWF(mangledFilename)) + { + return 1; // failed + } + } + else + { + if (PlayFLV(mangledFilename)) + { + return 1; // failed + } + } + + HRESULT hr = activeContainer->flash->Play(); + + if (mangledFilename != filename) + free(mangledFilename); + + return 0; +} + +int localPause=0; +void Pause() +{ + localPause=1; + + if (isFLV) + { + BSTR bstr_ret; + activeContainer->flash->CallFunction(L"<invoke name=\"Pause\" returntype=\"xml\"><arguments></arguments></invoke>", &bstr_ret); + } +} + +void UnPause() +{ + localPause=0; + if (isFLV) + { + BSTR bstr_ret; + activeContainer->flash->CallFunction(L"<invoke name=\"Resume\" returntype=\"xml\"><arguments></arguments></invoke>", &bstr_ret); + } +} + +int IsPaused() +{ + return localPause; +} + +void Stop() +{ + videoOutput->close(); + HWND videoWnd = (HWND)videoOutput->extended(VIDUSER_GET_VIDEOHWND, 0, 0); // ask for the video hwnd + SetWindowLongPtr(videoWnd, GWLP_WNDPROC, (LONG)(LONG_PTR)oldVidProc); + activeContainer->close(); + activeContainer->Release(); + activeContainer=0; +} + +int GetLength() +{ + return playLength; +} + +int GetOutputTime() +{ + return playPosition; +} + +void SetOutputTime(int time_in_ms) +{ + if (activeContainer) + { + if (isFLV) + { + double seconds = time_in_ms; + seconds/=1000.0; + + wchar_t funcCall[1024] = {0}; + StringCchPrintf(funcCall, 1024, L"<invoke name=\"Seek\" returntype=\"xml\"><arguments><number>%.3f</number></arguments></invoke>", seconds); + BSTR bstr_ret; + activeContainer->flash->CallFunction(funcCall, &bstr_ret); + } + else + { + // TODO: maybe change the frame? + } + } +} + +int pan = 0, volume = 255; +void SetVolume(int _volume) +{ + volume = _volume; + if (activeContainer) + { + if (isFLV) + { + int newVolume = (volume * 100) / 255; + + wchar_t funcCall[1024] = {0}; + StringCchPrintf(funcCall, 1024, L"<invoke name=\"SetVolume\" returntype=\"xml\"><arguments><number>%u</number></arguments></invoke>", newVolume); + BSTR bstr_ret; + activeContainer->flash->CallFunction(funcCall, &bstr_ret); + } + } +} + +void SetPan(int _pan) +{ + pan = _pan; + if (activeContainer) + { + if (isFLV) + { + int left = 100; + int right = 100; + if (pan < 0) + left += (pan * 100)/127; + if (pan>0) + right-=(pan*100)/127; + + wchar_t funcCall[1024] = {0}; + StringCchPrintf(funcCall, 1024, L"<invoke name=\"SetPan\" returntype=\"xml\"><arguments><number>%u</number><number>%u</number></arguments></invoke>", left, right); + BSTR bstr_ret = 0; + activeContainer->flash->CallFunction(funcCall, &bstr_ret); + } + } +} + +void EQSet(int on, char data[10], int preamp) +{} + +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); +In_Module plugin = +{ + IN_VER_RET, + "nullsoft(in_swf.dll)", + 0, + 0, + 0 /*"SWF\0Shockwave Flash Files\0"*/, + 1, + 1, + 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 +}; + +void About(HWND hwndParent) +{ + wchar_t message[1024] = {0}, text[1024] = {0}; + WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_SWF_OLD,text,1024); + StringCchPrintf(message, 1024, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), + plugin.description, TEXT(__DATE__)); + DoAboutMessageBox(hwndParent,text,message); +} + +extern "C" __declspec(dllexport) In_Module * winampGetInModule2() +{ + return &plugin; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_swf/main.h b/Src/Plugins/Input/in_swf/main.h new file mode 100644 index 00000000..ac62679c --- /dev/null +++ b/Src/Plugins/Input/in_swf/main.h @@ -0,0 +1,24 @@ +#pragma once + +#include "../Winamp/in2.h" +extern In_Module plugin; + +#include <windows.h> + +#include "../Winamp/wa_ipc.h" +extern IVideoOutput *videoOutput; + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); +extern WNDPROC oldVidProc; +#include "SWFContainer.h" +extern SWFContainer *activeContainer; + +extern int playPosition; +extern int playLength; +extern int volume, pan; +void SetVolume(int _volume); +void SetPan(int _pan); + +#include "../nu/AutoLock.h" +extern Nullsoft::Utility::LockGuard statusGuard; +extern wchar_t status[256]; diff --git a/Src/Plugins/Input/in_swf/resource.h b/Src/Plugins/Input/in_swf/resource.h new file mode 100644 index 00000000..b96c6314 --- /dev/null +++ b/Src/Plugins/Input/in_swf/resource.h @@ -0,0 +1,20 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by in_swf.rc +// +#define IDS_NULLSOFT_SWF_OLD 0 +#define IDS_SWF_FILES 1 +#define IDS_FAMILY_STRING 3 +#define IDS_ABOUT_TEXT 4 +#define IDS_NULLSOFT_SWF 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 5 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_swf/version.rc2 b/Src/Plugins/Input/in_swf/version.rc2 new file mode 100644 index 00000000..c9f11f5c --- /dev/null +++ b/Src/Plugins/Input/in_swf/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,15,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", "1,15,0,0" + VALUE "InternalName", "Nullsoft Flash Control Playback" + VALUE "LegalCopyright", "Copyright © 2008-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_swf.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_swf/winampFLV.fla b/Src/Plugins/Input/in_swf/winampFLV.fla Binary files differnew file mode 100644 index 00000000..4a733a79 --- /dev/null +++ b/Src/Plugins/Input/in_swf/winampFLV.fla diff --git a/Src/Plugins/Input/in_vorbis/DlgBase.cpp b/Src/Plugins/Input/in_vorbis/DlgBase.cpp new file mode 100644 index 00000000..43b0f771 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/DlgBase.cpp @@ -0,0 +1,67 @@ +#include "DlgBase.h" + +void DlgBase::MakeComboEdit(UINT id, DWORD s) +{ + HWND w = GetDlgItem(wnd, id); + RECT r; + GetChildRect(id, r); + DestroyWindow(w); + CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", 0, WS_CHILD | s, r.left - 1, r.top - 1, r.right - r.left, r.bottom - r.top, wnd, (HMENU)id, 0, 0); +} + +void DlgBase::GetChildRect(UINT id, RECT& child) +{ + RECT r_parent, r_child; + GetWindowRect(wnd, &r_parent); + GetWindowRect(GetDlgItem(wnd, id), &r_child); + int dx = r_parent.left; + int dy = r_parent.top; + if (!(GetWindowLong(wnd, GWL_STYLE)&WS_CHILD)) + { + dy += GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYDLGFRAME); + dx += GetSystemMetrics(SM_CXDLGFRAME); + } + child.left = r_child.left - dx; + child.right = r_child.right - dx; + child.top = r_child.top - dy; + child.bottom = r_child.bottom - dy; +} + +void DlgBase::do_sizing(UINT wp, RECT * r) + { + UINT dx, dy; + dx = r->right - r->left; + dy = r->bottom - r->top; + if (dx < min_size_x_w) + { + switch (wp) + { + case WMSZ_BOTTOMLEFT: + case WMSZ_LEFT: + case WMSZ_TOPLEFT: + r->left = r->right - min_size_x_w; + break; + case WMSZ_BOTTOMRIGHT: + case WMSZ_RIGHT: + case WMSZ_TOPRIGHT: + r->right = r->left + min_size_x_w; + break; + } + } + if (dy < min_size_y_w) + { + switch (wp) + { + case WMSZ_BOTTOMLEFT: + case WMSZ_BOTTOM: + case WMSZ_BOTTOMRIGHT: + r->bottom = r->top + min_size_y_w; + break; + case WMSZ_TOPLEFT: + case WMSZ_TOP: + case WMSZ_TOPRIGHT: + r->top = r->bottom - min_size_y_w; + break; + } + } + }
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/DlgBase.h b/Src/Plugins/Input/in_vorbis/DlgBase.h new file mode 100644 index 00000000..a194e836 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/DlgBase.h @@ -0,0 +1,118 @@ +#include <windows.h> +#include "main.h" + +static void SetWindowRect(HWND w, RECT * r) +{ + SetWindowPos(w, 0, r->left, r->top, r->right - r->left, r->bottom - r->top, SWP_NOZORDER | SWP_NOCOPYBITS); +} + +class DlgBase +{ + public: + BOOL isDialogMessage(MSG * m) + { + return wnd ? IsDialogMessage(wnd, m) : 0; + } +protected: + void endDialog(int x) + { + EndDialog(wnd, x); + } + + void _do_size_x(RECT * r, UINT id, UINT wx, UINT min_x) + { + RECT r1 = {r->left, r->top, wx - min_x + r->right, r->bottom}; + SetWindowRect(GetDlgItem(wnd, id), &r1); + } + + void _do_size_xy(RECT * r, UINT id, UINT wx, UINT wy, UINT min_x, UINT min_y) + { + RECT r1 = {r->left, r->top, wx - min_x + r->right, wy - min_y + r->bottom}; + SetWindowRect(GetDlgItem(wnd, id), &r1); + } + + void _do_align_x_size_y(RECT * r, UINT id, UINT wx, UINT wy, UINT min_x, UINT min_y) + { + RECT r1 = {wx - min_x + r->left, r->top, wx - min_x + r->right, wy - min_y + r->bottom}; + SetWindowRect(GetDlgItem(wnd, id), &r1); + } + + void _do_align_x(RECT * r, UINT id, UINT wx, UINT min_x) + { + RECT r1 = {wx - min_x + r->left, r->top, wx - min_x + r->right, r->bottom}; + SetWindowRect(GetDlgItem(wnd, id), &r1); + } + + void _do_align_xy(RECT * r, UINT id, UINT wx, UINT wy, UINT min_x, UINT min_y) + { + RECT r1 = {wx - min_x + r->left, wy - min_y + r->top, wx - min_x + r->right, wy - min_y + r->bottom}; + SetWindowRect(GetDlgItem(wnd, id), &r1); + } + +#define do_size_x(id,r) _do_size_x(r,id,sx,min_size_x) +#define do_size_xy(id,r) _do_size_xy(r,id,sx,sy,min_size_x,min_size_y) +#define do_align_x_size_y(id,r) _do_align_x_size_y(r,id,sx,sy,min_size_x,min_size_y) +#define do_align_xy(id,r) _do_align_xy(r,id,sx,sy,min_size_x,min_size_y) +#define do_align_x(id,r) _do_align_x(r,id,sx,min_size_x) + + HWND wnd; + UINT min_size_x, min_size_y; + UINT min_size_x_w, min_size_y_w; + + void do_sizing(UINT wp, RECT * r); + void MakeComboEdit(UINT id, DWORD s); + void GetChildRect(UINT id, RECT& child); + + virtual BOOL DlgProc(UINT msg, WPARAM wp, LPARAM lp) { return 0;}; + static BOOL CALLBACK TheDialogProc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp) + { + DlgBase * p; + if (msg == WM_INITDIALOG) + { + p = (DlgBase*)lp; + SetWindowLong(wnd, DWL_USER, lp); + p->wnd = wnd; + RECT r; + GetClientRect(wnd, &r); + p->min_size_x = r.right; + p->min_size_y = r.bottom; + GetWindowRect(wnd, &r); + p->min_size_x_w = r.right - r.left; + p->min_size_y_w = r.bottom - r.top; + } + else p = (DlgBase*)GetWindowLong(wnd, DWL_USER); + BOOL rv = 0; + if (p) + { + rv = p->DlgProc(msg, wp, lp); + if (msg == WM_DESTROY) + { + p->wnd = 0; + SetWindowLong(wnd, DWL_USER, 0); + } + } + return rv; + } + HWND myCreateDialog(UINT id, HWND parent) + { + return CreateDialogParamT(hIns, (char*)id, parent, TheDialogProc, (long)this); + } + virtual void myProcessMessage(MSG * msg) + { + if (!IsDialogMessage(wnd, msg)) + { + TranslateMessage(msg); + DispatchMessage(msg); + } + } + + int myDialogBox(UINT id, HWND parent) + { + return DialogBoxParamT(hIns, (char*)id, parent, TheDialogProc, (long)this); + } + DlgBase() { + wnd = 0; + min_size_x = min_size_y = min_size_x_w = min_size_y_w = 0; + } + virtual ~DlgBase() {} +}; diff --git a/Src/Plugins/Input/in_vorbis/ExtendedRead.cpp b/Src/Plugins/Input/in_vorbis/ExtendedRead.cpp new file mode 100644 index 00000000..c89f829d --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/ExtendedRead.cpp @@ -0,0 +1,65 @@ +#include "main.h" +#include "decoder.h" + +extern "C" +{ + //returns handle!=0 if successful, 0 if error + //size will return the final nb of bytes written to the output, -1 if unknown + __declspec( dllexport ) intptr_t winampGetExtendedRead_openW(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) { + VorbisFile * f = VorbisFile::Create(fn,false); + if(f) { + if(!*bps) *bps=16; // FUCKO HAX + Decoder * d = new Decoder(); + d->Init(f, *bps, *nch, false, false); + *nch = (int)d->nch; + *srate = (int)d->sr; + *bps = (int)d->bps; + *size = (int)(f->Length() * (double)((*nch) * (*srate) * (*bps/8))); + return (intptr_t)d; + } + return 0; + } + + __declspec( dllexport ) intptr_t winampGetExtendedRead_openW_float(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) { + VorbisFile * f = VorbisFile::Create(fn,false); + if(f) { + Decoder * d = new Decoder(); + d->Init(f, *bps, *nch, true, false); + *nch = (int)d->nch; + *srate = (int)d->sr; + *bps = (int)d->bps; + *size = (int)(f->Length() * (double)((*nch) * (*srate) * (*bps/8))); + return (intptr_t)d; + } + return 0; + } + + //returns nb of bytes read. -1 if read error (like CD ejected). if (ret<len), EOF is assumed + __declspec( dllexport ) intptr_t winampGetExtendedRead_getData(intptr_t handle, char *dest, size_t len, int *killswitch) { + Decoder * d = (Decoder *)handle; + size_t used = 0; + for(;;) { + used += (UINT)d->Read((UINT)(len - used),dest + used); + if(used >= len) break; + if(!d->DoFrame()) break; + if(*killswitch) break; + if (used) + return used; + } + return used; + } + + // return nonzero on success, zero on failure. + __declspec( dllexport ) int winampGetExtendedRead_setTime(intptr_t handle, int millisecs) { + Decoder * d = (Decoder *)handle; + d->Flush(); + return !d->Seek(((double)millisecs) / 1000.0); + } + + __declspec( dllexport ) void winampGetExtendedRead_close(intptr_t handle) { + Decoder * d = (Decoder *)handle; + d->Flush(); + delete d->file; + delete d; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/about.cpp b/Src/Plugins/Input/in_vorbis/about.cpp new file mode 100644 index 00000000..32409519 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/about.cpp @@ -0,0 +1,264 @@ +#include "main.h" +#include "api__in_vorbis.h" +#include "resource.h" +#include <strsafe.h> + +/*static UINT xiphframes_ids[12]={IDB_BITMAP1,IDB_BITMAP2,IDB_BITMAP3,IDB_BITMAP4,IDB_BITMAP5,IDB_BITMAP6,IDB_BITMAP7,IDB_BITMAP8,IDB_BITMAP9,IDB_BITMAP10,IDB_BITMAP11,IDB_BITMAP12}; +static HBITMAP xiphframes[12];*/ + +static UINT xiphframes_ids[12]={IDB_PNG1,IDB_PNG2,IDB_PNG3,IDB_PNG4,IDB_PNG5,IDB_PNG6,IDB_PNG7,IDB_PNG8,IDB_PNG9,IDB_PNG10,IDB_PNG11,IDB_PNG12}; +static ARGB32 *xiphframes[12] = {0}; +static HBITMAP xiphframesBmp[12] = {0}; + +static void slap(HWND wnd,int v) +{ + long hi=GetWindowLong(wnd,4); + if (v) hi+=v*1000; + else hi=0; + SetWindowLong(wnd,4,hi); +} + +static CfgInt cfg_rpm("rpm",0); + +static int visible_rpm,visible_max_rpm; +static char show_rpm=0; +static DWORD last_visible_rpm; + +ARGB32 * loadImg(const void * data, int len, int *w, int *h, bool ldata=false) +{ + FOURCC imgload = svc_imageLoader::getServiceType(); + int n = (int)mod.service->service_getNumServices(imgload); + for(int i=0; i<n; i++) + { + waServiceFactory *sf = mod.service->service_enumService(imgload,i); + if(sf) + { + svc_imageLoader * l = (svc_imageLoader*)sf->getInterface(); + if(l) + { + if(l->testData(data,len)) + { + ARGB32* ret; + if(ldata) ret = l->loadImageData(data,len,w,h); + else ret = l->loadImage(data,len,w,h); + sf->releaseInterface(l); + return ret; + } + sf->releaseInterface(l); + } + } + } + return NULL; +} + +ARGB32 * loadRrc(int id, wchar_t * sec, int *w, int *h, bool data=false) +{ + DWORD size=0; + HGLOBAL resourceHandle = WASABI_API_LOADRESFROMFILEW(sec, MAKEINTRESOURCEW(id), &size); + if(resourceHandle) + { + ARGB32* ret = loadImg(resourceHandle,size,w,h,data); + UnlockResource(resourceHandle); + return ret; + } + return NULL; +} + +static LRESULT WINAPI XiphProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) +{ + switch(msg) + { + case WM_CREATE: + SetWindowLong(wnd,8,last_visible_rpm=GetTickCount()); + SetTimer(wnd,666,10,0); + visible_rpm=-1; + visible_max_rpm=-1; + show_rpm=0; + break; + case WM_TIMER: + if (wp==666) + { + long low=GetWindowLong(wnd,0); + long hi=GetWindowLong(wnd,4); + + long org=low&~0xFFFF; + + int rpm=MulDiv(abs(hi),1000*60,12*0x10000); + + DWORD t=GetTickCount(); + DWORD ot=(DWORD)SetWindowLong(wnd,8,t); + bool redraw=0; + + if (rpm>25) show_rpm=1; + if (cfg_rpm<rpm) cfg_rpm=rpm; + + if (show_rpm && (t&~0x3F)!=(ot&~0x3F)) + { + wchar_t foo[128] = {0}; + if (visible_rpm<rpm || (visible_rpm>rpm && (t-last_visible_rpm)>333)) + { + last_visible_rpm=t; + visible_rpm=rpm; + StringCchPrintfW(foo,128,WASABI_API_LNGSTRINGW(IDS_GAME_SPEED),rpm); + SetDlgItemTextW(GetParent(wnd),IDC_RPM,foo); + } + if (visible_max_rpm!=cfg_rpm) + { + visible_max_rpm=cfg_rpm; + StringCchPrintfW(foo,128,WASABI_API_LNGSTRINGW(IDS_BEST_RPM),(int)cfg_rpm); + SetDlgItemTextW(GetParent(wnd),IDC_RPM2,foo); + } + } + + low+=hi*(t-ot); + while(low<0) low+=12*0x10000; + while(low>=12*0x10000) low-=12*0x10000; + + { + int z=hi>>6; + if (z) hi-=z; + else if (hi>0) hi--; + else if (hi<0) hi++; + } + + SetWindowLong(wnd,0,low); + SetWindowLong(wnd,4,hi); + if (redraw || (low&~0xFFFF)!=org) + { + RedrawWindow(wnd,0,0,RDW_INVALIDATE); + } + KillTimer(wnd,666); + SetTimer(wnd,666,10,0); + } + break; + case WM_LBUTTONDOWN: + slap(wnd,-1); + break; + case WM_RBUTTONDOWN: + slap(wnd,1); + break; + case WM_MBUTTONDOWN: + slap(wnd,0); + break; + case WM_PAINT: + { + int i=(GetWindowLong(wnd,0))>>16; + HDC dc = CreateCompatibleDC(0); + + if (!xiphframesBmp[i]) + { + int cur_w = 0, cur_h = 0; + xiphframes[i] = loadRrc(xiphframes_ids[i], L"PNG", &cur_w, &cur_h, true); + + BITMAPINFO bmi = {0}; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = cur_w; + bmi.bmiHeader.biHeight = -cur_h; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + void *bits = 0; + if(xiphframesBmp[i]) DeleteObject(xiphframesBmp[i]); + xiphframesBmp[i] = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, &bits, NULL, 0); + memcpy(bits, xiphframes[i], cur_w * cur_h * 4); + } + + if (xiphframesBmp[i]) + { + HGDIOBJ foo = SelectObject(dc, xiphframesBmp[i]); + HDC wdc = GetDC(wnd); + RECT r = {0}; + GetClientRect(wnd, &r); + FillRect(wdc, &r, GetSysColorBrush(COLOR_3DFACE)); + + BLENDFUNCTION blendFn = {0}; + blendFn.BlendOp = AC_SRC_OVER; + blendFn.SourceConstantAlpha = 255; + blendFn.AlphaFormat = AC_SRC_ALPHA; + AlphaBlend(wdc, 2, 2, r.right - 2, r.bottom - 2, dc, 0, 0, 63, 63, blendFn); + + ReleaseDC(wnd, wdc); + SelectObject(dc, foo); + } + DeleteDC(dc); + } + break; + case WM_DESTROY: + { + for (int i = 0; i < ARRAYSIZE(xiphframes_ids); i++) + { + if(xiphframesBmp[i]) DeleteObject(xiphframesBmp[i]); xiphframesBmp[i] = 0; + if(xiphframes[i] && WASABI_API_MEMMGR) WASABI_API_MEMMGR->sysFree((void *)xiphframes[i]); xiphframes[i] = 0; + } + KillTimer(wnd,666); + break; + } + }; + return DefWindowProc(wnd,msg,wp,lp); +} + +static BOOL CALLBACK AboutProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) +{ + switch(msg) + { + case WM_INITDIALOG: + { + wchar_t tmp[1024] = {0}, tmp2[1024] = {0}, *t1 = tmp, *t2 = tmp2, text[1024] = {0}; + SetWindowTextW(wnd,WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_VORBIS_DECODER_OLD,text,1024)); + StringCchPrintfW(tmp,1024,WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT),mod.description,__DATE__); + // due to quirks with the more common resource editors, is easier to just store the string + // internally only with \n and post-process to be \r\n (as here) so it will appear correctly + // on new lines as is wanted (silly multiline edit controls) + while(t1 && *t1 && (t2 - tmp2 < 1024)) + { + if(*t1 == L'\n') + { + *t2 = L'\r'; + t2 = CharNextW(t2); + } + *t2 = *t1; + t1 = CharNextW(t1); + t2 = CharNextW(t2); + } + + SetDlgItemTextW(wnd,IDC_ABOUT_TEXT,tmp2); + // fixes the incorrect selection of the text on dialog opening + PostMessage(GetDlgItem(wnd,IDC_ABOUT_TEXT),EM_SETSEL,-1,0); + return 1; + } + case WM_COMMAND: + if (wp==IDOK || wp==IDCANCEL) + { + do_cfg(1); + EndDialog(wnd,0); + } + break; + } + return 0; +} + +void About(HWND hwndParent) +{ + static char got_xiph; + if (!got_xiph) + { + WNDCLASS wc= + { + 0, + XiphProc, + 0, + 12, + WASABI_API_LNG_HINST, + 0, + LoadCursor(0,IDC_ARROW), + 0, + 0, + L"XIPH_CLASS", + }; + + RegisterClassW(&wc); + got_xiph=1; + } + + WASABI_API_DIALOGBOXW(IDD_ABOUT,hwndParent,AboutProc); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/api__in_vorbis.h b/Src/Plugins/Input/in_vorbis/api__in_vorbis.h new file mode 100644 index 00000000..5f89683e --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/api__in_vorbis.h @@ -0,0 +1,21 @@ +#ifndef NULLSOFT_API_H +#define NULLSOFT_API_H + +#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 <api/service/svcs/svc_imgload.h> + +#include <api/service/api_service.h> + +#include <api/service/waServiceFactory.h> + +#include <api/memmgr/api_memmgr.h> +extern api_memmgr *memmgrApi; +#define WASABI_API_MEMMGR memmgrApi + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/c_string.cpp b/Src/Plugins/Input/in_vorbis/c_string.cpp new file mode 100644 index 00000000..4310a0dd --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/c_string.cpp @@ -0,0 +1,96 @@ +#define STRICT +#include <windows.h> +#include <malloc.h> +#include <stdio.h> +#include "c_string.h" +#include "../nu/ns_wc.h" + +extern BOOL is_nt; + +template<class myChar> +void string_base<myChar>::makespace(UINT s) +{ + if (size<s) + { + int oldSize = size; + do size<<=1; while(size<s); + myChar *newPtr = (myChar*)realloc(ptr,size*sizeof(myChar)); + if (!newPtr) + { + newPtr = (myChar*)malloc(size*sizeof(myChar)); + if (newPtr) + { + memcpy(newPtr, ptr, oldSize*sizeof(myChar)); + free(ptr); + ptr = newPtr; + } + else return ; + } + else ptr = newPtr; + } +} + +void String::s_GetWindowText(HWND w) +{ + Reset(); + int len=GetWindowTextLengthA(w)+1; + GetWindowTextA(w,StringTempA(*this,len),len); +} + +void StringW::s_GetWindowText(HWND w) +{ + Reset(); + int len=GetWindowTextLengthW(w)+1; + GetWindowTextW(w,StringTempW(*this,len),len); +} + +void String::SetStringW(const WCHAR * c) +{ + UINT len=(lstrlenW(c)+1)*2; + WideCharToMultiByteSZ(CP_ACP,0,c,-1,StringTempA(*this,len),len,0,0); +} + +void StringW::SetStringA(const char * c) +{ + UINT len=(UINT)strlen(c)+1; + MultiByteToWideCharSZ(CP_ACP,0,c,-1,StringTempW(*this,len),len); +} + +void String::AddStringW(const WCHAR * c) +{ + AddString(String(c)); +} + +void StringW::AddStringA(const char * c) +{ + AddString(StringW(c)); +} + +void String::s_SetWindowText(HWND w) +{ + SetWindowTextA(w,*this); +} + +void StringW::s_SetWindowText(HWND w) +{ + SetWindowTextW(w,*this); +} + + +StringPrintf::StringPrintf(const char * fmt,...) +{ + va_list list; + va_start(list,fmt); + vsprintf(StringTempA(*this,1024),fmt,list); + va_end(list); +} + +StringPrintfW::StringPrintfW(const WCHAR * fmt,...) +{ + va_list list; + va_start(list,fmt); + vswprintf(StringTempW(*this,1024),1024,fmt,list); + va_end(list); +} + +String::String(const StringW & z) {AddStringW(z);}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/c_string.h b/Src/Plugins/Input/in_vorbis/c_string.h new file mode 100644 index 00000000..9773c078 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/c_string.h @@ -0,0 +1,156 @@ +#pragma once +template <class myChar> +class string_base +{ +private: + myChar * ptr; + UINT size,used; + void makespace(UINT s); + static UINT mylen(const myChar * p) {UINT r=0;while(p[r]) r++;return r;} +public: + void AddChar(myChar c) + { + makespace(used+2); + ptr[used++]=c; + ptr[used]=0; + } + string_base() + { + used=0; + size=128; + ptr=(myChar*)malloc(size*sizeof(myChar)); + ptr[0]=0; + } + + ~string_base() { if (ptr) free(ptr);} + + operator const myChar*() const {return ptr;} + + const myChar & operator*() const {return *ptr;} + + UINT Length() const {return used;} + + void AddString(const myChar * c) + { + UINT d=mylen(c); + makespace(used+d+1); + memcpy(ptr+used,c,sizeof(myChar)*d); + used+=d; + ptr[used]=0; + } + + void Reset() {Truncate(0);} + void Truncate(UINT x) {if (used>x) {used=x;ptr[x]=0;}} + + void SetString(const myChar * s) {Reset();AddString(s);} + + myChar * BufferStart(UINT n) + { + makespace(n+1); + memset(ptr,0,size); + return ptr; + } + + inline void BufferDone() {used=mylen(ptr);} + + void SetChar(UINT offset,myChar c)//hack for some ghey routines + { + if (!c) Truncate(offset); + else if (offset<used) ptr[offset]=c; + } +}; + +template<class myChar> +class StringTemp +{ +private: + string_base<myChar> * parent; + myChar * data; +public: + StringTemp(string_base<myChar> & s,UINT siz) {parent=&s;data=s.BufferStart(siz);} + ~StringTemp() {parent->BufferDone();} + operator myChar* () {return data;} +}; + +#define StringTempW StringTemp<WCHAR> +#define StringTempA StringTemp<char> + +class StringW; + +class String : public string_base<char> +{ +public: + String() {} + String(HWND w) {s_GetWindowText(w);} + String(const char* z) {SetString(z);} + String(const WCHAR* z) {SetStringW(z);} + String(const String& z) {SetString(z);} + String(const StringW& z); + void AddStringW(const WCHAR * c); + void SetStringW(const WCHAR * c); + void s_GetWindowText(HWND w); + void s_SetWindowText(HWND w); + void operator=(const char * s) {SetString(s);} + void operator+=(const char * s) {AddString(s);} + void operator=(String & s) {SetString(s);} + void operator+=(String & s) {AddString(s);} + inline void s_GetDlgItemText(HWND w,UINT id) {s_GetWindowText(GetDlgItem(w,id));} + inline void s_SetDlgItemText(HWND w,UINT id) {s_SetWindowText(GetDlgItem(w,id));} +}; + +class StringW : public string_base<WCHAR> +{ +public: + StringW() {} + StringW(HWND w) {s_GetWindowText(w);} + StringW(const WCHAR * z) {SetString(z);} + void AddStringA(const char * c); + void SetStringA(const char * c); + StringW(const char * z) {SetStringA(z);} + StringW(const StringW & z) {SetString(z);} + StringW(const String & z) {SetStringA(z);} + void s_GetWindowText(HWND w); + void s_SetWindowText(HWND w); + void operator=(const WCHAR * s) {SetString(s);} + void operator+=(const WCHAR * s) { if (s) AddString(s);} + void operator=(StringW & s) {SetString(s);} + void operator+=(StringW & s) {AddString(s);} + inline void s_GetDlgItemText(HWND w,UINT id) {s_GetWindowText(GetDlgItem(w,id));} + inline void s_SetDlgItemText(HWND w,UINT id) {s_SetWindowText(GetDlgItem(w,id));} + bool reg_read(const char *name); + void reg_write(const char *name); +}; + + +class StringPrintf : public String +{ +public: + StringPrintf(const char * fmt,...); +}; + +class StringPrintfW : public StringW +{ +public: + StringPrintfW(const WCHAR * fmt,...); +}; + +template<class myChar> +class StringF2T : public string_base<myChar> +{ +public: + StringF2T(const myChar * fn) + { + const myChar * ptr=fn,*dot=0,*src=fn; + while(ptr && *ptr) + { + if (*ptr=='\\' || *ptr=='/' || *ptr==':') src=ptr+1; + else if (*ptr=='.') dot=ptr; + ptr++; + } + + while(src && *src && (!dot || src<dot)) AddChar(*(src++)); + } +}; + +#define StringF2T_A StringF2T<char> +#define StringF2T_W StringF2T<WCHAR>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/chainedstream_parse.cpp b/Src/Plugins/Input/in_vorbis/chainedstream_parse.cpp new file mode 100644 index 00000000..35ed0198 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/chainedstream_parse.cpp @@ -0,0 +1,82 @@ +#include <windows.h> +#include <stdlib.h> +#include <stdio.h> + +#include <ogg/ogg.h> +#include <vorbis/vorbisfile.h> + +static size_t callback_fread(void *ptr, size_t size, size_t nmemb, HANDLE hFile) +{ + DWORD bw = 0; + ReadFile(hFile,ptr,(DWORD)(size*nmemb),&bw,0); + return bw/size; +} + +static size_t callback_write(void * ptr, size_t size, size_t nmemb, HANDLE hFile) +{ + DWORD bw = 0; + WriteFile(hFile,ptr,(DWORD)(size*nmemb),&bw,0); + return bw/size; +} + +static int callback_fseek(HANDLE hFile, __int64 offset, int whence) +{ + __int64 temp = offset; + SetFilePointer(hFile,*(DWORD*)&temp,((long*)&temp+1),whence); + return 0; +} + +static int callback_fclose(HANDLE f) +{ + return 0; +} + +static __int64 callback_ftell(HANDLE hFile) +{ + __int64 ret=0; + *(DWORD*)&ret = SetFilePointer(hFile,0,((long*)&ret+1),FILE_CURRENT); + return ret; +} + +static void* callbacks[4]= +{ + callback_fread,callback_fseek,callback_fclose,callback_ftell +}; + +namespace ogg_helper +{ + int num_get_tracks(HANDLE hFile/*track_indexer::callback * out,reader * r*/) + { + SetFilePointer(hFile,0,0,FILE_BEGIN); + OggVorbis_File l_vf; + memset(&l_vf,0,sizeof(l_vf)); + if (ov_open_callbacks(hFile,&l_vf,0,0,*(ov_callbacks*)callbacks)) + { + return 0; + } + int rv = l_vf.links; + ov_clear(&l_vf); + return rv; + } + + int query_chained_stream_offset(HANDLE hFile,int idx,__int64 * out_beginning,__int64 * out_end) + { + SetFilePointer(hFile,0,0,FILE_BEGIN); + OggVorbis_File l_vf; + memset(&l_vf,0,sizeof(l_vf)); + if (ov_open_callbacks(hFile,&l_vf,0,0,*(ov_callbacks*)callbacks)) + { + return 0; + } + int retval = 0; + if (idx>=0 && idx<l_vf.links) + { + retval = 1; + *out_beginning = l_vf.offsets[idx]; + *out_end = l_vf.offsets[idx+1]; + } + + ov_clear(&l_vf); + return retval; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/config.cpp b/Src/Plugins/Input/in_vorbis/config.cpp new file mode 100644 index 00000000..fc3d6afd --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/config.cpp @@ -0,0 +1,335 @@ +#include "main.h" +#include "api__in_vorbis.h" +#include "../nu/ns_wc.h" +#include <commctrl.h> +#include <shlobj.h> +#include "../winamp/wa_ipc.h" +#include "../nu/AutoChar.h" +#include <strsafe.h> + +int mc6_dm_names_ids[]={IDS_LEAVE_AS_IS,IDS_REMAP_6_CHANNELS,IDS_DOWNMIX_TO_4_CHANNELS,IDS_DOWNMIX_TO_2_CHANNELS_DS,IDS_DOWNMIX_TO_2_CHANNELS_DS2,IDS_DOWNMIX_TO_MONO}; +int mc6_map_names_id[]={IDS_CORRECT_FL_FC_FR_BL_BR_LFE,IDS_BROKEN_FL_FR_FC_BL_BR_LFE}; +int32_t priority_tab[7]={THREAD_PRIORITY_IDLE,THREAD_PRIORITY_LOWEST,THREAD_PRIORITY_BELOW_NORMAL,THREAD_PRIORITY_NORMAL,THREAD_PRIORITY_ABOVE_NORMAL,THREAD_PRIORITY_HIGHEST,THREAD_PRIORITY_TIME_CRITICAL}; + +char* defaultDumpDir() +{ + static char dumpdir[MAX_PATH] = {0}; + if(FAILED(SHGetFolderPathA(NULL, CSIDL_MYMUSIC, NULL, SHGFP_TYPE_CURRENT, dumpdir))) + { + if(FAILED(SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, dumpdir))) + { + lstrcpynA(dumpdir, "C:\\", MAX_PATH); + } + } + return dumpdir; +} + +CfgString + cfg_ssave_format("ssave_format","%filename%"), + cfg_dumpdir("dumpdir",defaultDumpDir()); + +CfgInt + cfg_http_bsize("http_bsize",0x10000), + cfg_fsave("fsave",0), + cfg_abr("abr",0), + cfg_proxy_mode("proxy_mode",2), + cfg_prebuf1("prebuf1",50), + cfg_prebuf2("prebuf2",75), + cfg_httpseek2("httpseek2",0), + cfg_fix0r("fix0r",1), + cfg_mc6_dm("mc6_dm",0), + cfg_mc6_map("_mc6_map",0), + cfg_remember_infosize("remember_infosize",1), + cfg_fullbuf("fullbuf",0), + cfg_cur_tab("cur_tab",0); + +static int old_preamp; +CfgFont cfg_font("font"); +static LOGFONT cfg_font_edit; + +BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam) +{ + char cl[32] = {0}; + GetClassNameA(hwnd, cl, ARRAYSIZE(cl)); + if (!lstrcmpiA(cl, WC_TREEVIEWA)) + { + PostMessage(hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection(hwnd)); + return FALSE; + } + + return TRUE; +} + +static int CALLBACK browzaproc(HWND hwnd, UINT msg, LPARAM lp, LPARAM dat) +{ + if (msg == BFFM_INITIALIZED) + { + SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)dat); + + // this is not nice but it fixes the selection not working correctly on all OSes + EnumChildWindows(hwnd, browseEnumProc, 0); + } + return 0; +} + +static void d_browza(HWND wnd,HWND bt,wchar_t* tx) +{ + IMalloc* pMalloc=0; + + SHGetMalloc(&pMalloc); + if (!pMalloc) return; + + wchar_t dir[MAX_PATH] = {0}; + GetWindowTextW(bt,dir,MAX_PATH); + BROWSEINFOW bi= + { + wnd, + 0, + 0, + tx, + BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE, + browzaproc, + (LPARAM)dir, + 0 + }; + ITEMIDLIST* li=SHBrowseForFolderW(&bi); + if (li) + { + SHGetPathFromIDListW(li,dir); + SetWindowTextW(bt,dir); + pMalloc->Free(li); + } + + pMalloc->Release(); +} + +static BOOL CALLBACK CfgProc1(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) +{ + switch(msg) + { + case WM_INITDIALOG: + { + wchar_t temp[128] = {0}, cfg_dialog_name[128] = {0}; + StringCchPrintfW(cfg_dialog_name,128,WASABI_API_LNGSTRINGW(IDS_TITLE_PREFERENCES), + WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_VORBIS_DECODER_OLD, temp, 128)); + SetWindowTextW(wnd,cfg_dialog_name); + + SendDlgItemMessage(wnd,IDC_FULLBUF,BM_SETCHECK,cfg_fullbuf,0); + + UINT n; + HWND w=GetDlgItem(wnd,IDC_MC6_DM); + for(n=0;n<sizeof(mc6_dm_names_ids)/sizeof(mc6_dm_names_ids[0]);n++) + { + SendMessageW(w,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(mc6_dm_names_ids[n])); + } + SendMessage(w,CB_SETCURSEL,cfg_mc6_dm,0); + + w=GetDlgItem(wnd,IDC_MC6_MAP); + for(n=0;n<sizeof(mc6_map_names_id)/sizeof(mc6_map_names_id[0]);n++) + { + SendMessageW(w,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(mc6_map_names_id[n])); + } + SendMessage(w,CB_SETCURSEL,cfg_mc6_map,0); + + SendDlgItemMessage(wnd,IDC_AVG_BR,BM_SETCHECK,cfg_abr,0); + + SetDlgItemInt(wnd,IDC_HTTP_BSIZE,cfg_http_bsize>>10,0); + if (cfg_fsave) SendDlgItemMessage(wnd,IDC_FSAVE,BM_SETCHECK,1,0); + if (cfg_fix0r) SendDlgItemMessage(wnd,IDC_FIX0R,BM_SETCHECK,1,0); + cfg_dumpdir.s_SetDlgItemText(wnd,IDC_STREAM_SAVE); + w=GetDlgItem(wnd,IDC_PROXY); + SendMessageW(w,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_NEVER)); + SendMessageW(w,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_PORT_80_ONLY)); + SendMessageW(w,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_ALWAYS)); + SendMessage(w,CB_SETCURSEL,cfg_proxy_mode,0); + + w=GetDlgItem(wnd,IDC_SLIDER1); + SendMessage(w,TBM_SETRANGE,0,MAKELONG(1,100)); + SendMessage(w,TBM_SETPOS,1,cfg_prebuf1); + w=GetDlgItem(wnd,IDC_SLIDER2); + SendMessage(w,TBM_SETRANGE,0,MAKELONG(1,100)); + SendMessage(w,TBM_SETPOS,1,cfg_prebuf2); + + cfg_ssave_format.s_SetDlgItemText(wnd,IDC_SSAVE_FMT); + SendMessage(wnd,WM_COMMAND,MAKEWPARAM(IDC_FSAVE,BN_CLICKED),(LPARAM)GetDlgItem(wnd,IDC_FSAVE)); + } + return 1; + + case WM_COMMAND: + switch(LOWORD(wp)) + { + case IDC_STREAM_SAVE: + d_browza(wnd,(HWND)lp,WASABI_API_LNGSTRINGW(IDS_SELECT_OUTPUT_DIRECTORY)); + break; + + case IDC_SSAVE_FMT_DEF: + SetDlgItemText(wnd,IDC_SSAVE_FMT,L"%filename%"); + break; + + case IDC_FSAVE: + { + int checked = IsDlgButtonChecked(wnd,IDC_FSAVE); + EnableWindow(GetDlgItem(wnd,IDC_STREAM_SAVE),checked); + EnableWindow(GetDlgItem(wnd,IDC_SSAVE_FMT),checked); + EnableWindow(GetDlgItem(wnd,IDC_SSAVE_FMT_DEF),checked); + } + break; + + case IDOK: + case IDCANCEL: + { + if (LOWORD(wp) == IDOK) + { + cfg_fullbuf=(int)SendDlgItemMessage(wnd,IDC_FULLBUF,BM_GETCHECK,0,0); + + cfg_mc6_dm=(int)SendDlgItemMessage(wnd,IDC_MC6_DM,CB_GETCURSEL,0,0); + cfg_mc6_map=(int)SendDlgItemMessage(wnd,IDC_MC6_MAP,CB_GETCURSEL,0,0); + + cfg_abr=(int)SendDlgItemMessage(wnd,IDC_AVG_BR,BM_GETCHECK,0,0); + + cfg_dumpdir.s_GetDlgItemText(wnd,IDC_STREAM_SAVE); + cfg_http_bsize=GetDlgItemInt(wnd,IDC_HTTP_BSIZE,0,0)<<10; + cfg_fsave=(int)SendDlgItemMessage(wnd,IDC_FSAVE,BM_GETCHECK,0,0); + cfg_fix0r=(int)SendDlgItemMessage(wnd,IDC_FIX0R,BM_GETCHECK,0,0); + cfg_proxy_mode=(int)SendDlgItemMessage(wnd,IDC_PROXY,CB_GETCURSEL,0,0); + cfg_prebuf1=(int)SendDlgItemMessage(wnd,IDC_SLIDER1,TBM_GETPOS,0,0); + cfg_prebuf2=(int)SendDlgItemMessage(wnd,IDC_SLIDER2,TBM_GETPOS,0,0); + cfg_ssave_format.s_GetDlgItemText(wnd,IDC_SSAVE_FMT); + } + do_cfg(1); + EndDialog(wnd,(LOWORD(wp) == IDOK)); + } + break; + } + break; + } + + const int controls[] = + { + IDC_SLIDER1, + IDC_SLIDER2, + }; + if (FALSE != WASABI_API_APP->DirectMouseWheel_ProcessDialogMessage(wnd, msg, wp, lp, controls, ARRAYSIZE(controls))) + { + return TRUE; + } + + return 0; +} + +extern HANDLE hThread;//hack + +void Config(HWND p) +{ + if (WASABI_API_DIALOGBOXPARAMW(IDD_CONFIG,p,CfgProc1,0)) + { + if (hThread) PostMessage(mod.hMainWindow,WM_USER,0,243); + } +} + +int CfgVar::read_int(const char *inifile, const char *section,const char * name,int def) +{ + return GetPrivateProfileIntA(section, name, def, inifile); +} + +void CfgVar::write_int(const char *inifile, const char *section, const char * name,int val) +{ + char temp[32] = {0}; + StringCchPrintfA(temp, 32, "%d", val); + WritePrivateProfileStringA(section, name, temp, inifile); +} + +void CfgVar::write_struct(const char *inifile, const char *section, const char * name, void * ptr,UINT size) +{ + WritePrivateProfileStructA("in_vorbis", name, ptr, size, INI_FILE); +} + +bool CfgVar::read_struct(const char *inifile, const char *section, const char * name,void * ptr,UINT size) +{ + return !!GetPrivateProfileStructA("in_vorbis", name, ptr, size, INI_FILE); +} + +void do_cfg(int s) +{ + #define CFG_VERSION 0x10204 + + if (!s) + { + if (CfgVar::read_int(INI_FILE, "in_vorbis", "version",0)==CFG_VERSION) + CfgVar::ReadConfig(); + } + else + { + CfgVar::WriteConfig(); + CfgVar::write_int(INI_FILE, "in_vorbis", "version",CFG_VERSION); + } +} + +CfgVar * CfgVar::list=0; + +void CfgVar::ReadConfig() +{ + CfgVar * p=list; + while(p) + { + p->Read(p->name); + p=p->next; + } +} + +void CfgVar::WriteConfig() +{ + CfgVar * p=list; + while(p) + { + p->Write(p->name); + p=p->next; + } +} + +bool StringW::reg_read(const char * name) +{ + char utf8_data[2048] = {0}; + wchar_t utf16_data[2048] = {0}; + GetPrivateProfileStringA("in_vorbis", name, "@default@", utf8_data, 2048, INI_FILE); + if (!strcmp("@default@", utf8_data)) + return false; + + MultiByteToWideCharSZ(CP_UTF8, 0, utf8_data, -1, utf16_data, 2048); + SetString(utf16_data); + + return true; +} + +void StringW::reg_write(const char * name) +{ + WritePrivateProfileStringA("in_vorbis", name, AutoChar((const WCHAR *)*this, CP_UTF8), INI_FILE); +} + +void CfgString::Read(const char * name) +{ + reg_read(name); +} + +void CfgString::Write(const char * name) +{ + StringW temp; + if (temp.reg_read(name)) + { + if (wcscmp(temp,*this)) reg_write(name); + } + else + { + if (wcscmp(def,*this)) reg_write(name); + } +} + +void CfgInt::Write(const char * name) +{ + if (read_int(INI_FILE, "in_vorbis", name,def)!=value) write_int(INI_FILE, "in_vorbis",name,value); +} + +void CfgInt::Read(const char * name) +{ + value=read_int(INI_FILE, "in_vorbis", name,def); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/core_api.h b/Src/Plugins/Input/in_vorbis/core_api.h new file mode 100644 index 00000000..07d48b5b --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/core_api.h @@ -0,0 +1,567 @@ + +/* Winamp 3 Player core api v0.1 +** (c)2000 nullsoft jcf/ct/dk +** Notes: +** Keep in mind that this header file is subject to change prior to the +** release of Winamp 3. The ability to configure plug-ins has yet to be +** added and is the first and foremost concern of the engineering team. +*/ + +#ifndef __CORE_API_H +#define __CORE_API_H +/* +// Visual C 6 makes big unaligned dlls. the following will correct it +#ifndef _DEBUG +// release optimizations +// /Og (global optimizations), /Os (favor small code), /Oy (no frame pointers) +#pragma optimize("gsy",on) +#pragma comment(linker,"/RELEASE") +// set the 512-byte alignment +#pragma comment(linker,"/opt:nowin98") +#endif +*/ +// Use Assert in your code to catch errors that shouldn't happen, when compiled in release mode, they are #defined out +#ifndef ASSERT +#ifdef _DEBUG +#define ASSERT(x) if (!(x)) MessageBox(NULL,"ASSERT FAILED: " #x,"ASSERT FAILED in " __FILE__ ,MB_OK|MB_ICONSTOP); +#else +#define ASSERT(x) +#endif +#endif + + + +/* CLASS DESCRIPTIONS */ + +/* WReader +** File reader module class, ie. opens and reads files, streams +*/ +class WReader; + +/* WInputInfo +** Class that returns information (length, title, metadata) about a specified file +*/ +class WInputInfo; + +/* WInfo_callback +** Player's interface that provides Winamp 3 core functions to your WInputInfo classes +*/ +class WInfo_callback; + +/* WInputSource +** Input Source manager base class, ie. decodes mp3's, wavs +*/ +class WInputSource; + +/* WOutputFilter +** Abstract base class for any Output Filter plug-in, ie. changes pitch, converts format, outputs to speakers +*/ +class WOutputFilter; + +/* WPlayer_callback +** Player's interface that provides Winamp 3 core functions to your Input Sources and Output Filter plug-ins +** (Getting a reader for opening a file, sending stuff about what's going on to the Winamp 3 core) +*/ +class WPlayer_callback; + + + + +class WPlayer_callback +{ + public: + /* GetReader + ** Allows your Input Source and Output Filter plugins to request a reader from Winamp, + ** so you don't have to worry about opening files or streams + */ + virtual WReader *GetReader(char *url)=0; + + + /* The 3 following functions allows your Input Source and Output Filter plugins to send error/warning/status + ** messages back to the Winamp 3 core + */ + + /* Error + ** playback should stop (soundcard driver error, etc) + */ + virtual void Error(char *reason)=0; + + /* Warning + ** warning (something recoverable, like file not found, etc) + */ + virtual void Warning(char *warning)=0; + + /* Status + ** status update (nothing really wrong) + */ + virtual void Status(char *status)=0; + + + + + /* TitleChange + ** should be called if the current file titlename changes during the decoding + */ + virtual void TitleChange(char *new_title)=0; + + /* InfoChange + ** should be called if the infos about the current file changes during the decoding + */ + virtual void InfoChange(char *new_info_str, int new_length)=0; + + /* UrlChange + ** should be called if the current file associated URL changes during the decoding + */ + virtual void UrlChange(char *new_url)=0; +}; + + + + +class WInfo_callback +{ + public: + /* GetReader + ** Allows your WInfo classes to request a reader from Winamp + ** so you don't have to worry about opening files or streams + */ + virtual WReader *GetReader(char *url)=0; +}; + + + + +class WInputInfo +{ + public: + /* WInputInfo + ** WInputInfo constructor + */ + WInputInfo(){ }; + + /* m_info + ** Filled by Winamp. Pointer to WInputInfo callback function + */ + WInfo_callback *m_info; + + /* Open + ** Called by Winamp to request informations about a specified media (file, url, etc...) + ** returns 0 if succesful, 1 if not + ** + ** You must open, get all information and close the specified file here and store + ** the useful information into member elements for quick access by other functions + */ + virtual int Open(char *url) { return 1; } + + /* GetTitle + ** Called by Winamp to get the decoded title about the file opened + ** i.e. id3 title name, etc... + */ + virtual void GetTitle(char *buf, int maxlen) { if (maxlen>0) buf[0]=0; }; + + /* GetInfoString + ** Called by Winamp to get extra informations about the file opened + ** i.e. "160kbps stereo 44Khz" for MP3 files,"4 channels" for MOD files,etc... + */ + virtual void GetInfoString(char *buf, int maxlen) { if (maxlen>0) buf[0]=0; }; + + /* GetLength + ** Called by Winamp to retrieves media type length in milliseconds + ** returns -1 if length is undefined/infinite + */ + virtual int GetLength(void) { return -1; }; + + /* GetMetaData + ** Fetches metadata by attribute name (Artist, Album, Bitrate, etc...) + ** attribute names are non case-sensitive. + ** returns size of data + */ + virtual int GetMetaData(char *name, char *data, int data_len) { if (data&&data_len>0) *data=0; return 0; } + + /* ~WInputInfo + ** WInputInfo virtual destructor + */ + //virtual ~WInputInfo() { }; + virtual void Release(int)=0; +}; + + + + + + + + +/* WINAMP Output Filter NOTIFY MESSAGES +** Messages returned to notify Output Filter plug-ins of events +*/ + +typedef enum { + + /* WOFNM_FILETITLECHANGE + ** Sent when the song changes + ** param1=new filename song + ** param2=new title song + */ + WOFNM_FILETITLECHANGE=1024, + + /* WOFNM_ENDOFDECODE + ** Sent when decoding ends + */ + WOFNM_ENDOFDECODE, + +} WOutputFilterNotifyMsg; + + + + +class WOutputFilter +{ + protected: + /* WOutputFilter + ** WOutputFilter constructor + */ + WOutputFilter() { m_next=NULL; }; + + public: + /* m_player + ** Filled by Winamp. Pointer to Winamp 3 core player interface + */ + WPlayer_callback *m_player; + + /* m_next + ** Internally used by Winamp. Pointer to next activated Output Filter + */ + WOutputFilter *m_next; + + /* ~WOutputFilter + ** WOutputFilter destructor + */ + //virtual ~WOutputFilter() { }; + virtual void Release(int)=0; + + /* GetDescription + ** Retrieves your plug-in's text description + */ + virtual char *GetDescription() { return "Unknown"; }; + + /* ProcessSamples + ** Render data as it receives it + ** sampledata: Data to process + ** bytes: number of bytes to process + ** bps: Bits per sample (8 or 16) + ** nch: Number of channels (1 or 2) + ** srate: Sample rate in Hz + ** killswitch: Will be set to 1 by winamp if stop if requested. Poll the pointed value very often to + ** make sure Winamp doesn't hang + ** + ** Returns the number of processed bytes or -1 if unable to open the device or an error occured. + ** + ** You have to open your device (ie. Directsound) the first time this function is called. + */ + virtual int ProcessSamples(char *sampledata, int bytes, int *bps, int *nch, int *srate, bool *killswitch) { return bytes; } + + /* FlushSamples + ** Flushes output buffers so that all is written + */ + virtual void FlushSamples(bool *killswitch) { }; + + /* Restart + ** Called by Winamp after a seek + */ + virtual void Restart(void) { } + + /* GetLatency + ** Returns < 0 for a final output latency, > 0 for an additive + */ + virtual int GetLatency(void) { return 0; } + + /* Pause + ** Suspends output + */ + virtual void Pause(int pause) { } + + /* ShutDown + ** Completely stops output + ** + ** Close your device here (not in destructor) + */ + virtual void ShutDown(void) { } + + /* SetVolume + ** Sets the volume (0 to 255) + ** return 1 if volume successfully modified + */ + virtual int SetVolume(int volume) { return 0; } + + /* SetPan + ** Sets Left-Right sound balance (-127 to 127) + ** return 1 if pan successfully modified + */ + virtual int SetPan(int pan) { return 0; } + + /* Notify + ** Called by Winamp to notify what's going on + */ + virtual void Notify(WOutputFilterNotifyMsg msg, int data1, int data2) { } + +}; + + + + +class WInputSource +{ + + protected: + /* WInputSource + ** WInputSource constructor + */ + WInputSource(){ }; + + + public: + /* m_player + ** Filled by Winamp. Pointer to Winamp 3 core interface + */ + WPlayer_callback *m_player; + + /* GetDescription + ** Retrieves your plug-in's text description + */ + virtual char *GetDescription() { return "Unknown"; }; + + /* UsesOutputFilters + ** Returns whether or not the Output Filter pipeline can be used + */ + virtual int UsesOutputFilters(void) { return 1; } + + /* Open + ** Used to open and prepare input media type + */ + virtual int Open(char *url, bool *killswitch)=0; + + /* GetSamples + ** This function must fill bps, nch and srate. + ** Here, you have to fill the sample_buffer with decoded data. Be sure to fill it with the specified + ** size (bytes). Use an internal buffer, etc ... + ** + ** sample_buffer: buffer to put decoded data into + ** bytes: size of the sample_buffer + ** bps: Bits par sample (8 or 16) + ** nch: Number of channels (1 or 2) + ** srate: Sample rate in Hz + ** killswitch: Will be set to 1 by winamp if stop if requested. Poll the pointed value very often to + ** make sure Winamp doesn't hang + */ + virtual int GetSamples(char *sample_buffer, int bytes, int *bps, int *nch, int *srate, bool *killswitch)=0; + + /* SetVolume + ** Sets the volume (0 to 255) + ** Return 1 if volume has been set + */ + virtual int SetVolume(int volume) { return 0; }; + + /* SetPan + ** Sets Left-Right sound balance (-127 to 127) + ** return 1 if pan successfully modified + */ + virtual int SetPan(int pan) { return 0; }; + + /* SetPosition + ** Sets position in ms. returns 0 on success, 1 if seek impossible + */ + virtual int SetPosition(int)=0; + + /* Pause + ** Suspends input + */ + virtual void Pause(int pause) { }; + + /* GetPosition + ** Retrieve position in milliseconds + */ + virtual int GetPosition(void) { return 0; } + + /* GetTitle + ** Called by Winamp to get the decoded title about the file opened + ** i.e. stream name, id3 title name, etc... + */ + virtual void GetTitle(char *buf, int maxlen) { if(maxlen>0) buf[0]=0; }; + + /* GetInfoString + ** Called by Winamp to get extra informations about the file openend + ** i.e. "32kbps 44khz", etc... + */ + virtual void GetInfoString(char *buf, int maxlen) { if(maxlen>0) buf[0]=0; }; + + /* GetLength + ** Called by Winamp to retrieves media type length in milliseconds + ** returns -1 if length is undefined/infinite + */ + virtual int GetLength(void) { return -1; } + + /* ~WInputSource + ** ~WInputSource virtual destructor + */ + //virtual ~WInputSource() { }; + virtual void Release(int)=0; +}; + + + + +class WReader +{ + protected: + + /* WReader + ** WReader constructor + */ + WReader() { } + + public: + + /* m_player + ** Filled by Winamp. Pointer to Winamp 3 core interface + */ + WPlayer_callback *m_player; + + /* GetDescription + ** Retrieves your plug-in's text description + */ + virtual char *GetDescription() { return "Unknown"; }; + + /* Open + ** Used to open a file, return 0 on success + */ + virtual int Open(char *url, bool *killswitch)=0; + + /* Read + ** Returns number of BYTES read (if < length then eof or killswitch) + */ + virtual int Read(char *buffer, int length, bool *killswitch)=0; + + /* GetLength + ** Returns length of the entire file in BYTES, return -1 on unknown/infinite (as for a stream) + */ + virtual int GetLength(void)=0; + + /* CanSeek + ** Returns 1 if you can skip ahead in the file, 0 if not + */ + virtual int CanSeek(void)=0; + + /* Seek + ** Jump to a certain absolute position + */ + virtual int Seek(int position, bool *killswitch)=0; + + /* GetHeader + ** Retrieve header. Used in read_http to retrieve the HTTP header + */ + virtual char *GetHeader(char *name) { return 0; } + + /* ~WReader + ** WReader virtual destructor + */ + //virtual ~WReader() { } + virtual void Release(int)=0; +}; + + + + +/* DLL PLUGINS EXPORT STRUCTURES */ + +#define READ_VER 0x100 +#define IN_VER 0x100 +#define OF_VER 0x100 + + +typedef struct +{ + /* version + ** Version revision number + */ + int version; + + /* description + ** Text description of the reader plug-in + */ + char *description; + + /* create + ** Function pointer to create a reader module + */ + WReader *(*create)(); + + /* ismine + ** Determines whether or not a file should be read by this plug-in + */ + int (*ismine)(char *url); + +} reader_source; + + + + +typedef struct +{ + /* version + ** Version revision number + */ + int version; + + /* description + ** Text description of the input plug-in + */ + char *description; + + /* extension_list + ** Defines all the supported filetypes by this input plug-in + ** In semicolon delimited format ("ext;desc;ext;desc" etc). + */ + char *extension_list; + + /* ismine + ** called before extension checks, to allow detection of tone://,http://, etc + ** Determines whether or not a file type should be decoded by this plug-in + */ + int (*ismine)(char *filename); + + /* create + ** Function pointer to create a decoder module + */ + WInputSource *(*create)(void); + + /* createinfo + ** Function pointer to create a decoder module information + */ + WInputInfo *(*createinfo)(void); + +} input_source; + + + +typedef struct +{ + /* version + ** Version revision number + */ + int version; + + /* description + ** Text description of the output plug-in + */ + char *description; + + /* create + ** Function pointer to create an Output Filter + */ + WOutputFilter *(*create)(); + +} output_filter; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/decoder.cpp b/Src/Plugins/Input/in_vorbis/decoder.cpp new file mode 100644 index 00000000..54cd364e --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/decoder.cpp @@ -0,0 +1,459 @@ +#include "main.h" +#include "decoder.h" +#include <math.h> +#include <locale.h> +#pragma warning(disable:4244) +#include "shaper.h" +#include "api__in_vorbis.h" + +Decoder::~Decoder() {if (shaper) delete shaper;} + +extern CfgInt + cfg_mc6_dm, cfg_mc6_map; +/* +if (vorbis_cfg.use_hq_preamp) +{ + sample *= pow(10., preamp_db/20); + + //hard 6dB limiting + if (sample < -0.5) + sample = tanh((sample + 0.5) / (1-0.5)) * (1-0.5) - 0.5; + else if (sample > 0.5) + sample = tanh((sample - 0.5) / (1-0.5)) * (1-0.5) + 0.5; +} */ + +#if 0 +static float q_tanh(float x) +{ + double foo1, foo2; + foo1 = pow(2.71828182845904523536028747135266, x); + foo2 = 1.0 / foo1; + return (foo1 -foo2) / (foo1 + foo2); +} +#else +#define q_tanh tanh +#endif + +float VorbisFile::GetGain() +{ + float peak; + + vorbis_comment * c; + float scale = 1.0f; + c = ov_comment(&vf, -1); + peak = 0.99f; + if (c) + { + if (AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false)) + { + char * _peak = 0, *_gain = 0; + float gain = 0; + bool have_rg = 0; + float lwing_gain = 0; + char *gain1 = 0, *gain2 = 0, *peak1 = 0, *peak2 = 0; + gain1 = vorbis_comment_query(c, "replaygain_album_gain", 0); + if (!gain1) gain1 = vorbis_comment_query(c, "rg_audiophile", 0); + gain2 = vorbis_comment_query(c, "replaygain_track_gain", 0); + if (!gain2) gain2 = vorbis_comment_query(c, "rg_radio", 0); + + peak1 = vorbis_comment_query(c, "replaygain_album_peak", 0); + peak2 = vorbis_comment_query(c, "replaygain_track_peak", 0); + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_source", 0)) + { + case 0: // track + _gain = gain2; + if (!_gain && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + _gain = gain1; + _peak = peak2; + if (!_peak && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + _peak = peak1; + break; + case 1: // album + _gain = gain1; + if (!_gain && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + _gain = gain2; + _peak = peak1; + if (!_peak && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + _peak = peak2; + break; + } + + if (!_peak) + { + _peak = vorbis_comment_query(c, "rg_peak", 0); + } + + _locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale(); + + if (_peak) peak = _atof_l(_peak, C_locale); + if (_gain) gain = _atof_l(_gain, C_locale); + + if (!_peak && !_gain) + { + char * l = vorbis_comment_query(c, "lwing_gain", 0); + if (l) + { + lwing_gain = _atof_l(l, C_locale); + have_rg = 1; + } + } + else have_rg = 1; + + if (!have_rg) + { + gain = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0f); + } + + scale = powf(10, (gain) / 20.0f); + if (lwing_gain) + scale *= lwing_gain; + else if (have_rg) + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_mode", 1)) + { + case 1: // apply gain, but don't clip + if (scale*peak > 1.0) scale = 1.0 / peak; + break; + case 2: // normalize + scale = 1.0 / peak; + break; + case 3: // no clipping + if (peak > 1.0f) + scale = 1.0 / peak; + break; + } + } + } + + + return scale; +} + +void Decoder::process_rg() +{ + scale = file->GetGain(); +} + +void Decoder::setup_mc() +{ + if (AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"mono", false)) + nch = 1; + else if (src_nch == 6) + { + switch (cfg_mc6_dm) + { + case 2: + nch = 4; + break; + case 3: + case 4: + nch = 2; + break; + case 5: + nch = 1; + break; + } + + if (nch > 2 && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"surround", true)) + nch = 2; + } +} + +void Decoder::Flush() +{ + bptr = 0; + pcmbuf = 0; + data = 0; + pos = 0; + if (shaper) {delete shaper;shaper = 0;} +} + +void Decoder::Init(VorbisFile * f, UINT _bits, UINT _nch, bool _useFloat, bool allowRG) +{ + useFloat = _useFloat; + + file = f; + vorbis_info * i = ov_info(&file->vf, -1); + + if (allowRG) + process_rg(); + else + scale = 1.0f; + + if (useFloat) + { + dither = false; + bps = 32; + } + else + { + dither = AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"dither", true); + + if (_bits) + bps = _bits; + else + bps = AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"bits", 16); + } + + if (useFloat) + { + clipmin = -10000; // some arbitrarily large number + clipmax = 10000; // some arbitrarily large number + } + else + { + clipmin = - (1 << (bps - 1)); + clipmax = (1 << (bps - 1)) - 1; + } + sr = i->rate; + nch = src_nch = i->channels; + Flush(); + cur_link = file->vf.current_link; + + if (_nch) + nch = _nch; + else + setup_mc(); +} + +UINT Decoder::DataAvailable() +{ + return data * (bps >> 3); +} + +int Decoder::DoFrame() +{ + need_reopen = 0; + while (1) + { + data = ov_read_float(&file->vf, &pcmbuf, 576, 0); + if ((int)data <= 0) + { + if (data == OV_HOLE) {continue;} + data = 0; + return 0; + } + break; + } + + pos = 0; + if (cur_link != file->vf.current_link) + { + vorbis_info* i = ov_info(&file->vf, -1); + if (sr != (UINT)i->rate || src_nch != (UINT)i->channels) + { + UINT old_nch = nch, old_sr = sr; + if (shaper) {delete shaper;shaper = 0;} + sr = i->rate; + src_nch = nch = i->channels; + setup_mc(); + if (nch != old_nch || sr != old_sr) + { + need_reopen = 1; + } + } + process_rg(); + cur_link = file->vf.current_link; + } + data *= nch; + return 1; +} + +int Decoder::Read(UINT bytes, void * buf) +{ + UINT wr = 0; + if (buf && bytes && data > 0) + { + char* out = (char*)buf; + UINT d; + double mul; + int ofs; + float * img; + + d = bytes / (bps >> 3); + if (d > data) d = data; + if (!d) return 0; + data -= d; + if (useFloat) + { + mul = 1.0; + ofs = 0; + } + else + { + mul = (double)( (1 << ((bps) - 1)) - 1 ); + ofs = (bps == 8) ? 0x80 : 0; + } + wr += d * (bps >> 3); + + img = (float*)alloca(sizeof(float) * nch); + do + { + UINT cur_ch; + if (nch == 1 && src_nch > 0) + { + UINT c; + img[0] = 0; + for (c = 0;c < src_nch;c++) + { + img[0] += pcmbuf[c][pos]; + } + img[0] /= (float)src_nch; + } + else if (nch == src_nch && !(nch == 6 && cfg_mc6_dm == 1)) + { + UINT c; + for (c = 0;c < nch;c++) + { + img[c] = pcmbuf[c][pos]; + } + } + else if (src_nch == 6) + { + UINT FL, FR, C; + if (cfg_mc6_map == 1) + { + FL = 0; + FR = 1; + C = 2; + } + else + { + FL = 0; + C = 1; + FR = 2; + } + + if (nch == 6) + { //remap order for correct 5.1 output + img[0] = pcmbuf[FL][pos]; + img[1] = pcmbuf[FR][pos]; + img[2] = pcmbuf[C][pos]; + img[3] = pcmbuf[5][pos]; + img[4] = pcmbuf[3][pos]; + img[5] = pcmbuf[4][pos]; + } + else if (nch == 2) + { + /* + FL FR C BL BR LFE + 0 1 2 3 4 5 + + L,C,R,SL,SR,LFE + 0 1 2 3 4 5 + + + output: + FL FR C LFE BL BR + + + stereo: + Lt=L+0.707*(V-SL-SR+LFE) + Rt=R+0.707*(C+SL+SR+LFE) + + + Lt=L+0.707*(C+LFE) + Rt=R+0.707*(C+LFE) + SLt=SL + SRt=SR + + */ + if (cfg_mc6_dm == 4) //ds2 + { + const double a = pow(10., 1.5 / 20.), b = 1 / a; + img[0] = pcmbuf[FL][pos] + 0.707 * (pcmbuf[C][pos] - a * pcmbuf[3][pos] - b * pcmbuf[4][pos] + pcmbuf[5][pos]); + img[1] = pcmbuf[FR][pos] + 0.707 * (pcmbuf[C][pos] + b * pcmbuf[3][pos] + a * pcmbuf[4][pos] + pcmbuf[5][pos]); + } + else + { + img[0] = pcmbuf[FL][pos] + 0.707 * (pcmbuf[C][pos] - pcmbuf[3][pos] - pcmbuf[4][pos] + pcmbuf[5][pos]); + img[1] = pcmbuf[FR][pos] + 0.707 * (pcmbuf[C][pos] + pcmbuf[3][pos] + pcmbuf[4][pos] + pcmbuf[5][pos]); + } + } + else if (nch == 4) + { + img[0] = pcmbuf[FL][pos] + 0.707 * (pcmbuf[C][pos] + pcmbuf[5][pos]); + img[1] = pcmbuf[FR][pos] + 0.707 * (pcmbuf[C][pos] + pcmbuf[5][pos]); + img[2] = pcmbuf[3][pos]; + img[3] = pcmbuf[4][pos]; + } + } + + for (cur_ch = 0;cur_ch < nch;cur_ch++) + { + float v = img[cur_ch]; + int val; + v *= scale; + v *= mul; + if (dither) + { + if (!shaper) + { + //Shaper(int freq,int _nch,int min,int max,int _dtype,int pdf,double noiseamp); + shaper = new Shaper(sr, nch, clipmin, clipmax, 2, DITHER_TRIANGLE, 0); + } + // double peak=0; + val = shaper->do_shaping(v /*,&peak*/, cur_ch); + //shaper clips for us + } + else + { + val = (int)v; + if (val < clipmin) val = clipmin; + else if (val > clipmax) val = clipmax; + //1<<16 = 0x10000 + + } + val += ofs; + + switch (bps) + { + case 8: + *(BYTE*)out = (UINT)val; + break; + case 16: + *(short*)out = val; + break; + case 24: + { + ((BYTE*)out)[0] = (UINT)val; + ((BYTE*)out)[1] = (UINT)val >> 8; + ((BYTE*)out)[2] = (UINT)val >> 16; + } + break; + case 32: + if (useFloat) + { + *(float *)out = v; + } + else + { + //*(long*)out=val; + //break; + *(long*)out = 0; + } + break; + }; + out += (bps >> 3); + d--; + } + pos++; + } + while (d); + + } + return wr; +} + +int VorbisFile::Seek(double p) { return ov_time_seek(&vf, p);} + +int Decoder::Seek(double p) +{ + Flush(); + return file->Seek(p); +} + +//char *vorbis_comment_query(vorbis_comment *vc, char *tag, int count) +const char* VorbisFile::get_meta(const char* tag, UINT c) +{ + return vorbis_comment_query(vf.seekable ? vf.vc + vf.current_link : vf.vc, (char*)tag, c); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/decoder.h b/Src/Plugins/Input/in_vorbis/decoder.h new file mode 100644 index 00000000..aa2b8c01 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/decoder.h @@ -0,0 +1,41 @@ +class Shaper; + +class Decoder +{ +private: + void process_rg(); + void setup_mc(); + float* bptr; + float** pcmbuf; + Shaper * shaper; + UINT data,pos; + float scale; + int cur_link; + int cur_preamp; + int clipmin,clipmax; +public: + VorbisFile * file; + + UINT nch,sr,kbps,bps,src_nch; + + Decoder() + { + memset(this,0,sizeof(*this)); + } + + ~Decoder(); + + int Seek(double p); + int Read(UINT bytes,void * buf); + void Flush(); + void Init(VorbisFile * f, UINT _bits=0, UINT _nch=0, bool _useFloat=false, bool allowRG=true); + void wa2_setinfo(UINT cur_bitrate); + + UINT DataAvailable(); + int DoFrame(); + bool need_reopen; + int play_init(); + bool play_inited; + bool dither; + bool useFloat; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/genres.c b/Src/Plugins/Input/in_vorbis/genres.c new file mode 100644 index 00000000..3f71c504 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/genres.c @@ -0,0 +1,97 @@ +#include <windows.h> +#include "genres.h" +#include <shlwapi.h> + +extern const wchar_t *INI_DIRECTORY; + +static void file_init(wchar_t *file_path, wchar_t *fn) +{ + PathCombineW(file_path, INI_DIRECTORY, fn); +} + +static char eol[2]={13,10}; + +static char get_char(HANDLE f,BOOL * eof) +{ + DWORD br=0; + char r=0; + ReadFile(f,&r,1,&br,0); + if (!br) *eof=1; + return r; +} + +void genres_read(HWND wnd, wchar_t* fn) +{ + char temp[MAX_GENRE] = {0}; + char add[MAX_GENRE] = {0}; + BOOL eof=0; + char c = 0; + wchar_t file_path[MAX_PATH] = {0}; + HANDLE f; + + file_init(file_path, fn); + + f = CreateFileW(file_path, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); + if (f==INVALID_HANDLE_VALUE) return; + GetWindowTextA(wnd,add,MAX_GENRE); + while(!eof) + { + UINT ptr=0; + BOOL start=1; + while(ptr<MAX_GENRE-1) + { + c=get_char(f,&eof); + if (eof) break; + if (c==10 || c==13) + { + if (start) continue; + else break; + } + start=0; + temp[ptr++]=c; + } + if (ptr) + { + temp[ptr]=0; + SendMessage(wnd,CB_ADDSTRING,0, (LPARAM)temp); + if (add[0]) + { + if (!_stricmp(add,temp)) add[0]=0; + } + } + } + CloseHandle(f); + if (add[0]) SendMessage(wnd,CB_ADDSTRING,0,(LPARAM)add); +} + +void genres_write(HWND wnd, wchar_t* fn) +{ + wchar_t file_path[MAX_PATH] = {0}; + char temp[MAX_GENRE] = {0}; + UINT max = 0,n = 0; + DWORD bw = 0; + HANDLE f; + { + char add[MAX_GENRE] = {0}; + GetWindowTextA(wnd,add,MAX_GENRE); + if (!add[0]) return; + max=(UINT)SendMessage(wnd,CB_GETCOUNT,0,0); + for(n=0;n<max;n++) + { + SendMessage(wnd,CB_GETLBTEXT,n,(LPARAM)temp); + if (!_stricmp(temp,add)) return; + } + SendMessage(wnd,CB_ADDSTRING,0,(LPARAM)add); + } + file_init(file_path, fn); + f = CreateFileW(file_path, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); + if (f==INVALID_HANDLE_VALUE) return; + max=(UINT)SendMessage(wnd,CB_GETCOUNT,0,0); + for(n=0;n<max;n++) + { + SendMessage(wnd,CB_GETLBTEXT,n,(LPARAM)temp); + bw = 0; WriteFile(f,temp,(DWORD)strlen(temp),&bw,0); + bw = 0; WriteFile(f,eol,2,&bw,0); + } + CloseHandle(f); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/genres.h b/Src/Plugins/Input/in_vorbis/genres.h new file mode 100644 index 00000000..ad840701 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/genres.h @@ -0,0 +1,16 @@ +#ifndef NULLSOFT_IN_VORBIS_GENRES_H +#define NULLSOFT_IN_VORBIS_GENRES_H + +#ifdef __cplusplus +extern "C" { +#endif + +void genres_read(HWND wnd, wchar_t* fn); +void genres_write(HWND wnd, wchar_t* fn); +#define MAX_GENRE 256 + +#ifdef __cplusplus +} +#endif + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/http.cpp b/Src/Plugins/Input/in_vorbis/http.cpp new file mode 100644 index 00000000..87998084 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/http.cpp @@ -0,0 +1,596 @@ +#include "api__in_vorbis.h" +#include "../../..\Components\wac_network\wac_network_http_receiver_api.h" +#include "rf.h" +#include "main.h" +#include "../Winamp/wa_ipc.h" +#include <api/service/waservicefactory.h> +#include "../nu/AutoWide.h" +#include "../nu/AutoChar.h" +extern CfgInt cfg_fix0r,cfg_httpseek2,cfg_proxy_mode,cfg_prebuf1,cfg_prebuf2,cfg_fsave,cfg_http_bsize; + +#define CANSEEK + +WORD *wdup(const char * src);//info.c + +#define zeromem(x) memset(&x,0,sizeof(x)) + +class StreamSave +{ + private: + ogg_sync_state oy_src; + ogg_stream_state os_src; + ogg_stream_state os_dst; + ogg_page og_src; + ogg_page og_dst; + ogg_packet op; + StringW tmp_fn; + BOOL is_temp; + BOOL got_streams,got_delta,use_fix0r; + ogg_int64_t pcm_delta; + int packets,serial; + HANDLE hFile; + public: + + StreamSave() + { + zeromem(oy_src); + zeromem(os_src); + zeromem(os_dst); + zeromem(og_src); + zeromem(og_dst); + zeromem(op); + got_streams=0; + got_delta=0; + pcm_delta=0; + hFile=0; + packets=0; + serial=0; + is_temp=1; + + tmp_fn=cfg_dumpdir; + if (tmp_fn[tmp_fn.Length()-1]!='\\') tmp_fn.AddChar('\\'); + + tmp_fn+=StringPrintfW(L"oggtemp%u.foo",GetTickCount64()&0xFFFF); + + hFile=CreateFileW(tmp_fn,GENERIC_WRITE,0,0,CREATE_ALWAYS,FILE_ATTRIBUTE_HIDDEN,0); + if (hFile==INVALID_HANDLE_VALUE) hFile=0; + else + { + ogg_sync_init(&oy_src); + use_fix0r=cfg_fix0r; + } + }; + + void Write(void * ptr,UINT siz) + { + if (!hFile) return; + + void * b=ogg_sync_buffer(&oy_src,siz); + memcpy(b,ptr,siz); + ogg_sync_wrote(&oy_src,siz); + + while(ogg_sync_pageout(&oy_src,&og_src)>0) + { + if (!got_streams) + { + serial=ogg_page_serialno(&og_src); + ogg_stream_init(&os_src,serial); + ogg_stream_init(&os_dst,serial); + got_streams=1; + packets=0; + got_delta=0; + } + else if (serial!=ogg_page_serialno(&og_src)) + { + if (got_streams) + { + /*while(ogg_stream_flush(&os_dst,&og_dst)) + { + write_page(dst,&og_dst,&wb); + }*/ + + ogg_stream_clear(&os_src); + ogg_stream_clear(&os_dst); + } + serial=ogg_page_serialno(&og_src); + ogg_stream_init(&os_src,serial); + ogg_stream_init(&os_dst,serial); + + packets=0; + got_delta=0; + } + + ogg_stream_pagein(&os_src,&og_src); + while(ogg_stream_packetout(&os_src,&op)>0) + { + if (use_fix0r && !got_delta && packets>2 && op.granulepos>=0) //hack to fix saved streams + { + got_delta=1; + if (op.granulepos>4096*(packets-2)) pcm_delta=op.granulepos; + } + if (got_delta) + { + if (op.granulepos>=pcm_delta) op.granulepos-=pcm_delta; + else if (op.granulepos>0) op.granulepos=0; + } + ogg_stream_packetin(&os_dst,&op); + packets++; + } + + while((packets==3 ? ogg_stream_flush(&os_dst,&og_dst) : ogg_stream_pageout(&os_dst,&og_dst))>0) + { + DWORD bw = 0; + WriteFile(hFile,og_dst.header,og_dst.header_len,&bw,0); + bw = 0; WriteFile(hFile,og_dst.body,og_dst.body_len,&bw,0); + } + } + } + + void FixName(VorbisFile * vf,const char * streamname) + { + if (!hFile) return; + CloseHandle(hFile); + StringW fn(cfg_dumpdir); + + if (fn[fn.Length()-1]!='\\') fn.AddChar('\\'); + + UINT n=fn.Length(); + fn+=(wchar_t *)AutoWide(vf->get_meta("TITLE", 0), CP_UTF8); + UINT m=fn.Length(); + + while(n<m) + { + char * b="/\\:*?\"<>|"; + while(b && *b) + { + if (fn[n]==*b) {fn.SetChar(n,'_');break;} + b++; + } + n++; + }; + fn.AddStringA(".ogg"); + if (!MoveFileW(tmp_fn,fn)) + { + DeleteFileW(fn); + MoveFileW(tmp_fn,fn); + } + SetFileAttributesW(fn,FILE_ATTRIBUTE_NORMAL); + hFile=CreateFileW(fn,GENERIC_WRITE,0,0,OPEN_EXISTING,0,0); + + if (hFile==INVALID_HANDLE_VALUE) {hFile=0;} + else SetFilePointer(hFile,0,0,FILE_END); + is_temp=0; + } + + ~StreamSave() + { + if (hFile) + { + /*if (got_streams) + { + while(ogg_stream_flush(&os_dst,&og_dst)) + { + write_page(dst,&og_dst,&wb); + } + }*/ + + ogg_stream_clear(&os_src); + ogg_stream_clear(&os_dst); + + SetFilePointer(hFile,0,0,FILE_CURRENT); + CloseHandle(hFile); + if (is_temp) DeleteFileW(tmp_fn); + } + ogg_sync_clear(&oy_src); + } +}; + +static const char * do_proxy(const char * url) +{ + switch(cfg_proxy_mode) + { + default: + return 0; + case 1: + { + const char * p=strstr(url,"://"); + if (!p) p=url; + while(p && *p && *p!=':' && *p!='/') p++; + if (p && *p==':') + { + if (atoi(p+1)!=80) return 0; + } + } + case 2: + { + char *x = (char *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GET_PROXY_STRING); + if (x == (char *)1 || !x || !*x) + return 0; + return x; + } + } +} + +class VorbisFile_HTTP : public VorbisFile +{ + protected: + api_httpreceiver *get; + UINT bsize; + uint64_t len; + UINT pos; + UINT seekpos; + BOOL can_seek; + StreamSave * saver; + virtual void Idle(); + virtual int f_seek(__int64 offset,int whence); + virtual size_t f_read(UINT siz,void * ptr); + virtual UINT f_tell(); + virtual UINT FileSize() {return len;} + bool is_live; + + public: + virtual int GetType() {return TYPE_HTTP;} + virtual bool IsLive() {return is_live;} + bool http_init(); + + void do_prebuf() {VorbisFile::do_prebuf();fillbuf(bsize * cfg_prebuf1 / 100,0);} + + VorbisFile_HTTP(UINT s, const wchar_t *u,bool is_info, bool hasauth) : VorbisFile(u,is_info), usedauth(hasauth) + { + get=0; + can_seek=0; + len=pos=seekpos=0; + bsize=s; + saver=0; + m_needs_auth=0; + lpinfo[0]=0; + force_lpinfo[0]=0; + is_live = false; + memset(dlg_realm, 0, sizeof(dlg_realm)); + } + + ~VorbisFile_HTTP() + { + if (get) + { + waServiceFactory *sf = mod.service->service_getServiceByGuid(httpreceiverGUID); + if (sf) + sf->releaseInterface(get); + get=0; + } + if (saver) delete saver; + } + + void fillbuf(UINT max,bool shutup); + + size_t _http_read(char* ptr,size_t total); + int reconnect(UINT ofs); + virtual void post_init() + { + if (saver) saver->FixName(this,get->getheader("ice-name")); + } + + static BOOL CALLBACK httpDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam); + int m_needs_auth; + char dlg_realm[256]; + char lpinfo[256]; + char force_lpinfo[256]; + bool usedauth; +}; + +int VorbisFile_HTTP::reconnect(UINT ofs) +{ + // get.reset_headers(); + get->addheader("User-Agent: WinampOGG/5.24(MPEG stream compatible)"); + get->addheader("Accept:*/*"); + if (ofs>0) get->addheader(StringPrintf("Range: bytes=%u-",ofs)); + get->connect(AutoChar(url)); + + Status(WASABI_API_LNGSTRINGW(IDS_CONNECTING)); + + int st=get->run(); + if (st<0) + { + return 1; + } + return 0; +} + +void VorbisFile_HTTP::fillbuf(UINT max,bool shutup) +{ + if (len>0 && pos+max>len) max=len-pos; + while(!Aborting() && !abort_prebuf) //stop prebuffering if we wanna seek + { + if (!shutup) + { + Status(StringPrintfW(WASABI_API_LNGSTRINGW(IDS_PREBUFFERING), get->bytes_available()*100/bsize)); + } + if (get->run()) break; + if (Aborting() || abort_prebuf || get->bytes_available()>=(int)max) break; + Sleep(2); + } + if (!shutup) + { + Status(0); + } +} + +size_t VorbisFile_HTTP::_http_read(char* ptr,size_t total) +{ +#ifdef CANSEEK + if (seekpos!=-1) + { + UINT sp=seekpos; + seekpos=-1; + if (sp!=pos) + { + if (sp>pos && sp<=pos+get->bytes_available()) + { + get->get_bytes(0,sp-pos); + } + else + { + if (reconnect(sp)) + { + return 0;//oh well... + } + } + pos=sp; + } + } +#endif + UINT wr=0; + while(!Aborting() && wr<total) + { + int st=get->run(); + int d=get->get_bytes(ptr,(int)total-wr); + if (st && !d) break; + wr+=d; + ptr+=d; + pos+=d; + if ((len>0 && pos>=len) || wr>=total || Aborting()) break; + if (use_prebuf) fillbuf(bsize * cfg_prebuf2 / 100,0); + else Sleep(1); + } + return wr; +} + +void VorbisFile_HTTP::Idle() +{ + get->run(); + Sleep(1); + get->run(); + Sleep(1); +} + +size_t VorbisFile_HTTP::f_read(UINT siz,void* ptr) +{ + if (Aborting()) return 0;//fixme + int i=(int)_http_read((char*)ptr,siz); + if (i>0 && saver) saver->Write(ptr,i); + return i; +} + +int VorbisFile_HTTP::f_seek(ogg_int64_t offset,int whence) +{ +#ifdef CANSEEK + if (can_seek) + { + switch(whence) + { + case FILE_BEGIN: + seekpos=(int)offset; + break; + case FILE_END: + seekpos=len+(int)offset; + break; + case FILE_CURRENT: + seekpos=pos+(int)offset; + break; + } + if (seekpos>len) seekpos=len; + return 0; + } + else +#endif + return -1; +} + +UINT VorbisFile_HTTP::f_tell() +{ +#ifdef CANSEEK + if (can_seek) + { + if (seekpos!=-1) return seekpos; + else return pos; + } + else +#endif + return -1; +} + +HWND GetDialogBoxParent() +{ + HWND parent = (HWND)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETDIALOGBOXPARENT); + if (!parent || parent == (HWND)1) + return mod.hMainWindow; + return parent; +} + +bool VorbisFile_HTTP::http_init() +{ + if (mod.service) + { + waServiceFactory *sf = mod.service->service_getServiceByGuid(httpreceiverGUID); + if (sf) get = (api_httpreceiver *)sf->getInterface(); + } + if (!get) return false; + get->open(API_DNS_AUTODNS, bsize, do_proxy(AutoChar(url))); + + if (reconnect(0)) + { + return 0; + } + +#ifdef CANSEEK + // if (cfg_httpseek) + { + //need to get http headers first + while(!memcmp(get->getallheaders(),"\0\0",2)) + { + if (get->run()<0 || Aborting()) + { + int reply = get->getreplycode(); + if ( reply == 401 ) + { + api_connection *mcon=get->GetConnection(); + if ( mcon && mcon->GetReceiveBytesAvailable()) + { + char p[1024]=""; + while ( mcon->GetReceiveBytesAvailable() ) + { + char b[2048]=""; + mcon->ReceiveLine(b,2048); + if ( *b ) + { + char *t= strstr(b,"WWW-Authenticate:"); + if ( t && *t ) + { + char *y = strstr(t,"\""); + if ( y && *y ) + { + y++; + if ( *y ) + { + char *u = strstr(y,"\""); + if ( u && *u ) + { + *u = 0; + wsprintfA(p,"%s",y); + } + } + } + } + } + } + + if ( *p ) // found our realm + { + if (!force_lpinfo[0]) GetPrivateProfileStringA("HTTP-AUTH",p,"",force_lpinfo,sizeof(force_lpinfo),INI_FILE); + if (!force_lpinfo[0] || lpinfo[0] || usedauth ) + { + lstrcpynA(dlg_realm,p,sizeof(dlg_realm)); + if (!WASABI_API_DIALOGBOXPARAM(IDD_HTTPAUTH, GetDialogBoxParent(), httpDlgProc, (LPARAM)this)) + { + force_lpinfo[0]=0; + } + else + { + WritePrivateProfileStringA("HTTP-AUTH",p,force_lpinfo,INI_FILE); + } + } + Status(WASABI_API_LNGSTRINGW(IDS_AUTH_REQUIRED)); + m_needs_auth=1; + } + } + } + return 0; + } + //hg->get.wait(10); + Sleep(1); + } + len=get->content_length(); + const char* poo=get->getheader("icy-name"); + if (poo) stream_title=poo; + if (cfg_httpseek2 && len) can_seek=1; + is_live=(len<=0); + } +#endif + + //if (hg->len==0 || hg->len==-1) hg->can_seek=0; + seekpos=-1; + + if (cfg_fsave && !can_seek) + { + saver=new StreamSave; + } + return 1; +} + +VorbisFile * VorbisFile::Create_HTTP(const char * url,bool is_info) +{ + VorbisFile_HTTP * r=new VorbisFile_HTTP(cfg_http_bsize,AutoWide(url),is_info, false); + if (r) + { + if (!r->http_init()) + { + int trys=0; + while ( r && r->m_needs_auth && trys++ < 2) + { + const char *p=strstr(url,"://"); + if (p && *p) + { + p += 3; + if (p && *p) + { + char lurl[4096] = {0}; + wsprintfA(lurl, "http://%s@%s", r->force_lpinfo, p); + delete r; + r = new VorbisFile_HTTP(cfg_http_bsize,AutoWide(lurl),is_info, true); + if (r && r->http_init()) + { + return r; + } + } + } + } + delete r; + r=0; + } + } + return r; +} + +BOOL CALLBACK VorbisFile_HTTP::httpDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) +{ + VorbisFile_HTTP *_this; + switch (uMsg) + { + case WM_INITDIALOG: +#ifdef WIN64 + SetWindowLong(hwndDlg, GWLP_USERDATA, (LONG)lParam); + _this = (VorbisFile_HTTP*)(GetWindowLong(hwndDlg, GWLP_USERDATA)); +#else + SetWindowLong(hwndDlg, GWL_USERDATA, (LONG)lParam); + _this = (VorbisFile_HTTP*)GetWindowLong(hwndDlg, GWL_USERDATA); +#endif + if (_this->force_lpinfo[0]) + SetDlgItemTextA(hwndDlg,IDC_EDITAUTH,_this->force_lpinfo); + else SetDlgItemTextA(hwndDlg,IDC_EDITAUTH,_this->lpinfo); + SetDlgItemTextA(hwndDlg,IDC_REALM,_this->dlg_realm); + return 1; + + case WM_COMMAND: +#ifdef WIN64 + _this = (VorbisFile_HTTP*)GetWindowLong(hwndDlg, GWLP_USERDATA); +#else + _this = (VorbisFile_HTTP*)GetWindowLong(hwndDlg, GWL_USERDATA); +#endif + if (LOWORD(wParam) == IDOKAUTH) + { + char *p; + GetDlgItemTextA(hwndDlg,IDC_EDITAUTH,_this->force_lpinfo,sizeof(_this->force_lpinfo)); + p = strstr(_this->force_lpinfo,"\r"); + if ( p && *p ) *p=0; + p = strstr(_this->force_lpinfo,"\n"); + if ( p && *p ) *p=0; + EndDialog(hwndDlg,1); + } + else if (LOWORD(wParam) == IDCANCELAUTH) + { + EndDialog(hwndDlg,0); + } + break; + } + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/in_vorbis.rc b/Src/Plugins/Input/in_vorbis/in_vorbis.rc new file mode 100644 index 00000000..a8d4b5e3 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/in_vorbis.rc @@ -0,0 +1,415 @@ +// 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 + +///////////////////////////////////////////////////////////////////////////// +// Polish resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_PLK) +#ifdef _WIN32 +LANGUAGE LANG_POLISH, SUBLANG_DEFAULT +#pragma code_page(1250) +#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 + +#endif // Polish resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// 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 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ABOUT DIALOGEX 0, 0, 173, 113 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + CONTROL "",IDC_CUSTOM1,"XIPH_CLASS",0x0,6,8,44,40 + CTEXT "",IDC_RPM,7,56,45,8 + CTEXT "",IDC_RPM2,4,64,51,8 + LTEXT "",IDC_ABOUT_TEXT,58,8,110,85 + DEFPUSHBUTTON "OK",IDCANCEL,117,93,50,14 +END + +IDD_HTTPAUTH DIALOGEX 0, 0, 154, 70 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Ogg Authentication" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LTEXT "Realm: ",IDC_STATIC,4,4,25,8 + LTEXT "",IDC_REALM,37,4,113,8 + LTEXT "Enter your login and password in the form of:\n\tlogin:password",IDC_STATIC,4,15,146,17 + EDITTEXT IDC_EDITAUTH,4,35,146,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOKAUTH,4,52,50,13 + PUSHBUTTON "Cancel",IDCANCELAUTH,100,53,50,13 +END + +IDD_INFO_DLG_NEW DIALOGEX 0, 0, 329, 238 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "Ogg Vorbis Info" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LTEXT "File/URL :",-1,6,6,34,8 + EDITTEXT IDC_URL,43,5,282,12,ES_AUTOHSCROLL | ES_READONLY + GROUPBOX "File / Stream Info",IDC_STATIC_MISC,224,20,101,120 + EDITTEXT IDC_MISC,231,30,88,107,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | NOT WS_BORDER + GROUPBOX "Chained Streams",IDC_STATIC_CS,224,155,101,30 + PUSHBUTTON "<= previous",IDC_PREV_STREAM,229,167,44,13 + PUSHBUTTON "next =>",IDC_NEXT_STREAM,276,167,43,13 + CONTROL "Hide special fields",IDC_HIDE_SPEC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,229,190,73,10 + PUSHBUTTON "",IDC_MODE_TOGGLE,228,203,93,13 + PUSHBUTTON "Close",IDCANCEL,273,220,48,13 +END + +IDD_INFO_PANEL_SIMPLE DIALOGEX 0, 0, 227, 218 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "Standard Tags",IDC_STATIC_STD,3,2,219,119 + LTEXT "Title",IDC_STATIC,29,16,14,8 + EDITTEXT IDC_TITLE,48,14,168,12,ES_AUTOHSCROLL + LTEXT "Artist",IDC_STATIC,27,32,16,8 + EDITTEXT IDC_ARTIST,48,30,168,12,ES_AUTOHSCROLL + LTEXT "Album",IDC_STATIC,24,48,19,8 + EDITTEXT IDC_ALBUM,48,46,124,12,ES_AUTOHSCROLL + LTEXT "Track:",IDC_STATIC_TRACK,175,48,21,8 + EDITTEXT IDC_TRACK,198,46,18,12,ES_AUTOHSCROLL + LTEXT "Year",IDC_STATIC,28,64,16,8 + EDITTEXT IDC_DATE,48,62,56,12,ES_AUTOHSCROLL + LTEXT "Genre",IDC_STATIC,117,64,22,8 + COMBOBOX IDC_GENRE,140,62,77,68,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + LTEXT "Comment",IDC_STATIC,13,80,29,8 + EDITTEXT IDC_COMMENT,48,78,169,37,ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL + GROUPBOX "Other",IDC_STATIC_TAGS,3,123,219,91 + LISTBOX IDC_LIST,10,134,207,75,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP +END + +IDD_INFO_PANEL_ADVANCED DIALOGEX 0, 0, 227, 218 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LISTBOX IDC_LIST,3,3,219,212,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP +END + +IDD_INFO DIALOGEX 0, 0, 341, 164 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Advanced",IDC_STATIC,0,0,341,164 + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,6,13,164,143 + LTEXT "Name:",IDC_STATIC,175,13,22,8 + EDITTEXT IDC_NAME,175,23,159,14,ES_AUTOHSCROLL + LTEXT "Value:",IDC_STATIC,175,39,21,8 + EDITTEXT IDC_VALUE,175,49,159,90,ES_MULTILINE | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL + PUSHBUTTON "Add New",IDC_BUTTON_ADD,176,142,50,13 + PUSHBUTTON "Delete",IDC_BUTTON_DEL,230,142,50,13 + PUSHBUTTON "Delete All",IDC_BUTTON_DELALL,284,142,50,13 +END + +IDD_CONFIG DIALOGEX 0, 0, 222, 261 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Decoding",IDC_STATIC,4,4,214,93 + CONTROL "Buffer full files from disk",IDC_FULLBUF,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,17,89,10 + CONTROL "Show average bitrate while playing",IDC_AVG_BR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,31,128,10 + GROUPBOX "5.1-Channel Input Processing",IDC_STATIC,10,44,201,47 + LTEXT "Apply conversion:",IDC_STATIC,16,58,72,8,0,WS_EX_RIGHT + COMBOBOX IDC_MC6_DM,95,56,110,96,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Source channel order:",IDC_STATIC,16,75,72,8,0,WS_EX_RIGHT + COMBOBOX IDC_MC6_MAP,95,73,110,96,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Streaming",IDC_STATIC,4,101,214,139 + LTEXT "Buffer size",IDC_STATIC,10,115,34,8 + EDITTEXT IDC_HTTP_BSIZE,48,113,28,12,ES_AUTOHSCROLL | ES_NUMBER + LTEXT "KB",IDC_STATIC,80,115,9,8 + GROUPBOX "Prebuffering:",IDC_STATIC,10,129,201,44 + LTEXT "At start:",IDC_STATIC,16,142,31,8 + CONTROL "",IDC_SLIDER1,"msctls_trackbar32",TBS_NOTICKS | WS_TABSTOP,70,141,130,10 + LTEXT "After underrun:",IDC_STATIC,16,153,54,8 + CONTROL "",IDC_SLIDER2,"msctls_trackbar32",TBS_NOTICKS | WS_TABSTOP,70,152,130,10 + LTEXT "0%",IDC_STATIC,71,161,13,8 + LTEXT "50%",IDC_STATIC,122,161,18,8 + LTEXT "100%",IDC_STATIC,185,161,20,8 + LTEXT "Use Winamp's proxy server settings:",IDC_STATIC,10,179,120,8 + COMBOBOX IDC_PROXY,134,177,77,58,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + GROUPBOX "",IDC_STATIC,10,191,201,43 + CONTROL "Save streamed files to:",IDC_FSAVE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,15,201,89,10 + PUSHBUTTON "default",IDC_SSAVE_FMT_DEF,165,200,40,11 + PUSHBUTTON "",IDC_STREAM_SAVE,15,215,105,13 + EDITTEXT IDC_SSAVE_FMT,120,215,85,13,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,124,244,45,13 + PUSHBUTTON "Cancel",IDCANCEL,173,244,45,13 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_ABOUT, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 168 + TOPMARGIN, 4 + BOTTOMMARGIN, 107 + END + + IDD_HTTPAUTH, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 150 + TOPMARGIN, 4 + BOTTOMMARGIN, 66 + END + + IDD_CONFIG, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 218 + TOPMARGIN, 4 + BOTTOMMARGIN, 257 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + +#if defined(APSTUDIO_INVOKED) || defined(DISABLED) +#if defined(APSTUDIO_INVOKED) +IDB_BITMAP9$(DISABLED) BITMAP "oggdrop\\120.bmp" +#else +IDB_BITMAP9 BITMAP "oggdrop\\120.bmp" +#endif +#endif +#if defined(APSTUDIO_INVOKED) || defined(DISABLED) +#if defined(APSTUDIO_INVOKED) +IDB_BITMAP8$(DISABLED) BITMAP "oggdrop\\119.bmp" +#else +IDB_BITMAP8 BITMAP "oggdrop\\119.bmp" +#endif +#endif +#if defined(APSTUDIO_INVOKED) || defined(DISABLED) +#if defined(APSTUDIO_INVOKED) +IDB_BITMAP7$(DISABLED) BITMAP "oggdrop\\118.bmp" +#else +IDB_BITMAP7 BITMAP "oggdrop\\118.bmp" +#endif +#endif +#if defined(APSTUDIO_INVOKED) || defined(DISABLED) +#if defined(APSTUDIO_INVOKED) +IDB_BITMAP6$(DISABLED) BITMAP "oggdrop\\117.bmp" +#else +IDB_BITMAP6 BITMAP "oggdrop\\117.bmp" +#endif +#endif +#if defined(APSTUDIO_INVOKED) || defined(DISABLED) +#if defined(APSTUDIO_INVOKED) +IDB_BITMAP5$(DISABLED) BITMAP "oggdrop\\116.bmp" +#else +IDB_BITMAP5 BITMAP "oggdrop\\116.bmp" +#endif +#endif +#if defined(APSTUDIO_INVOKED) || defined(DISABLED) +#if defined(APSTUDIO_INVOKED) +IDB_BITMAP4$(DISABLED) BITMAP "oggdrop\\115.bmp" +#else +IDB_BITMAP4 BITMAP "oggdrop\\115.bmp" +#endif +#endif +#if defined(APSTUDIO_INVOKED) || defined(DISABLED) +#if defined(APSTUDIO_INVOKED) +IDB_BITMAP3$(DISABLED) BITMAP "oggdrop\\114.bmp" +#else +IDB_BITMAP3 BITMAP "oggdrop\\114.bmp" +#endif +#endif +#if defined(APSTUDIO_INVOKED) || defined(DISABLED) +#if defined(APSTUDIO_INVOKED) +IDB_BITMAP2$(DISABLED) BITMAP "oggdrop\\113.bmp" +#else +IDB_BITMAP2 BITMAP "oggdrop\\113.bmp" +#endif +#endif +#if defined(APSTUDIO_INVOKED) || defined(DISABLED) +#if defined(APSTUDIO_INVOKED) +IDB_BITMAP12$(DISABLED) BITMAP "oggdrop\\123.bmp" +#else +IDB_BITMAP12 BITMAP "oggdrop\\123.bmp" +#endif +#endif +#if defined(APSTUDIO_INVOKED) || defined(DISABLED) +#if defined(APSTUDIO_INVOKED) +IDB_BITMAP11$(DISABLED) BITMAP "oggdrop\\122.bmp" +#else +IDB_BITMAP11 BITMAP "oggdrop\\122.bmp" +#endif +#endif +#if defined(APSTUDIO_INVOKED) || defined(DISABLED) +#if defined(APSTUDIO_INVOKED) +IDB_BITMAP10$(DISABLED) BITMAP "oggdrop\\121.bmp" +#else +IDB_BITMAP10 BITMAP "oggdrop\\121.bmp" +#endif +#endif +#if defined(APSTUDIO_INVOKED) || defined(DISABLED) +#if defined(APSTUDIO_INVOKED) +IDB_BITMAP1$(DISABLED) BITMAP "oggdrop\\112.bmp" +#else +IDB_BITMAP1 BITMAP "oggdrop\\112.bmp" +#endif +#endif + +///////////////////////////////////////////////////////////////////////////// +// +// PNG +// + +IDB_PNG1 PNG "oggdrop\\112.png" +IDB_PNG2 PNG "oggdrop\\113.png" +IDB_PNG3 PNG "oggdrop\\114.png" +IDB_PNG4 PNG "oggdrop\\115.png" +IDB_PNG5 PNG "oggdrop\\116.png" +IDB_PNG6 PNG "oggdrop\\117.png" +IDB_PNG7 PNG "oggdrop\\118.png" +IDB_PNG8 PNG "oggdrop\\119.png" +IDB_PNG9 PNG "oggdrop\\120.png" +IDB_PNG10 PNG "oggdrop\\121.png" +IDB_PNG11 PNG "oggdrop\\122.png" +IDB_PNG12 PNG "oggdrop\\123.png" + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_VORBIS_DECODER "Nullsoft Vorbis Decoder v%s" + 65535 "{5C5BCA4E-279E-4867-8E24-58C8B186959A}" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_VORBIS_DECODER_OLD "Nullsoft Vorbis Decoder" + IDS_FILE_ERROR "file error" + IDS_LENGTH "Length: " + IDS_AVERAGE_BITRATE "Average bitrate: " + IDS_FILE_SIZE "File size: " + IDS_NOMINAL_BITRATE "Nominal bitrate: " + IDS_MIN_BITRATE "Min bitrate: " + IDS_MAX_BITRATE "Max bitrate: " + IDS_CHANNELS "Channels" + IDS_SAMPLING_RATE "Sampling rate" + IDS_SERIAL_NUMBER "Serial number" + IDS_VERSION "Version" +END + +STRINGTABLE +BEGIN + IDS_VENDOR "Vendor" + IDS_TO_SIMPLE_MODE "<< to simple mode" + IDS_TO_ADVANCED_MODE "to advanced mode >>" + IDS_OGG_VORBIS_INFO "Ogg Vorbis info - %s" + IDS_BEST_RPM "Best: %u RPM" + IDS_LEAVE_AS_IS "leave as is" + IDS_REMAP_6_CHANNELS "remap 6 channels" + IDS_DOWNMIX_TO_4_CHANNELS "downmix to 4 channels" + IDS_DOWNMIX_TO_2_CHANNELS_DS "downmix to 2 channels (DS)" + IDS_DOWNMIX_TO_2_CHANNELS_DS2 "downmix to 2 channels (DS2)" + IDS_DOWNMIX_TO_MONO "downmix to mono" + IDS_CORRECT_FL_FC_FR_BL_BR_LFE "correct (FL FC FR BL BR LFE)" + IDS_BROKEN_FL_FR_FC_BL_BR_LFE "broken (FL FR FC BL BR LFE)" +END + +STRINGTABLE +BEGIN + IDS_ABOUT_TEXT "%s\n© 2001-2023 Winamp SA\nWritten by: Peter Pawlowski\nBuild date: %hs\n\nThanks to:\n Craig Freer\n Tomi 'Nebularia' Jylhä-Ollila\n Jack Moffitt\n Szabolcs Péter" + IDS_TITLE_PREFERENCES "%s Preferences" + IDS_NEVER "never" + IDS_PORT_80_ONLY "port 80 only" + IDS_ALWAYS "always" + IDS_SELECT_OUTPUT_DIRECTORY "Select output directory:" +END + +STRINGTABLE +BEGIN + IDS_CONNECTING "Connecting..." + IDS_PREBUFFERING "Prebuffering : %u%%" + IDS_AUTH_REQUIRED "Authentication Required" + IDS_OGG_FILES "Ogg Vorbis Files (*.OGG;*.OGA)" + IDS_NAME "Name" + IDS_VALUE "Value" + IDS_KBPS "kbps" + IDS_HZ "Hz" + IDS_GAME_SPEED "%u RPM" + IDS_BYTES "bytes" + IDS_FAMILY_STRING "Ogg Vorbis File" +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_vorbis/in_vorbis.sln b/Src/Plugins/Input/in_vorbis/in_vorbis.sln new file mode 100644 index 00000000..fe0c2e6d --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/in_vorbis.sln @@ -0,0 +1,65 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29424.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_vorbis", "in_vorbis.vcxproj", "{7C58AC02-7941-42BE-8F97-9BF2064D6631}" + ProjectSection(ProjectDependencies) = postProject + {4FC28B55-2A14-43D5-86F7-201054F338A9} = {4FC28B55-2A14-43D5-86F7-201054F338A9} + {49238ED1-3146-49AB-9523-E9826EE4A0C8} = {49238ED1-3146-49AB-9523-E9826EE4A0C8} + {EC9475D2-FEE2-4F8C-9BB9-A11D5EB597C4} = {EC9475D2-FEE2-4F8C-9BB9-A11D5EB597C4} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libogg", "..\libogg\libogg.vcxproj", "{4FC28B55-2A14-43D5-86F7-201054F338A9}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vorbis_static", "..\libvorbis\win32\vorbis_static.vcxproj", "{49238ED1-3146-49AB-9523-E9826EE4A0C8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vorbisfile_static", "..\libvorbis\win32\vorbisfile_static.vcxproj", "{EC9475D2-FEE2-4F8C-9BB9-A11D5EB597C4}" +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 + {7C58AC02-7941-42BE-8F97-9BF2064D6631}.Debug|Win32.ActiveCfg = Debug|Win32 + {7C58AC02-7941-42BE-8F97-9BF2064D6631}.Debug|Win32.Build.0 = Debug|Win32 + {7C58AC02-7941-42BE-8F97-9BF2064D6631}.Release|Win32.ActiveCfg = Release|Win32 + {7C58AC02-7941-42BE-8F97-9BF2064D6631}.Release|Win32.Build.0 = Release|Win32 + {7C58AC02-7941-42BE-8F97-9BF2064D6631}.Debug|x64.ActiveCfg = Debug|x64 + {7C58AC02-7941-42BE-8F97-9BF2064D6631}.Debug|x64.Build.0 = Debug|x64 + {7C58AC02-7941-42BE-8F97-9BF2064D6631}.Release|x64.ActiveCfg = Release|x64 + {7C58AC02-7941-42BE-8F97-9BF2064D6631}.Release|x64.Build.0 = Release|x64 + {49238ED1-3146-49AB-9523-E9826EE4A0C8}.Debug|Win32.ActiveCfg = Debug|Win32 + {49238ED1-3146-49AB-9523-E9826EE4A0C8}.Debug|Win32.Build.0 = Debug|Win32 + {49238ED1-3146-49AB-9523-E9826EE4A0C8}.Release|Win32.ActiveCfg = Release|Win32 + {49238ED1-3146-49AB-9523-E9826EE4A0C8}.Release|Win32.Build.0 = Release|Win32 + {49238ED1-3146-49AB-9523-E9826EE4A0C8}.Debug|x64.ActiveCfg = Debug|x64 + {49238ED1-3146-49AB-9523-E9826EE4A0C8}.Debug|x64.Build.0 = Debug|x64 + {49238ED1-3146-49AB-9523-E9826EE4A0C8}.Release|x64.ActiveCfg = Release|x64 + {49238ED1-3146-49AB-9523-E9826EE4A0C8}.Release|x64.Build.0 = Release|x64 + {EC9475D2-FEE2-4F8C-9BB9-A11D5EB597C4}.Debug|Win32.ActiveCfg = Debug|Win32 + {EC9475D2-FEE2-4F8C-9BB9-A11D5EB597C4}.Debug|Win32.Build.0 = Debug|Win32 + {EC9475D2-FEE2-4F8C-9BB9-A11D5EB597C4}.Release|Win32.ActiveCfg = Release|Win32 + {EC9475D2-FEE2-4F8C-9BB9-A11D5EB597C4}.Release|Win32.Build.0 = Release|Win32 + {EC9475D2-FEE2-4F8C-9BB9-A11D5EB597C4}.Debug|x64.ActiveCfg = Debug|x64 + {EC9475D2-FEE2-4F8C-9BB9-A11D5EB597C4}.Debug|x64.Build.0 = Debug|x64 + {EC9475D2-FEE2-4F8C-9BB9-A11D5EB597C4}.Release|x64.ActiveCfg = Release|x64 + {EC9475D2-FEE2-4F8C-9BB9-A11D5EB597C4}.Release|x64.Build.0 = Release|x64 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Debug|Win32.ActiveCfg = Debug|Win32 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Debug|Win32.Build.0 = Debug|Win32 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Release|Win32.ActiveCfg = Release|Win32 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Release|Win32.Build.0 = Release|Win32 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Debug|x64.ActiveCfg = Debug|x64 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Debug|x64.Build.0 = Debug|x64 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Release|x64.ActiveCfg = Release|x64 + {4FC28B55-2A14-43D5-86F7-201054F338A9}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3248659F-0FFC-4CF4-A9BC-F7D10C713D5D} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_vorbis/in_vorbis.vcxproj b/Src/Plugins/Input/in_vorbis/in_vorbis.vcxproj new file mode 100644 index 00000000..3bfe43ab --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/in_vorbis.vcxproj @@ -0,0 +1,339 @@ +<?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>{7C58AC02-7941-42BE-8F97-9BF2064D6631}</ProjectGuid> + <RootNamespace>in_vorbis</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"> + <VcpkgEnableManifest>false</VcpkgEnableManifest> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <IntrinsicFunctions>true</IntrinsicFunctions> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;WA2;TAGZ_UNICODE;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4090;4312;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0415</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;Msimg32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + </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> + <IntrinsicFunctions>true</IntrinsicFunctions> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;_DEBUG;_WINDOWS;WA2;TAGZ_UNICODE;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4090;4312;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0415</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;Msimg32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + </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> + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;TAGZ_UNICODE;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> + <DisableSpecificWarnings>4090;4312;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0415</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;Msimg32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + <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> + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;TAGZ_UNICODE;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> + <DisableSpecificWarnings>4090;4312;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0415</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;Msimg32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + <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> + <ProjectReference Include="..\..\..\external_dependencies\openmpt-trunk\include\vorbis\win32\vorbisfile_static.vcxproj"> + <Project>{ec9475d2-fee2-4f8c-9bb9-a11d5eb597c4}</Project> + </ProjectReference> + <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj"> + <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\..\..\nsmkv\Lacing.cpp" /> + <ClCompile Include="..\..\..\nsmkv\vint.cpp" /> + <ClCompile Include="about.cpp" /> + <ClCompile Include="chainedstream_parse.cpp" /> + <ClCompile Include="config.cpp" /> + <ClCompile Include="c_string.cpp" /> + <ClCompile Include="decoder.cpp" /> + <ClCompile Include="ExtendedRead.cpp" /> + <ClCompile Include="genres.c" /> + <ClCompile Include="http.cpp" /> + <ClCompile Include="infobox.cpp" /> + <ClCompile Include="info_.cpp" /> + <ClCompile Include="localfile.cpp" /> + <ClCompile Include="mkv_vorbis_decoder.cpp" /> + <ClCompile Include="shaper.cpp" /> + <ClCompile Include="vcedit.c" /> + <ClCompile Include="wa2.cpp" /> + <ClCompile Include="winnt_helper.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="api__in_vorbis.h" /> + <ClInclude Include="c_string.h" /> + <ClInclude Include="decoder.h" /> + <ClInclude Include="genres.h" /> + <ClInclude Include="main.h" /> + <ClInclude Include="mkv_vorbis_decoder.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="shaper.h" /> + <ClInclude Include="vcedit.h" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_vorbis.rc" /> + </ItemGroup> + <ItemGroup> + <Image Include="oggdrop\112.png" /> + <Image Include="oggdrop\113.png" /> + <Image Include="oggdrop\114.png" /> + <Image Include="oggdrop\115.png" /> + <Image Include="oggdrop\116.png" /> + <Image Include="oggdrop\117.png" /> + <Image Include="oggdrop\118.png" /> + <Image Include="oggdrop\119.png" /> + <Image Include="oggdrop\120.png" /> + <Image Include="oggdrop\121.png" /> + <Image Include="oggdrop\122.png" /> + <Image Include="oggdrop\123.png" /> + </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_vorbis/in_vorbis.vcxproj.filters b/Src/Plugins/Input/in_vorbis/in_vorbis.vcxproj.filters new file mode 100644 index 00000000..6ce4ad5b --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/in_vorbis.vcxproj.filters @@ -0,0 +1,145 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="winnt_helper.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="wa2.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="vcedit.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="shaper.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="mkv_vorbis_decoder.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="localfile.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="infobox.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="info_.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="http.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="genres.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedRead.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="decoder.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="config.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="chainedstream_parse.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="c_string.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="about.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\Lacing.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsmkv\vint.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="api__in_vorbis.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="c_string.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="decoder.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="genres.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="main.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="mkv_vorbis_decoder.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="shaper.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="vcedit.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Image Include="oggdrop\112.png"> + <Filter>Image Files</Filter> + </Image> + <Image Include="oggdrop\113.png"> + <Filter>Image Files</Filter> + </Image> + <Image Include="oggdrop\114.png"> + <Filter>Image Files</Filter> + </Image> + <Image Include="oggdrop\115.png"> + <Filter>Image Files</Filter> + </Image> + <Image Include="oggdrop\116.png"> + <Filter>Image Files</Filter> + </Image> + <Image Include="oggdrop\117.png"> + <Filter>Image Files</Filter> + </Image> + <Image Include="oggdrop\118.png"> + <Filter>Image Files</Filter> + </Image> + <Image Include="oggdrop\119.png"> + <Filter>Image Files</Filter> + </Image> + <Image Include="oggdrop\120.png"> + <Filter>Image Files</Filter> + </Image> + <Image Include="oggdrop\121.png"> + <Filter>Image Files</Filter> + </Image> + <Image Include="oggdrop\122.png"> + <Filter>Image Files</Filter> + </Image> + <Image Include="oggdrop\123.png"> + <Filter>Image Files</Filter> + </Image> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{7c471b36-309f-402b-a247-e640a094f9bc}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{735fc214-c8e5-4619-8c70-a184a8373c92}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{00aa1653-baa3-4e88-b6d1-fa408b0c4a85}</UniqueIdentifier> + </Filter> + <Filter Include="Image Files"> + <UniqueIdentifier>{32126a2c-dd90-4901-a5e1-aee5b7363e37}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_vorbis.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/info_.cpp b/Src/Plugins/Input/in_vorbis/info_.cpp new file mode 100644 index 00000000..687dc737 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/info_.cpp @@ -0,0 +1,658 @@ +//tag editor file i/o code, title formatting interface +#include "main.h" +#include "genres.h" +#include "../nu/AutoWide.h" +#include "../nu/AutoChar.h" +#include "../nu/ns_wc.h" +#include "api__in_vorbis.h" +#include <wchar.h> +#include <math.h> +#include <shlwapi.h> +#include "vcedit.h" +#include <strsafe.h> +#include "resource.h" + +namespace ogg_helper //chainedstream_parse +{ + int num_get_tracks(HANDLE hFile); + int query_chained_stream_offset(HANDLE hFile, int idx, __int64 * out_beginning, __int64 * out_end); +} + +/*static void xfer(HANDLE src, HANDLE dst, __int64 size) +{ + enum { BUFFER = 1024 * 1024 }; + void * buffer = malloc((int)(BUFFER > size ? size : BUFFER)); + while (size > 0) + { + int d = BUFFER; + if ((__int64)d > size) d = (int)size; + DWORD br = 0; + ReadFile(src, buffer, d, &br, 0); + WriteFile(dst, buffer, d, &br, 0); + size -= d; + } +}*/ + +static void seek64(HANDLE src, __int64 offset) +{ + __int64 temp = offset; + SetFilePointer(src, *(DWORD*)&temp, ((long*)&temp + 1), FILE_BEGIN); +} + +extern OSVERSIONINFO os_ver; +extern HANDLE hThread; + +static DWORDLONG get_space(const wchar_t * url) +{ + ULARGE_INTEGER free_space; + char zzz[4] = {(char)url[0], (char)url[1], (char)url[2], 0}; //"c:\"; + + free_space.QuadPart = 0; + + if (os_ver.dwPlatformId == VER_PLATFORM_WIN32_NT || (os_ver.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS && LOWORD(os_ver.dwBuildNumber) > 1000)) + { + static BOOL (WINAPI* pGetDiskFreeSpaceEx)(LPCTSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER); + if (!pGetDiskFreeSpaceEx) + { + pGetDiskFreeSpaceEx = (BOOL (WINAPI*)(LPCTSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER))GetProcAddress(GetModuleHandle(L"kernel32.dll"), "GetDiskFreeSpaceExA"); + } + if (pGetDiskFreeSpaceEx) + { + ULARGE_INTEGER blah1, blah2; + pGetDiskFreeSpaceEx((LPCTSTR)zzz, &free_space, &blah1, &blah2); + } + } + if (!free_space.QuadPart) + { + DWORD spc, bps, nfc, tnc; + GetDiskFreeSpaceA(zzz, &spc, &bps, &nfc, &tnc); + free_space.QuadPart = UInt32x32To64(spc * bps, nfc); + } + return free_space.QuadPart; +} + +bool sync_movefile(const wchar_t * src, const wchar_t * dst); + +struct vcedit_param +{ + HANDLE hFile; + __int64 remaining; +}; + +static size_t callback_fread(void *ptr, size_t size, size_t nmemb, vcedit_param * param) +{ + int to_read = (int)(nmemb *size); + if (to_read > param->remaining) to_read = (int)param->remaining; + DWORD br = 0; + ReadFile(param->hFile, ptr, to_read, &br, 0); + param->remaining -= br; + return br / size; +} + +static size_t callback_write(const void *ptr, size_t size, size_t nmemb, HANDLE hFile) +{ + DWORD bw = 0; + WriteFile(hFile, ptr, (DWORD)(size*nmemb), &bw, 0); + return bw / size; +} + +BOOL modify_file(const wchar_t* url, const vorbis_comment * comments, int links) +{ //also used for stream save fix + HANDLE dst = INVALID_HANDLE_VALUE; + int scream = 0; + StringW tmp; + + winampGetExtendedFileInfoW_Cleanup(); + + tmp = url; + tmp += L".tmp"; + + HANDLE src = CreateFileW(url, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); + + if (src != INVALID_HANDLE_VALUE) + { + ULARGE_INTEGER src_size; + src_size.LowPart = GetFileSize(src, &src_size.HighPart); + if (src_size.QuadPart > get_space(url)) + { //shit happens... try default temp location + StringW tempdir; + GetTempPathW(MAX_PATH, StringTempW(tempdir, MAX_PATH)); + if (get_space(tempdir) < src_size.QuadPart) + { //oh well + CloseHandle(src); + src = INVALID_HANDLE_VALUE; + } + { + tmp = tempdir; + if (tmp[tmp.Length() - 1] != '\\') tmp.AddChar('\\'); + + StringCchPrintfW(StringTempW(tempdir, MAX_PATH), MAX_PATH, L"ogg%u_%u.tmp", GetTickCount(), GetCurrentProcessId()); + tmp.AddString(tempdir); + } + } + dst = CreateFileW(tmp, GENERIC_WRITE | GENERIC_READ, 0, 0, CREATE_ALWAYS, 0, 0); + } + + if (dst != INVALID_HANDLE_VALUE && src != INVALID_HANDLE_VALUE) + { + { + FILETIME ct; + GetFileTime(src, &ct, 0, 0); + SetFileTime(dst, &ct, 0, 0); + } + + int num_links = ogg_helper::num_get_tracks(src); + if (num_links < links) scream = 1; + else + { + int cur_link; + for (cur_link = 0; cur_link < links && !scream; cur_link++) + { + __int64 stream_beginning, stream_end; + if (ogg_helper::query_chained_stream_offset(src, cur_link, &stream_beginning, &stream_end)) + { + seek64(src, stream_beginning); + vcedit_state *vs; + vcedit_param param; + param.hFile = src; + param.remaining = stream_end - stream_beginning; + vs = vcedit_new_state(); + if (vcedit_open_callbacks(vs, ¶m, (vcedit_read_func)callback_fread, (vcedit_write_func)callback_write) < 0) + { + scream = 1; + } + else + { + vorbis_comment * vc = vcedit_comments(vs); + vorbis_comment_clear(vc); + vorbis_comment_init(vc); + const vorbis_comment * vc_src = comments + cur_link; + + int n; + for (n = 0;n < vc_src->comments;n++) + { + if (vc_src->user_comments[n]) + vorbis_comment_add(vc, vc_src->user_comments[n]); + } + + vcedit_write(vs, dst); + vcedit_clear(vs); + } + } + } + } + } + else scream = 1; + if (src != INVALID_HANDLE_VALUE) CloseHandle(src); + if (dst != INVALID_HANDLE_VALUE) + { + CloseHandle(dst); + if (scream) + { + DeleteFileW(tmp); + } + } + + if (!scream) + { + BOOL f_sync; + EnterCriticalSection(&sync); + + f_sync = !_wcsicmp(url, cur_file) && hThread; //check for i/o conflict with currently played file + LeaveCriticalSection(&sync); + if (f_sync) + { //drat, it's now playing + scream = !sync_movefile(tmp, url); + } + else + { + if (!DeleteFileW(url)) scream = 1; + else + { + if (!MoveFileW(tmp, url)) + { + if (!CopyFileW(tmp, url, 0)) scream = 1; + DeleteFileW(tmp); + } + } + } + } + if (scream) return 0; + else return 1; +} + +wchar_t *wdup(const char * src) +{ + return _wcsdup(StringW(src)); +} + +extern StringW stat_disp; + +void GetFileInfo(const wchar_t *file, wchar_t *title, int *len) +{ + VorbisFile* vf = 0; + BOOL is_cur_file = 0; + BOOL is_vf_local = 1; + if (title) *title = 0; + if (len) *len = -1; + + if (!file || !*file) + { + file = cur_file; + is_cur_file = 1; + } + else if (!lstrcmpiW(file, cur_file)) + { + is_cur_file = 1; + } + + if (title && stat_disp.Length() > 0 && is_cur_file) + { + lstrcpynW(title, stat_disp, 256); + title = 0; + } + + if (!len && !title) return ; + + if (is_cur_file) + { + EnterCriticalSection(&sync); + if (theFile) + { + vf = theFile; + is_vf_local = 0; + } + else + LeaveCriticalSection(&sync); + } + + if (!vf) + { + vf = VorbisFile::Create(file, 1); + if (!vf) + { + if (title) + { + lstrcpynW(title, PathFindFileNameW(file), 256); + wchar_t *blah = PathFindExtensionW(title); + *blah=0; + } + return ; + } + } + + if (len) + { + *len = (int)(vf->Length() * 1000); + } + + if (title) + { + const char *t = vf->get_meta("ARTIST", 0); + if (t) + { + MultiByteToWideCharSZ(CP_UTF8, 0, t, -1, title, 256); + t = vf->get_meta("TITLE", 0); + if (t) + { + StringCchCatW(title, 256, L" - "); + StringCchCatW(title, 256, AutoWide(t, CP_UTF8)); + } + } + else + { + const char *t = vf->get_meta("TITLE", 0); + if (t) + MultiByteToWideCharSZ(CP_UTF8, 0, t, -1, title, 256); + else + { + lstrcpynW(title, PathFindFileNameW(file), 256); + wchar_t *blah = PathFindExtensionW(title); + *blah=0; + } + } + } + //q: + if (is_vf_local) + delete vf; + else + LeaveCriticalSection(&sync); +} + +void w9x_itow(wchar_t *dest, int value, int destlen) +{ + StringCchPrintfW(dest, destlen, L"%d", value); + +} +void w9x_utow(wchar_t *dest, int value, int destlen) +{ + StringCchPrintfW(dest, destlen, L"%u", value); +} +void w9x_htow(wchar_t *dest, int value, int destlen) +{ + StringCchPrintfW(dest, destlen, L"%08x", value); +} + +static void print_misc(VorbisFile * _vf,int link,wchar_t * out, int outlen) +{ + OggVorbis_File * vf=&_vf->vf; + double t=ov_time_total(vf,link); + vorbis_info * vi=ov_info(vf,link); + vorbis_comment * vc=ov_comment(vf,link); + if (!vi || !vc) {WASABI_API_LNGSTRINGW_BUF(IDS_FILE_ERROR,out,outlen);return;} + + wchar_t kbps_str[16] = {0}; + WASABI_API_LNGSTRINGW_BUF(IDS_KBPS, kbps_str, 16); + + wchar_t length[48]=L"", avgbitrate[48]=L"", filesize[48]=L"", nombitrate[48]=L"", maxbitrate[48]=L"", minbitrate[48]=L""; + if (t>0) + { + int h = (int)(t/3600.0); + int m = (int)(t/60.0)%60; + int s = (int)t%60; + + if(h>0) StringCchPrintfW(length,48,L"%s%u:%02u:%02u\r\n",WASABI_API_LNGSTRINGW(IDS_LENGTH),h,m,s); + else if(m>0) StringCchPrintfW(length,48,L"%s%u:%02u\r\n",WASABI_API_LNGSTRINGW(IDS_LENGTH),m,s); + else if(s>0) StringCchPrintfW(length,48,L"%s%u\r\n",WASABI_API_LNGSTRINGW(IDS_LENGTH),s); + + UINT fs=_vf->FileSize(); + if (fs>0) + { + int kbps = (int)(((double)fs)/(t*125.0)); + wchar_t tmp[32] = {0}; + StringCchPrintfW(avgbitrate,48,L"%s%u %s\r\n",WASABI_API_LNGSTRINGW(IDS_AVERAGE_BITRATE),kbps,kbps_str); + + int fs1=fs/1000000; + int fs2=(fs/1000)%1000; + int fs3=fs%1000; + if(fs1) + StringCchPrintfW(filesize,48,L"%s%u%03u%03u %s\r\n",WASABI_API_LNGSTRINGW(IDS_FILE_SIZE),fs1,fs2,fs3,WASABI_API_LNGSTRINGW_BUF(IDS_BYTES,tmp,32)); + else if(fs2) + StringCchPrintfW(filesize,48,L"%s%u%03u %s\r\n",WASABI_API_LNGSTRINGW(IDS_FILE_SIZE),fs2,fs3,WASABI_API_LNGSTRINGW_BUF(IDS_BYTES,tmp,32)); + else + StringCchPrintfW(filesize,48,L"%s%u %s\r\n",WASABI_API_LNGSTRINGW(IDS_FILE_SIZE),fs3,WASABI_API_LNGSTRINGW_BUF(IDS_BYTES,tmp,32)); + } + } + + if (vi->bitrate_nominal>0) + StringCchPrintfW(nombitrate,48,L"%s%u %s\r\n",WASABI_API_LNGSTRINGW(IDS_NOMINAL_BITRATE),vi->bitrate_nominal/1000,kbps_str); + + if (vi->bitrate_lower>0) + StringCchPrintfW(minbitrate,48,L"%s%u %s\r\n",WASABI_API_LNGSTRINGW(IDS_MIN_BITRATE),vi->bitrate_lower/1000,kbps_str); + + if (vi->bitrate_nominal>0) + StringCchPrintfW(maxbitrate,48,L"%s%u %s\r\n",WASABI_API_LNGSTRINGW(IDS_MAX_BITRATE),vi->bitrate_nominal/1000,kbps_str); + + wchar_t tmp[32] = {0}, tmp2[32] = {0}, tmp3[32] = {0}, tmp4[32] = {0}, tmp5[32] = {0}, hzStr[8] = {0}; + StringCchPrintfW(out,outlen,L"%s%s%s%s%s%s%s: %u\r\n%s: %u %s\r\n%s: %u\r\n%s: %u\r\n%s: \r\n%s", + length, avgbitrate, filesize, nombitrate, maxbitrate, minbitrate, + WASABI_API_LNGSTRINGW_BUF(IDS_CHANNELS,tmp,32),vi->channels, + WASABI_API_LNGSTRINGW_BUF(IDS_SAMPLING_RATE,tmp2,32),vi->rate, WASABI_API_LNGSTRINGW_BUF(IDS_HZ,hzStr,8), + WASABI_API_LNGSTRINGW_BUF(IDS_SERIAL_NUMBER,tmp3,32),ov_serialnumber(vf,link), + WASABI_API_LNGSTRINGW_BUF(IDS_VERSION,tmp4,32),vi->version, + WASABI_API_LNGSTRINGW_BUF(IDS_VENDOR,tmp5,32),(wchar_t*)AutoWide(vc->vendor,CP_UTF8)); +} + +static VorbisFile* last_vf = 0; +static wchar_t last_file[MAX_PATH] = {0}; +static FILETIME ftLastWriteTime; + +// is used to determine if the last write time of the file has changed when +// asked to get the metadata for the same cached file so we can update things +BOOL HasFileTimeChanged(const wchar_t *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData = {0}; + if (GetFileAttributesExW(fn, GetFileExInfoStandard, &fileData) == TRUE) + { + if(CompareFileTime(&ftLastWriteTime, &fileData.ftLastWriteTime)) + { + ftLastWriteTime = fileData.ftLastWriteTime; + return TRUE; + } + } + return FALSE; +} + +void UpdateFileTimeChanged(const wchar_t *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData; + if (GetFileAttributesExW(fn, GetFileExInfoStandard, &fileData) == TRUE) + { + ftLastWriteTime = fileData.ftLastWriteTime; + } +} + +// need to call this when we shut down just to make sure things are correctly cleaned up +//(the joys of caching for speed) +void winampGetExtendedFileInfoW_Cleanup(void) +{ + if (last_vf) + { + delete last_vf; + last_vf = 0; + } + last_file[0] = 0; +} + +static void CALLBACK winampGetExtendedFileInfoW_Timer(HWND hwnd, UINT uMsg, UINT_PTR eventId, DWORD elapsed) +{ + // TODO need to do a better way of getting and caching the metadata + // this is _just_ a temp fix for the file being locked when it + // it really needs 'class Info' to be able to cache and read. + KillTimer(hwnd, eventId); + winampGetExtendedFileInfoW_Cleanup(); +} + +bool KeywordMatch(const char *mainString, const char *keyword) +{ + return !_stricmp(mainString, keyword); +} + +#define START_TAG_ALIAS(name, alias) if (KeywordMatch(data, name)) lookup=alias +#define TAG_ALIAS(name, alias) else if (KeywordMatch(data, name)) lookup=alias +extern "C" __declspec( dllexport ) int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, int destlen) +{ + if (!_stricmp(data, "type")) + { + dest[0] = '0'; + dest[1] = 0; + return 1; + } + else if (!_stricmp(data, "rateable")) + { + dest[0] = '1'; + dest[1] = 0; + return 1; + } + else if (!_stricmp(data, "streammetadata")) + { + return 0; + } + + if (!fn || (fn && !fn[0])) return 0; + + if (!_stricmp(data, "family")) + { + LPCWSTR e; + int pID = -1; + DWORD lcid; + e = PathFindExtensionW(fn); + if (L'.' != *e) return 0; + e++; + lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); + if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"OGG", -1) || + CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"OGA", -1)) pID = IDS_FAMILY_STRING; + if (pID != -1 && S_OK == StringCchCopyW(dest, destlen, WASABI_API_LNGSTRINGW(pID))) return 1; + return 0; + } + + if (!_stricmp(data, "mime")) + { + StringCchCopyW(dest, destlen, L"audio/ogg"); + return 1; + } + + // attempt to cache/use a cached instance of VorbisFile to speed up metadata queries + // which is especially needed with large ogg files (like with a 4Mb embedded image!) + VorbisFile* vf = 0; + if(last_file[0] && !_wcsicmp(last_file, fn) && !HasFileTimeChanged(fn)) + { + vf = last_vf; + } + else + { + // different file so clean up if there's a cached instance + if(last_vf) + { + delete last_vf; + last_vf = 0; + } + // open the new file and cache the current filename for subsequent query checks + vf = VorbisFile::Create(fn, 1); + lstrcpynW(last_file, fn, MAX_PATH); + } + + if (!vf) return 0; + else last_vf = vf; + + // TODO need to do a better way of getting and caching the metadata + // this is _just_ a temp fix for the file being locked when it + // it really needs 'class Info' to be able to cache and read. + SetTimer(mod.hMainWindow, 256, 2000, winampGetExtendedFileInfoW_Timer); + + const char *lookup = 0; + if (!_stricmp(data, "length")) + { + int len = (int)(vf->Length() * 1000); + w9x_itow(dest, len, destlen); + return 1; + } + else if (!_stricmp(data, "bitrate")) + { + int br = vf->get_avg_bitrate(); + w9x_itow(dest, br, destlen); + return 1; + } + else if (!_stricmp(data, "SERIALNUMBER")) + { + w9x_utow(dest, ov_serialnumber(&vf->vf, -1), destlen); + return 1; + } + else if (!_stricmp(data, "SERIALNUMBER_HEX")) + { + w9x_htow(dest, ov_serialnumber(&vf->vf, -1), destlen); + return 1; + } + else if (!_stricmp(data, "gain")) + { + float gain = 20.0f*(float)log10(vf->GetGain()); + StringCchPrintfW(dest, destlen, L"%-+.2f dB", gain); + return 1; + } + else if(!_stricmp(data,"formatinformation")) + { + print_misc(vf,0,dest,destlen); + return 1; + } + TAG_ALIAS("title", "TITLE"); + TAG_ALIAS("artist", "ARTIST"); + TAG_ALIAS("album", "ALBUM"); + TAG_ALIAS("genre", "GENRE"); + TAG_ALIAS("comment", "COMMENT"); + TAG_ALIAS("year", "DATE"); + TAG_ALIAS("track", "TRACKNUMBER"); + TAG_ALIAS("albumartist", "ALBUMARTIST"); + TAG_ALIAS("composer", "COMPOSER"); + TAG_ALIAS("disc", "DISCNUMBER"); + TAG_ALIAS("publisher", "PUBLISHER"); + TAG_ALIAS("conductor", "CONDUCTOR"); + TAG_ALIAS("tool", "ENCODED-BY"); + TAG_ALIAS("replaygain_track_gain", "REPLAYGAIN_TRACK_GAIN"); + TAG_ALIAS("replaygain_track_peak", "REPLAYGAIN_TRACK_PEAK"); + TAG_ALIAS("replaygain_album_gain", "REPLAYGAIN_ALBUM_GAIN"); + TAG_ALIAS("replaygain_album_peak", "REPLAYGAIN_ALBUM_PEAK"); + TAG_ALIAS("GracenoteFileID", "GRACENOTEFILEID"); + TAG_ALIAS("GracenoteExtData", "GRACENOTEEXTDATA"); + TAG_ALIAS("bpm", "BPM"); + TAG_ALIAS("remixing", "REMIXING"); + TAG_ALIAS("subtitle", "VERSION"); + TAG_ALIAS("isrc", "ISRC"); + TAG_ALIAS("category", "CATEGORY"); + TAG_ALIAS("rating", "RATING"); + TAG_ALIAS("producer", "PRODUCER"); + + if (!lookup) + return 0; + + const char *value = vf->get_meta(lookup, 0); + + if(KeywordMatch("comment",data)) { + if(!value || !*value) value = vf->get_meta("DESCRIPTION", 0); + } + + if(KeywordMatch("year",data)) { + if(!value || !*value) value = vf->get_meta("YEAR", 0); + } + + if(KeywordMatch("track",data)) { + if(!value || !*value) value = vf->get_meta("TRACK", 0); + } + + if(KeywordMatch("albumartist",data)) { + if(!value || !*value) value = vf->get_meta("ALBUM ARTIST", 0); + if(!value || !*value) value = vf->get_meta("ENSEMBLE", 0); + } + + if(KeywordMatch("publisher",data)) { + if(!value || !*value) value = vf->get_meta("ORGANIZATION", 0); + } + + if(KeywordMatch("category",data)) { + if(!value || !*value) value = vf->get_meta("CONTENTGROUP", 0); + if(!value || !*value) value = vf->get_meta("GROUPING", 0); + } + + if(KeywordMatch(data, "rating")) { + if(!value || !*value) value = vf->get_meta("RATING", 0); + if(value && *value) { + int rating = atoi(value); + // keeps things limited to our range of 0-100 + if (rating >= 100) { + rating = 5; + } + // 1-100 case + else if (rating > 5 && rating < 100) { + rating /= 20; + // shift up by one rating when in next band + // 1-20 = 1, 21-40 = 2, 41-60 = 3, 61-80 = 4, 81-100 = 5 + rating += ((atoi(value) - (rating * 20)) > 0); + } + // Remove support for old 1-10 range + /* or maybe we're dealing with a 1-10 range + else if (rating > 5) { + rating /= 2; + } */ + + // otherwise it is hopefully in the 0-5 range + else if (rating > 0 && rating <= 5) { + } + // otherwise just make sure and set zero + else { + rating = 0; + } + + StringCchPrintfW(dest, destlen, L"%u", rating); + return 1; + } + } + + if(value) + MultiByteToWideCharSZ(CP_UTF8, 0, value, -1, dest, destlen); + else + { + dest[0]=0; + return 1; + } + + return 1; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/infobox.cpp b/Src/Plugins/Input/in_vorbis/infobox.cpp new file mode 100644 index 00000000..30d90056 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/infobox.cpp @@ -0,0 +1,1457 @@ +#include "main.h" +#include "api__in_vorbis.h" +#include "genres.h" +#include <commctrl.h> +#include "../Agave/Language/api_language.h" +#include "../nu/AutoWide.h" +#include "../nu/AutoChar.h" +#include "resource.h" +#include <strsafe.h> + +extern In_Module mod; +/* old PP info box. still used for streams */ + + +extern CfgFont cfg_font; + +extern CfgInt cfg_modeless,cfg_remember_infosize; + +static CfgInt + cfg_hide_special_fields("hide_special_fields",1), + cfg_adv_info("adv_info",0), + cfg_infobox_sx("infobox_sx",0), + cfg_infobox_sy("infobox_sy",0), + cfg_infobox_x("infobox_x",0), + cfg_infobox_y("infobox_y",0); + +void SetDlgItemTextWrap(HWND w,UINT id,wchar_t * tx) +{ + SetDlgItemTextW(w,id,tx); +} + +typedef struct +{ + UINT id; + wchar_t * name; +} STD_TAG_ITEM; + +#define N_STD_TAGZ 7 + +extern BOOL cfg_adv_warn; + +static const STD_TAG_ITEM std_tagz[N_STD_TAGZ]= +{ + {IDC_TITLE,L"TITLE"}, + {IDC_ARTIST,L"ARTIST"}, + {IDC_ALBUM,L"ALBUM"}, + {IDC_GENRE,L"GENRE"}, + {IDC_DATE,L"DATE"}, + {IDC_COMMENT,L"COMMENT"}, + {IDC_TRACK,L"TRACKNUMBER"}, +}; + +const wchar_t * special_fields[]={L"RG_PEAK",L"RG_RADIO",L"RG_AUDIOPHILE",L"LWING_GAIN",L"REPLAYGAIN_ALBUM_GAIN",L"REPLAYGAIN_ALBUM_PEAK",L"REPLAYGAIN_TRACK_GAIN",L"REPLAYGAIN_TRACK_PEAK"}; +#define N_SPECIAL_FIELDS (sizeof(special_fields)/sizeof(special_fields[0])) + + +typedef struct tagTAG +{ + tagTAG * next; + wchar_t * name; + wchar_t * value; +} TAG; + +typedef struct +{ + wchar_t *name,*value; +} TAGDESC; + +class OggTagData +{ +public: + TAG * tags; + TAG ** last; + TAG * newtag() + { + TAG * t=new TAG; + *last=t; + last=&t->next; + t->next=0; + return t; + } + + OggTagData() + { + tags=0; + last=&tags; + } + + String vendor; + + OggTagData(vorbis_comment * vc) : vendor(vc->vendor) + { + tags=0; + last=&tags; + int n; + for(n=0;n<vc->comments;n++) + { + TAG * t=newtag(); + char * c=vc->user_comments[n]; + char * p=strchr(c,'='); + if (p) + { + int size = MultiByteToWideChar(CP_UTF8, 0, c, (int)(p-c), 0,0); + t->name=(wchar_t*)malloc((size+1)*sizeof(wchar_t)); + MultiByteToWideChar(CP_UTF8, 0, c, (int)(p-c), t->name, size); + t->name[size]=0; + p++; + } + else + { + t->name=_wcsdup(L"COMMENT"); + p=c; + } + + int size = MultiByteToWideChar(CP_UTF8, 0, p, -1, 0,0); + t->value=(wchar_t*)malloc((size)*sizeof(wchar_t)); + MultiByteToWideChar(CP_UTF8, 0, p, -1, t->value, size); + } + } + void Clear() + { + TAG * t=tags; + while(t) + { + TAG * t1=t->next; + free(t->name); + free(t->value); + delete t; + t=t1; + } + tags=0; + last=&tags; + } + void AddTag(const wchar_t * name,const wchar_t * value) + { + TAG * t=newtag(); + t->name=_wcsdup(name); + t->value=_wcsdup(value); + } + ~OggTagData() + { + Clear(); + } +}; + +static void SetWindowRect(HWND w,RECT * r) +{ + SetWindowPos(w,0,r->left,r->top,r->right-r->left,r->bottom-r->top,SWP_NOZORDER|SWP_NOCOPYBITS|SWP_NOACTIVATE); +} + +class DlgBase +{ +protected: + bool DieOnDestroyWindow,is_modeless,is_modal_ex,modal_ex_quit; + int modal_ex_quit_val; + + void endDialog(int x) + { + if (is_modeless) DestroyWindow(wnd); + else if (is_modal_ex) + { + modal_ex_quit=1; + modal_ex_quit_val=x; + DestroyWindow(wnd); + } + else EndDialog(wnd,x); + } + + void _do_size_x(RECT * r,UINT id,UINT wx,UINT min_x) + { + RECT r1={r->left,r->top,(LONG)(wx-min_x)+r->right,r->bottom}; + SetWindowRect(GetDlgItem(wnd,id),&r1); + } + + void _do_size_xy(RECT * r,UINT id,UINT wx,UINT wy,UINT min_x,UINT min_y) + { + RECT r1={r->left,r->top,(LONG)(wx-min_x)+r->right,(LONG)(wy-min_y)+r->bottom}; + SetWindowRect(GetDlgItem(wnd,id),&r1); + } + + void _do_align_x_size_y(RECT * r,UINT id,UINT wx,UINT wy,UINT min_x,UINT min_y) + { + RECT r1={ (LONG)(wx-min_x)+r->left,r->top,(LONG)(wx-min_x)+r->right,(LONG)(wy-min_y)+r->bottom}; + SetWindowRect(GetDlgItem(wnd,id),&r1); + } + + void _do_align_x(RECT * r,UINT id,UINT wx,UINT min_x) + { + RECT r1={ (LONG)(wx-min_x+r)->left,(LONG)r->top,(LONG)(wx-min_x+r)->right,(LONG)r->bottom}; + SetWindowRect(GetDlgItem(wnd,id),&r1); + } + + void _do_align_xy(RECT * r,UINT id,UINT wx,UINT wy,UINT min_x,UINT min_y) + { + RECT r1={(LONG)(wx-min_x+r)->left,(LONG)(wy-min_y+r)->top,(LONG)(wx- min_x+r)->right,(LONG)(wy-min_y+r)->bottom}; + SetWindowRect(GetDlgItem(wnd,id),&r1); + } + +#define do_size_x(id,r) _do_size_x(r,id,sx,min_size_x) +#define do_size_xy(id,r) _do_size_xy(r,id,sx,sy,min_size_x,min_size_y) +#define do_align_x_size_y(id,r) _do_align_x_size_y(r,id,sx,sy,min_size_x,min_size_y) +#define do_align_xy(id,r) _do_align_xy(r,id,sx,sy,min_size_x,min_size_y) +#define do_align_x(id,r) _do_align_x(r,id,sx,min_size_x) + + HWND wnd; + UINT min_size_x,min_size_y; + UINT min_size_x_w,min_size_y_w; + + void do_sizing(UINT wp,RECT * r) + { + UINT dx,dy; + dx=r->right-r->left; + dy=r->bottom-r->top; + if (dx<min_size_x_w) + { + switch(wp) + { + case WMSZ_BOTTOMLEFT: + case WMSZ_LEFT: + case WMSZ_TOPLEFT: + r->left=r->right-min_size_x_w; + break; + case WMSZ_BOTTOMRIGHT: + case WMSZ_RIGHT: + case WMSZ_TOPRIGHT: + r->right=r->left+min_size_x_w; + break; + } + } + if (dy<min_size_y_w) + { + switch(wp) + { + case WMSZ_BOTTOMLEFT: + case WMSZ_BOTTOM: + case WMSZ_BOTTOMRIGHT: + r->bottom=r->top+min_size_y_w; + break; + case WMSZ_TOPLEFT: + case WMSZ_TOP: + case WMSZ_TOPRIGHT: + r->top=r->bottom-min_size_y_w; + break; + } + } + } + void MakeComboEdit(UINT id,DWORD s) + { + HWND w=GetDlgItem(wnd,id); + RECT r; + GetChildRect(id,r); + DestroyWindow(w); + CreateWindowEx( WS_EX_CLIENTEDGE, L"EDIT", 0, WS_CHILD | s, r.left - 1, r.top - 1, r.right - r.left, r.bottom - r.top, wnd, (HMENU)id, 0, 0 ); + } + void GetChildRect(UINT id,RECT& child) + { + RECT r_parent,r_child; + GetWindowRect(wnd,&r_parent); + GetWindowRect(GetDlgItem(wnd,id),&r_child); + int dx=r_parent.left; + int dy=r_parent.top; + if (!(GetWindowLong(wnd,GWL_STYLE)&WS_CHILD)) + { + dy+=GetSystemMetrics(SM_CYCAPTION)+GetSystemMetrics(SM_CYDLGFRAME); + dx+=GetSystemMetrics(SM_CXDLGFRAME); + } + child.left=r_child.left-dx; + child.right=r_child.right-dx; + child.top=r_child.top-dy; + child.bottom=r_child.bottom-dy; + } + + virtual BOOL DlgProc(UINT msg,WPARAM wp,LPARAM lp) {return 0;}; + static BOOL CALLBACK TheDialogProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) + { + DlgBase * p; + if (msg==WM_INITDIALOG) + { + p=(DlgBase*)lp; +#ifdef WIN64 + SetWindowLong(wnd, DWLP_USER, (LONG)lp); +#else + SetWindowLong(wnd, DWL_USER, lp); +#endif + p->wnd=wnd; + RECT r; + GetClientRect(wnd,&r); + p->min_size_x=r.right; + p->min_size_y=r.bottom; + GetWindowRect(wnd,&r); + p->min_size_x_w=r.right-r.left; + p->min_size_y_w=r.bottom-r.top; + } +#ifdef WIN64 + else p = (DlgBase*)GetWindowLong(wnd, DWLP_USER); +#else + else p = (DlgBase*)GetWindowLong(wnd, DWL_USER); +#endif + BOOL rv=0; + if (p) + { + rv=p->DlgProc(msg,wp,lp); + if (msg==WM_DESTROY) + { + p->wnd=0; + if (p->DieOnDestroyWindow) + { + delete p; +#ifdef WIN64 + SetWindowLong(wnd, DWLP_USER, 0); +#else + SetWindowLong(wnd, DWL_USER, 0); +#endif + } + } + } + return rv; + } + HWND myCreateDialog(UINT id,HWND parent) + { + DieOnDestroyWindow=1; + is_modeless=1; + is_modal_ex=0; + return WASABI_API_CREATEDIALOGPARAMW(id,parent,TheDialogProc,(LPARAM)this); + } + virtual void myProcessMessage(MSG * msg) + { + if (!IsDialogMessage(wnd,msg)) + { + TranslateMessage(msg); + DispatchMessage(msg); + } + } + + int myDialogBoxEx(UINT id,HWND parent) + { + DieOnDestroyWindow=0; + is_modeless=0; + is_modal_ex=1; + modal_ex_quit=0; + modal_ex_quit_val=-1; + WASABI_API_CREATEDIALOGPARAMW(id,parent,TheDialogProc,(LPARAM)this); + if (wnd) + { + BOOL b=IsWindowEnabled(parent); + + if (b) EnableWindow(parent,0); + + MSG msg; + while(!modal_ex_quit && GetMessage(&msg,0,0,0)) + { + myProcessMessage(&msg); + } + + if (wnd) + { + DestroyWindow(wnd); + wnd=0; + } + + if (b) EnableWindow(parent,1); + SetActiveWindow(parent); + } + return modal_ex_quit_val; + } + + int myDialogBox(UINT id,HWND parent) + { + DieOnDestroyWindow=0; + is_modeless=0; + is_modal_ex=0; + return (int)WASABI_API_DIALOGBOXPARAMW(id,parent,TheDialogProc,(LPARAM)this); + } + DlgBase() + { + wnd=0; + DieOnDestroyWindow=0; + is_modeless=0; + is_modal_ex=0; + modal_ex_quit=0; + modal_ex_quit_val=0; + min_size_x=min_size_y=min_size_x_w=min_size_y_w=0; + } + virtual ~DlgBase() {DieOnDestroyWindow=0;if (wnd) DestroyWindow(wnd);} +public: + BOOL isDialogMessage(MSG * m) {return wnd ? IsDialogMessage(wnd,m) : 0;} +}; + +static char tags_file[]="tags.txt"; +static wchar_t genres_file[] = L"genres.txt"; + +class /*_declspec(novtable) */InfoDlgPanel : public DlgBase +{ +protected: + HFONT font; + HWND list; + RECT info_list; + OggTagData hidden; + + InfoDlgPanel(UINT id,HWND parent,HFONT foo) + { + font=foo; + myCreateDialog(id,parent); + //SetWindowLong(wnd,GWL_STYLE,GetWindowLong(wnd,GWL_STYLE)|WS_TABSTOP); + list=GetDlgItem(wnd,IDC_LIST); + SendMessage(list,WM_SETFONT,(WPARAM)font,0); + + GetChildRect(IDC_LIST,info_list); + } + + int lb_addstring(TAGDESC * tag,int idx=-1) + { + StringW tmp; + tmp+=tag->name; + tmp+=L"="; + tmp+=tag->value; + + const WCHAR * p=(const WCHAR*)tmp; + const WCHAR * foo=wcsstr(p,L"\x0d\x0a"); + if (foo) + { + tmp.Truncate((UINT)(foo-p)); + tmp.AddString(L" (...)"); + } + int rv= + (int)SendMessageW(list, + idx<0 ? LB_ADDSTRING : LB_INSERTSTRING, + idx<0 ? 0 : idx, + ((LPARAM)(const WCHAR*)tmp) + ); + if (rv>=0) SendMessage(list,LB_SETITEMDATA,rv,(LPARAM)tag); + else + { + free(tag->name); + free(tag->value); + delete tag; + } + return rv; + + } + + virtual void OnUpdateRect(UINT sx,UINT sy) + {//WM_SIZE-ish + do_size_xy(IDC_LIST,&info_list); + } + + virtual BOOL DlgProc(UINT msg,WPARAM wp,LPARAM lp) + { + switch(msg) + { + case WM_DESTROY: + lb_clear(); + list=0; + break; + case WM_COMMAND: + switch(wp) + { + case IDOK: + case IDCANCEL: + SendMessage(GetParent(wnd),msg,wp,lp); + break; + } + break; +// case WM_INITDIALOG: +// return 1; + } + return 0; + } + + void lb_clear() + { + if (!list) return; + int num=(int)SendMessage(list,LB_GETCOUNT,0,0); + while(num>0) + { + TAGDESC * l=(TAGDESC*)SendMessage(list,LB_GETITEMDATA,0,0); + if (l) + { + free(l->name); + free(l->value); + delete l; + } + SendMessage(list,LB_DELETESTRING,0,0); + num--; + } + } + + virtual void SetTag(wchar_t * name,wchar_t * value) + { + TAGDESC * l=new TAGDESC; + l->name=_wcsdup(name); + l->value=_wcsdup(value); + lb_addstring(l); + } + +public: + virtual void Clear() + { + hidden.Clear(); + lb_clear(); + } + void UpdateRect(RECT &r) + { + SetWindowPos(wnd,0,r.left,r.top,r.right-r.left,r.bottom-r.top,SWP_NOZORDER|SWP_NOACTIVATE|SWP_SHOWWINDOW); + RECT z; + GetClientRect(wnd,&z); + OnUpdateRect(z.right-z.left,z.bottom-z.top); + } + + virtual void GetTags(OggTagData & tags) + { + if (!list) return; + int num=(int)SendMessage(list,LB_GETCOUNT,0,0); + int n; + for(n=0;n<num;n++) + { + TAGDESC * l=(TAGDESC*)SendMessage(list,LB_GETITEMDATA,n,0); + tags.AddTag(l->name,l->value); + } + TAG * t=hidden.tags; + while(t) + { + tags.AddTag(t->name,t->value); + t=t->next; + } + } + + void SetTags(OggTagData & tags,BOOL hidespec) + { + TAG * t=tags.tags; + while(t) + { + bool hide=0; + if (hidespec) + { + int n; + for(n=0;n<N_SPECIAL_FIELDS;n++) + { + if (!_wcsicmp(t->name,special_fields[n])) + { + hidden.AddTag(t->name,t->value); + hide=1;break; + } + } + } + if (!hide) SetTag(t->name,t->value); + t=t->next; + } + } +}; + +class InfoDlgPanel_adv : public InfoDlgPanel +{ +private: + virtual void OnUpdateRect(UINT sx,UINT sy) + { + InfoDlgPanel::OnUpdateRect(sx,sy); + } +public: + InfoDlgPanel_adv(HWND parent,HFONT foo) : InfoDlgPanel(IDD_INFO_PANEL_ADVANCED,parent,foo) + { + } +}; + +class InfoDlgPanel_simple : public InfoDlgPanel +{ +private: + RECT info_static_std,info_title,info_artist,info_album,info_track,info_genre,info_comment,info_static_track,info_static_tags; + wchar_t *tag_bk[N_STD_TAGZ]; + BOOL STFU; +protected: + virtual void OnUpdateRect(UINT sx,UINT sy) + { + do_size_x(IDC_STATIC_STD,&info_static_std); + do_size_x(IDC_TITLE,&info_title); + do_size_x(IDC_ARTIST,&info_artist); + do_size_x(IDC_ALBUM,&info_album); + do_align_x(IDC_TRACK,&info_track); + do_size_x(IDC_GENRE,&info_genre); + do_size_x(IDC_COMMENT,&info_comment); + do_align_x(IDC_STATIC_TRACK,&info_static_track); + do_size_xy(IDC_STATIC_TAGS,&info_static_tags); + InfoDlgPanel::OnUpdateRect(sx,sy); + } + virtual BOOL DlgProc(UINT msg,WPARAM wp,LPARAM lp) + { + switch(msg) + { + case WM_COMMAND: + if (!STFU && (HIWORD(wp)==EN_CHANGE || HIWORD(wp)==CBN_EDITCHANGE || HIWORD(wp)==CBN_SELCHANGE)) + { + UINT n; + for(n=0;n<N_STD_TAGZ;n++) + { + if (LOWORD(wp)==std_tagz[n].id) + { + if (tag_bk[n]) {free(tag_bk[n]);tag_bk[n]=0;} + break; + } + } + } + break; + } + return InfoDlgPanel::DlgProc(msg,wp,lp); + } +public: + virtual void Clear() + { + int n; + for(n=0;n<N_STD_TAGZ;n++) + { + if (tag_bk[n]) + { + free(tag_bk[n]); + tag_bk[n]=0; + } + SetDlgItemText(wnd,std_tagz[n].id,L""); + } + InfoDlgPanel::Clear(); + + } + ~InfoDlgPanel_simple() + { + UINT n; + for(n=0;n<N_STD_TAGZ;n++) + { + if (tag_bk[n]) free(tag_bk[n]); + } + } + InfoDlgPanel_simple(HWND parent,HFONT foo) : InfoDlgPanel(IDD_INFO_PANEL_SIMPLE,parent,foo) + { + STFU=0; + memset(tag_bk,0,sizeof(tag_bk)); + UINT n; + MakeComboEdit(IDC_GENRE,ES_READONLY|ES_AUTOHSCROLL|WS_VISIBLE); + for(n=0;n<N_STD_TAGZ;n++) + { + HWND w=GetDlgItem(wnd,std_tagz[n].id); + SendMessage(w,WM_SETFONT,(WPARAM)font,0); + SendMessage(w,EM_SETREADONLY,1,0); + } + + GetChildRect(IDC_STATIC_STD,info_static_std); + GetChildRect(IDC_TITLE,info_title); + GetChildRect(IDC_ARTIST,info_artist); + GetChildRect(IDC_ALBUM,info_album); + GetChildRect(IDC_TRACK,info_track); + GetChildRect(IDC_GENRE,info_genre); + GetChildRect(IDC_COMMENT,info_comment); + GetChildRect(IDC_STATIC_TRACK,info_static_track); + GetChildRect(IDC_STATIC_TAGS,info_static_tags); + + genres_read(GetDlgItem(wnd,IDC_GENRE), genres_file); + } + + virtual void GetTags(OggTagData & tags) + { + genres_write(GetDlgItem(wnd,IDC_GENRE),genres_file); + UINT n; + for(n=0;n<N_STD_TAGZ;n++) + { + if (tag_bk[n]) + { + tags.AddTag(std_tagz[n].name,tag_bk[n]); + } + else + { + StringW t; + t.s_GetWindowText(GetDlgItem(wnd,std_tagz[n].id)); + if (t.Length()>0) + { + tags.AddTag(std_tagz[n].name,t); + } + } + } + InfoDlgPanel::GetTags(tags); + } + virtual void SetTag(wchar_t * name,wchar_t * value) + { + STFU=1; + UINT n; + for(n=0;n<N_STD_TAGZ;n++) + { + if (tag_bk[n]) continue; + if (!_wcsicmp(name,std_tagz[n].name)) + { + tag_bk[n]=_wcsdup(value); + SetDlgItemTextWrap(wnd,std_tagz[n].id,value); + STFU=0; + return; + } + } + STFU=0; + InfoDlgPanel::SetTag(name,value); + } +}; + +char * rstrcpy(char* s1,char* s2) +{ + while(s2 && *s2) *(s1++)=*(s2++); + return s1; +} + +static void _inline print_misc(VorbisFile * _vf,int link,char * out,int len) +{ + OggVorbis_File * vf=&_vf->vf; + char* p=out, kbps_str[16] = {0}; + double t=ov_time_total(vf,link); + vorbis_info * vi=ov_info(vf,link); + vorbis_comment * vc=ov_comment(vf,link); + if (!vi || !vc) {WASABI_API_LNGSTRING_BUF(IDS_FILE_ERROR,out,512);return;} + + StringCchPrintfA(kbps_str, 16, " %s\r\n", WASABI_API_LNGSTRING(IDS_KBPS)); + + if (t>0) + { + p=rstrcpy(p,WASABI_API_LNGSTRING(IDS_LENGTH)); + int h=(int)(t/3600.0); + if (h>0) + { + uitoa(h,p); + while(p && *p) p++; + *(p++)=':'; + } + int m=(int)(t/60.0)%60; +// if (m>0 || h>0) + { + sprintf(p,h>0 ? "%02u" : "%u",m); + while(p && *p) p++; + *(p++)=':'; + } + int s=(int)t%60; + //sprintf(p,(m>0 || h>0) ? "%02u" : "%u seconds",s); + sprintf(p,"%02d",s); + while(p && *p) p++; + p=rstrcpy(p,"\r\n"); + +// uitoa((int)(t*1000.0),p); +// while(p && *p) p++; +// p=rstrcpy(p," ms"); + UINT fs=_vf->FileSize(); + if (fs>0) + { + if (vf->links==1) + { + p=rstrcpy(p,WASABI_API_LNGSTRING(IDS_AVERAGE_BITRATE)); + uitoa((int)(((double)fs)/(t*125.0)),p); + while(p && *p) p++; + p=rstrcpy(p,kbps_str); + } + if (fs>0) + { + p=rstrcpy(p,WASABI_API_LNGSTRING(IDS_FILE_SIZE)); + + UINT fs1=fs/1000000; + UINT fs2=(fs/1000)%1000; + UINT fs3=fs%1000; + if (fs1) sprintf(p,"%u%03u%03u %s\r\n",fs1,fs2,fs3,WASABI_API_LNGSTRING(IDS_BYTES)); + else if (fs2) sprintf(p,"%u%03u %s\r\n",fs2,fs3,WASABI_API_LNGSTRING(IDS_BYTES)); + else sprintf(p,"%u %s\r\n",fs3,WASABI_API_LNGSTRING(IDS_BYTES)); + while(p && *p) p++; + } + } + } + if (vi->bitrate_nominal>0) + { + p=rstrcpy(p,WASABI_API_LNGSTRING(IDS_NOMINAL_BITRATE)); + uitoa(vi->bitrate_nominal/1000,p); + while(p && *p) p++; + p=rstrcpy(p,kbps_str); + } + if (vi->bitrate_lower>0) + { + p=rstrcpy(p,WASABI_API_LNGSTRING(IDS_MIN_BITRATE)); + uitoa(vi->bitrate_lower/1000,p); + while(p && *p) p++; + p=rstrcpy(p,kbps_str); + } + if (vi->bitrate_upper>0) + { + p=rstrcpy(p,WASABI_API_LNGSTRING(IDS_MAX_BITRATE)); + uitoa(vi->bitrate_upper/1000,p); + while(p && *p) p++; + p=rstrcpy(p,kbps_str); + } + + char tmp[32] = {0}, tmp2[32] = {0}, tmp3[32] = {0}, tmp4[32] = {0}, tmp5[32] = {0}, tmp6[8] = {0}; + StringCchPrintfA(p,len, "%s : %u\r\n" + "%s : %u %s\r\n" + "%s : %u\r\n" + "%s : %u\r\n" + "%s : \r\n%s", + WASABI_API_LNGSTRING_BUF(IDS_CHANNELS,tmp,32),vi->channels, + WASABI_API_LNGSTRING_BUF(IDS_SAMPLING_RATE,tmp2,32),vi->rate,WASABI_API_LNGSTRING_BUF(IDS_HZ,tmp6,8), + WASABI_API_LNGSTRING_BUF(IDS_SERIAL_NUMBER,tmp3,32),ov_serialnumber(vf,link), + WASABI_API_LNGSTRING_BUF(IDS_VERSION,tmp4,32),vi->version, + WASABI_API_LNGSTRING_BUF(IDS_VENDOR,tmp5,32),vc->vendor); +} + +class InfoDlg : public DlgBase +{ +private: + InfoDlg * next; + static InfoDlg* Instances; + OggTagData **tags; + char ** infos; + int n_streams,cur_stream; + StringW url; + HFONT ui_font; + BOOL is_adv; + InfoDlgPanel* panel; + RECT info_static_misc,info_misc,info_url,info_cancel,info_mode,info_static_cs,info_cs_next,info_cs_prev,info_hidespec; + + void calc_panel_rect(RECT &r) + { + RECT cr,cr1,cr2; + GetClientRect(wnd,&cr); + GetChildRect(IDC_STATIC_MISC,cr1); + GetChildRect(IDC_URL,cr2); + r.left=0; + r.top=cr2.bottom+1; + r.right=cr1.left-1; + r.bottom=cr.bottom; + } + void do_size() + { + if (panel) + { + RECT r; + calc_panel_rect(r); + panel->UpdateRect(r); + } + } + void do_panel(BOOL mode,int d_stream) + { + //if (panel && is_adv==mode && !d_stream) return; + if (panel) + { + if (mode!=is_adv) + { + delete panel; + panel=0; + } + else + { + panel->Clear(); + } + } + cur_stream+=d_stream; + if (cur_stream<0) cur_stream=0; + else if (cur_stream>=n_streams) cur_stream=n_streams-1; + is_adv=mode; + if (!panel) + { + panel = mode ? (InfoDlgPanel*) new InfoDlgPanel_adv(wnd,ui_font) : (InfoDlgPanel*) new InfoDlgPanel_simple(wnd,ui_font); + do_size(); + } + if (panel) + { + panel->SetTags(*tags[cur_stream],(BOOL)SendDlgItemMessage(wnd,IDC_HIDE_SPEC,BM_GETCHECK,0,0)); + } + SetDlgItemText(wnd,IDC_MODE_TOGGLE,WASABI_API_LNGSTRINGW((is_adv ? IDS_TO_SIMPLE_MODE : IDS_TO_ADVANCED_MODE))); + EnableWindow(GetDlgItem(wnd,IDC_PREV_STREAM),cur_stream>0); + EnableWindow(GetDlgItem(wnd,IDC_NEXT_STREAM),cur_stream<n_streams-1); + // TODO + SetDlgItemTextA(wnd,IDC_MISC,infos[cur_stream]); + } +protected: + virtual BOOL DlgProc(UINT msg,WPARAM wp,LPARAM lp) + { + switch(msg) + { + case WM_INITDIALOG: + if (n_streams<=1) + { + ShowWindow(GetDlgItem(wnd,IDC_STATIC_CS),SW_HIDE); + ShowWindow(GetDlgItem(wnd,IDC_PREV_STREAM),SW_HIDE); + ShowWindow(GetDlgItem(wnd,IDC_NEXT_STREAM),SW_HIDE); + } + + SendDlgItemMessage(wnd,IDC_HIDE_SPEC,BM_SETCHECK,cfg_hide_special_fields,0); + + do_panel(cfg_adv_info,0); + url.s_SetDlgItemText(wnd,IDC_URL); + GetChildRect(IDC_URL,info_url); + GetChildRect(IDC_STATIC_MISC,info_static_misc); + GetChildRect(IDC_MISC,info_misc); + GetChildRect(IDCANCEL,info_cancel); + GetChildRect(IDC_MODE_TOGGLE,info_mode); + GetChildRect(IDC_STATIC_CS,info_static_cs); + GetChildRect(IDC_NEXT_STREAM,info_cs_next); + GetChildRect(IDC_PREV_STREAM,info_cs_prev); + GetChildRect(IDC_HIDE_SPEC,info_hidespec); + + if (cfg_remember_infosize && cfg_infobox_sx>0 && cfg_infobox_sy>0) + { + int max_x=GetSystemMetrics(SM_CXSCREEN),max_y=GetSystemMetrics(SM_CYSCREEN); + if (cfg_infobox_x<0) cfg_infobox_x=0; + else if (cfg_infobox_x+cfg_infobox_sx>max_x) cfg_infobox_x=max_x-cfg_infobox_sx; + if (cfg_infobox_y<0) cfg_infobox_y=0; + else if (cfg_infobox_y+cfg_infobox_sy>max_y) cfg_infobox_y=max_y-cfg_infobox_sy; + + SetWindowPos(wnd,0,cfg_infobox_x,cfg_infobox_y,cfg_infobox_sx,cfg_infobox_sy,SWP_NOZORDER|SWP_NOACTIVATE); + } + + StringPrintfW(WASABI_API_LNGSTRINGW(IDS_OGG_VORBIS_INFO),(const WCHAR*)StringF2T_W((const WCHAR*)url)).s_SetWindowText(wnd); + return 1; + case WM_SIZE: + { + UINT sx=LOWORD(lp),sy=HIWORD(lp); + do_size_x(IDC_URL,&info_url); + do_align_x(IDC_STATIC_MISC,&info_static_misc); + do_align_x(IDC_MISC,&info_misc); + do_align_xy(IDCANCEL,&info_cancel); + do_align_xy(IDC_MODE_TOGGLE,&info_mode); + do_align_xy(IDC_STATIC_CS,&info_static_cs); + do_align_xy(IDC_PREV_STREAM,&info_cs_prev); + do_align_xy(IDC_NEXT_STREAM,&info_cs_next); + do_align_xy(IDC_HIDE_SPEC,&info_hidespec); + } + //RedrawWindow(wnd,0,0,RDW_INVALIDATE); + do_size(); + break; + case WM_SIZING: + do_sizing((UINT)wp,(RECT*)lp); + break; + case WM_COMMAND: + switch(wp) + { + case IDCANCEL: + endDialog(0); + break; + case IDC_MODE_TOGGLE: + do_panel(!is_adv,0); + break; + case IDC_PREV_STREAM: + do_panel(is_adv,-1); + break; + case IDC_NEXT_STREAM: + do_panel(is_adv,1); + break; + case IDC_HIDE_SPEC: + cfg_hide_special_fields=(int)SendMessage((HWND)lp,BM_GETCHECK,0,0); + do_panel(is_adv,0); + break; + } + break; + case WM_CLOSE: + endDialog(0); + break; + case WM_DESTROY: + if (!is_modeless)//fucko close + { + modal_ex_quit=1; + } + + { + RECT r; + GetWindowRect(wnd,&r); + cfg_infobox_sx=r.right-r.left; + cfg_infobox_sy=r.bottom-r.top; + cfg_infobox_x=r.left; + cfg_infobox_y=r.top; + } + break; + } + return 0; + } + virtual void myProcessMessage(MSG * msg) + { + if (!panel || !panel->isDialogMessage(msg)) + { + DlgBase::myProcessMessage(msg); + } + } + +public: + InfoDlg(VorbisFile * _vf,const wchar_t * _url) + : url(_url), is_adv(FALSE) + { + OggVorbis_File * vf=&_vf->vf; + n_streams=vf->links; + cur_stream=vf->current_link; + tags=(OggTagData**)malloc(n_streams*sizeof(void*)); + int n; + for(n=0;n<n_streams;n++) tags[n]=new OggTagData(ov_comment(vf,n)); + infos=(char**)malloc(sizeof(void*)*n_streams); + for(n=0;n<n_streams;n++) + { + int l = 512+(int)strlen(vf->vc->vendor); + infos[n]=(char*)malloc(l); + print_misc(_vf,n,infos[n],l); + } + + ui_font=CreateFontIndirect(&cfg_font.data); + panel=0; + next=0; + } + ~InfoDlg() + { + if (ui_font) DeleteObject((HGDIOBJ)ui_font); + cfg_adv_info=is_adv; + if (tags) + { + int n; + for(n=0;n<n_streams;n++) + delete tags[n]; + free(tags); + } + if (infos) + { + int n; + for(n=0;n<n_streams;n++) free(infos[n]); + free(infos); + } + + InfoDlg ** pp=&Instances,*p=Instances; + while(p) + { + if (p==this) + { + *pp=next; + break; + } + else {pp=&p->next;p=*pp;} + } + } + void Run(HWND parent,bool modeless) + { + next=Instances; + Instances=this;//HACK - prevent crash on shutdown (used to be only for modeless) + + if (modeless) + { + myCreateDialog(IDD_INFO_DLG_NEW,parent); + } + else myDialogBoxEx(IDD_INFO_DLG_NEW,parent); + } + +friend int RunInfoDlg(const in_char * url,HWND parent); +}; + +int RunInfoDlg(const in_char * url,HWND parent) +{ + static bool in_modal; + if (in_modal) return 1; + else in_modal=1; + VorbisFile * vf; + bool vf_global=0; + int ret=0; + StringW _url; + _url.AddString(url); + + { + InfoDlg * p=InfoDlg::Instances; + while(p) + { + if (!_wcsicmp(p->url,_url)) + { + ShowWindow(p->wnd,SW_SHOW); + SetActiveWindow(p->wnd); + return 0; + } + p=p->next; + } + } + + EnterCriticalSection(&sync); + if ((url==cur_file || !_wcsicmp(url,cur_file)) && theFile) {vf=theFile;vf_global=1;} + else + { + LeaveCriticalSection(&sync); + vf=VorbisFile::Create(url,1); + if (!vf) + { + in_modal=0; + return 0; + } + } + { + InfoDlg d(vf,_url); + if (vf_global) LeaveCriticalSection(&sync); + else delete vf; + d.Run(parent,0); + ret = !d.modal_ex_quit_val; + } + in_modal=0; + return ret; +} + +InfoDlg* InfoDlg::Instances=0; +/* end crappy PP dialog */ + +bool VorbisTagToWinampTag(wchar_t * tag, int len) +{ +#define TAG_ALIAS(b,a) if(!_wcsicmp(L ## a, tag)) { lstrcpynW(tag, L ## b, len); return true; } + TAG_ALIAS("title", "TITLE"); + TAG_ALIAS("artist", "ARTIST"); + TAG_ALIAS("album", "ALBUM"); + TAG_ALIAS("genre", "GENRE"); + TAG_ALIAS("comment", "COMMENT"); + TAG_ALIAS("year", "DATE"); + TAG_ALIAS("track", "TRACKNUMBER"); + TAG_ALIAS("albumartist", "ALBUMARTIST"); + TAG_ALIAS("composer", "COMPOSER"); + TAG_ALIAS("disc", "DISCNUMBER"); + TAG_ALIAS("publisher", "PUBLISHER"); + TAG_ALIAS("conductor", "CONDUCTOR"); + TAG_ALIAS("bpm", "BPM"); + return false; +#undef TAG_ALIAS +} + +bool WinampTagToVorbisTag(wchar_t * tag, int len) +{ +#define TAG_ALIAS(a,b) if(!_wcsicmp(L ## a, tag)) { lstrcpynW(tag, L ## b, len); return true; } + TAG_ALIAS("title", "TITLE"); + TAG_ALIAS("artist", "ARTIST"); + TAG_ALIAS("album", "ALBUM"); + TAG_ALIAS("genre", "GENRE"); + TAG_ALIAS("comment", "COMMENT"); + TAG_ALIAS("year", "DATE"); + TAG_ALIAS("track", "TRACKNUMBER"); + TAG_ALIAS("albumartist", "ALBUMARTIST"); + TAG_ALIAS("composer", "COMPOSER"); + TAG_ALIAS("disc", "DISCNUMBER"); + TAG_ALIAS("publisher", "PUBLISHER"); + TAG_ALIAS("conductor", "CONDUCTOR"); + TAG_ALIAS("bpm", "BPM"); + return false; +#undef TAG_ALIAS +} + +static INT_PTR CALLBACK ChildProc_Advanced(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { + static int sel=-1; + static int ismychange=0; + switch(msg) + { + case WM_NOTIFYFORMAT: + return NFR_UNICODE; + case WM_INITDIALOG: + { + #define ListView_InsertColumnW(hwnd, iCol, pcol) \ + (int)SNDMSG((hwnd), LVM_INSERTCOLUMNW, (WPARAM)(int)(iCol), (LPARAM)(const LV_COLUMNW *)(pcol)) + SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam); + sel=-1; + HWND hwndlist = GetDlgItem(hwndDlg,IDC_LIST); + ListView_SetExtendedListViewStyle(hwndlist, LVS_EX_FULLROWSELECT); + LVCOLUMNW lvc = {0, }; + lvc.mask = LVCF_TEXT|LVCF_WIDTH; + lvc.pszText = WASABI_API_LNGSTRINGW(IDS_NAME); + lvc.cx = 82; + ListView_InsertColumnW(hwndlist, 0, &lvc); + lvc.pszText = WASABI_API_LNGSTRINGW(IDS_VALUE); + lvc.cx = 160; + ListView_InsertColumnW(hwndlist, 1, &lvc); + + Info *info = (Info *)lParam; + int n = info->GetNumMetadataItems(); + for(int i=0; i<n; i++) { + wchar_t key[512] = {0}; + wchar_t value[2048] = {0}; + info->EnumMetadata(i,key,512,value,2048); + if(value[0] && key[0]) { + LVITEMW lvi={LVIF_TEXT,i,0}; + lvi.pszText = key; + SendMessage(hwndlist,LVM_INSERTITEMW,0,(LPARAM)&lvi); + lvi.iSubItem=1; + lvi.pszText = (wchar_t*)value; + SendMessage(hwndlist,LVM_SETITEMW,0,(LPARAM)&lvi); + } + } + ListView_SetColumnWidth(hwndlist,0,(n?LVSCW_AUTOSIZE:LVSCW_AUTOSIZE_USEHEADER)); + ListView_SetColumnWidth(hwndlist,1,(n?LVSCW_AUTOSIZE:LVSCW_AUTOSIZE_USEHEADER)); + + SetDlgItemTextW(hwndDlg,IDC_NAME,L""); + SetDlgItemTextW(hwndDlg,IDC_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),FALSE); + } + break; + case WM_DESTROY: + { + HWND hwndlist = GetDlgItem(hwndDlg,IDC_LIST); + ListView_DeleteAllItems(hwndlist); + while(ListView_DeleteColumn(hwndlist,0)); + Info * info = (Info*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + delete info; + } + break; + case WM_USER: + if(wParam && lParam && !ismychange) + { + wchar_t * value = (wchar_t*)lParam; + wchar_t tag[100] = {0}; + lstrcpynW(tag,(wchar_t*)wParam,100); + WinampTagToVorbisTag(tag,100); + Info *info = (Info *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if(!*value) + { + info->RemoveMetadata(tag); + if(!_wcsicmp(L"ALBUMARTIST",tag)) + { + // need to remove these two, also, or else it's gonna look like delete doesn't work + // if the file was tagged using these alternate fields + info->RemoveMetadata(L"ALBUM ARTIST"); + info->RemoveMetadata(L"ENSEMBLE"); + } + if(!_wcsicmp(L"PUBLISHER",tag)) + { + // need to remove this also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + info->RemoveMetadata(L"ORGANIZATION"); + } + if(!_wcsicmp(L"DATE",tag)) + { + // need to remove this also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + info->RemoveMetadata(L"YEAR"); + } + if(!_wcsicmp(L"TRACKNUMBER",tag)) + { + // need to remove this also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + info->RemoveMetadata(L"TRACK"); + } + } + else + { + info->SetMetadata(tag,value); + } + + HWND hlist = GetDlgItem(hwndDlg,IDC_LIST); + int n = ListView_GetItemCount(hlist); + for(int i=0; i<n; i++) + { + wchar_t key[100]=L""; + LVITEMW lvi={LVIF_TEXT,i,0}; + lvi.pszText=key; + lvi.cchTextMax=100; + SendMessage(hlist,LVM_GETITEMW,0,(LPARAM)&lvi); + if(!_wcsicmp(key,tag)) + { + lvi.iSubItem = 1; + lvi.pszText = value; + SendMessage(hlist,LVM_SETITEMW,0,(LPARAM)&lvi); + if(!*value) + ListView_DeleteItem(hlist,i); + else if(ListView_GetItemState(hlist,i,LVIS_SELECTED)) + SetDlgItemTextW(hwndDlg,IDC_VALUE,value); + return 0; + } + } + // bew hew, not found + LVITEMW lvi={0,0x7FFFFFF0,0}; + n = (int)SendMessage(hlist,LVM_INSERTITEMW,0,(LPARAM)&lvi); + lvi.mask = LVIF_TEXT; + lvi.iItem = n; + lvi.iSubItem = 0; + lvi.pszText = tag; + SendMessage(hlist,LVM_SETITEMW,0,(LPARAM)&lvi); + lvi.iSubItem = 1; + lvi.pszText = value; + SendMessage(hlist,LVM_SETITEMW,0,(LPARAM)&lvi); + } + break; + case WM_NOTIFY: + { + LPNMHDR l=(LPNMHDR)lParam; + if(l->idFrom==IDC_LIST && l->code == LVN_KEYDOWN) { + if((((LPNMLVKEYDOWN)l)->wVKey) == VK_DELETE){ + int selitem = ListView_GetNextItem(l->hwndFrom,-1,LVNI_SELECTED|LVNI_FOCUSED); + if(selitem != -1) + SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_BUTTON_DEL,BN_CLICKED),(LPARAM)GetDlgItem(hwndDlg,IDC_BUTTON_DEL)); + } + } + else if(l->idFrom==IDC_LIST && l->code == LVN_ITEMCHANGED) { + LPNMLISTVIEW lv=(LPNMLISTVIEW)lParam; + if(lv->uNewState & LVIS_SELECTED) { + int n = lv->iItem; + LVITEMW lvi={LVIF_TEXT,lv->iItem,0}; + wchar_t key[100] = {0}; + wchar_t value[1024] = {0}; + lvi.pszText=key; + lvi.cchTextMax=100; + SendMessage(l->hwndFrom,LVM_GETITEMW,0,(LPARAM)&lvi); + lvi.pszText=value; + lvi.cchTextMax=1024; + lvi.iSubItem=1; + SendMessage(l->hwndFrom,LVM_GETITEMW,0,(LPARAM)&lvi); + SetDlgItemTextW(hwndDlg,IDC_NAME,key); + SetDlgItemTextW(hwndDlg,IDC_VALUE,value); + sel = n; + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),TRUE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),TRUE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),TRUE); + } + if(lv->uOldState & LVIS_SELECTED) { + sel = -1; + SetDlgItemTextW(hwndDlg,IDC_NAME,L""); + SetDlgItemTextW(hwndDlg,IDC_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),FALSE); + } + } + } + break; + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDOK: + { + Info * info = (Info*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + if (!info->Save()) + { + MessageBox(hwndDlg, + L"Cannot save metadata: Error writing file or file is read-only.", + L"Error saving metadata.", + MB_OK); + } + } + break; + case IDC_NAME: + case IDC_VALUE: + if(HIWORD(wParam) == EN_CHANGE && sel>=0) { + wchar_t key[100] = {0}; + wchar_t value[1024] = {0}; + LVITEMW lvi={LVIF_TEXT,sel,0}; + GetDlgItemTextW(hwndDlg,IDC_NAME,key,100); + GetDlgItemTextW(hwndDlg,IDC_VALUE,value,1024); + lvi.pszText=key; + lvi.cchTextMax=100; + SendMessage(GetDlgItem(hwndDlg,IDC_LIST),LVM_SETITEMW,0,(LPARAM)&lvi); + lvi.pszText=value; + lvi.cchTextMax=1024; + lvi.iSubItem=1; + SendMessage(GetDlgItem(hwndDlg,IDC_LIST),LVM_SETITEMW,0,(LPARAM)&lvi); + VorbisTagToWinampTag(key,100); + ismychange=1; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)key,(WPARAM)value); + ismychange=0; + } + else if(HIWORD(wParam) == EN_KILLFOCUS && sel>=0) { + wchar_t key[100] = {0}; + wchar_t value[1024] = {0}; + GetDlgItemTextW(hwndDlg,IDC_NAME,key,100); + GetDlgItemTextW(hwndDlg,IDC_VALUE,value,1024); + Info *info = (Info *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + wchar_t oldkey[100]=L""; + bool newitem=true; + if(sel < info->GetNumMetadataItems()) { + info->EnumMetadata(sel,oldkey,100,0,0); + newitem=false; + } + + if(!newitem && wcscmp(oldkey,key)) { // key changed + info->SetTag(sel,key); + } else { + info->SetMetadata(key,value); + } + VorbisTagToWinampTag(key,100); + ismychange=1; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)key,(WPARAM)value); + ismychange=0; + } + break; + case IDC_BUTTON_DEL: + if(sel >= 0) { + wchar_t tag[100] = {0}; + GetDlgItemTextW(hwndDlg,IDC_NAME,tag,100); + SetDlgItemTextW(hwndDlg,IDC_NAME,L""); + SetDlgItemTextW(hwndDlg,IDC_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),FALSE); + Info *info = (Info *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if(sel < info->GetNumMetadataItems()) + info->RemoveMetadata(sel); + ListView_DeleteItem(GetDlgItem(hwndDlg,IDC_LIST),sel); + sel=-1; + VorbisTagToWinampTag(tag,100); + ismychange=1; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)tag,(WPARAM)L""); + ismychange=0; + } + break; + case IDC_BUTTON_DELALL: + ListView_DeleteAllItems(GetDlgItem(hwndDlg,IDC_LIST)); + SetDlgItemTextW(hwndDlg,IDC_NAME,L""); + SetDlgItemTextW(hwndDlg,IDC_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),FALSE); + sel=-1; + { + Info *info = (Info *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + int n = info->GetNumMetadataItems(); + while(n>0) { + --n; + wchar_t tag[100] = {0}; + info->EnumMetadata(n,tag,100,0,0); + VorbisTagToWinampTag(tag,100); + ismychange=1; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)tag,(WPARAM)L""); + ismychange=0; + info->RemoveMetadata(n); + } + } + break; + case IDC_BUTTON_ADD: + { + HWND hwndlist = GetDlgItem(hwndDlg,IDC_LIST); + LVITEMW lvi={0,0x7FFFFFF0,0}; + int n = (int)SendMessage(hwndlist,LVM_INSERTITEMW,0,(LPARAM)&lvi); + ListView_SetItemState(hwndlist,n,LVIS_SELECTED,LVIS_SELECTED); + } + break; + } + break; + } + return 0; +} + +extern "C" +{ + // return 1 if you want winamp to show it's own file info dialogue, 0 if you want to show your own (via In_Module.InfoBox) + // if returning 1, remember to implement winampGetExtendedFileInfo("formatinformation")! + __declspec(dllexport) int winampUseUnifiedFileInfoDlg(const wchar_t * fn) + { + if (PathIsURLW(fn)) + return 0; + return 1; + } + + // should return a child window of 513x271 pixels (341x164 in msvc dlg units), or return NULL for no tab. + // Fill in name (a buffer of namelen characters), this is the title of the tab (defaults to "Advanced"). + // filename will be valid for the life of your window. n is the tab number. This function will first be + // called with n == 0, then n == 1 and so on until you return NULL (so you can add as many tabs as you like). + // The window you return will recieve WM_COMMAND, IDOK/IDCANCEL messages when the user clicks OK or Cancel. + // when the user edits a field which is duplicated in another pane, do a SendMessage(GetParent(hwnd),WM_USER,(WPARAM)L"fieldname",(LPARAM)L"newvalue"); + // this will be broadcast to all panes (including yours) as a WM_USER. + __declspec(dllexport) HWND winampAddUnifiedFileInfoPane(int n, const wchar_t * filename, HWND parent, wchar_t *name, size_t namelen) + { + if(n == 0) { // add first pane + SetPropW(parent,L"INBUILT_NOWRITEINFO", (HANDLE)1); + Info *info = new Info(filename); + if(info->Error()) + { + delete info; + return NULL; + } + return WASABI_API_CREATEDIALOGPARAMW(IDD_INFO,parent,ChildProc_Advanced,(LPARAM)info); + } + return NULL; + } +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/localfile.cpp b/Src/Plugins/Input/in_vorbis/localfile.cpp new file mode 100644 index 00000000..7f163f7d --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/localfile.cpp @@ -0,0 +1,260 @@ +#include "main.h" +#include "../nu/AutoChar.h" + +extern CfgInt cfg_fullbuf; + +int VorbisFile::_f_close(void *) {return 0;} + +int VorbisFile::_f_seek(void* rs,__int64 offset,int whence) +{ + return ((VorbisFile*)rs)->f_seek(offset,whence); +} + +size_t VorbisFile::_f_read(void* ptr,size_t size,size_t nmemb,void * rs) +{ + return ((VorbisFile*)rs)->f_read((UINT)(size*nmemb),ptr); +} + +long VorbisFile::_f_tell(void* rs) +{ + return ((VorbisFile*)rs)->f_tell(); +} + +ov_callbacks VorbisFile::oc={_f_read,_f_seek,_f_close,_f_tell}; + +static __int64 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; +} + +static __int64 FileSize64(HANDLE file) +{ + LARGE_INTEGER position; + position.QuadPart=0; + position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart); + + if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) + return INVALID_FILE_SIZE; + else + return position.QuadPart; +} + +class VorbisFile_Local : public VorbisFile +{ +private: + HANDLE hFile; +protected: + int f_seek(__int64 offset,int whence) + { + if(whence==SEEK_SET) offset+=baseoffs; + if (Seek64(hFile,offset,whence) != INVALID_SET_FILE_POINTER) return 0; + else return -1; + } + + size_t f_read(UINT siz,void * ptr) + { + DWORD bw=0; + ReadFile(hFile,ptr,siz,&bw,0); + return bw; + } + + UINT f_tell() + { + return (UINT)(SetFilePointer(hFile,0,0,FILE_CURRENT)-baseoffs); + } + + UINT FileSize() + { + return (UINT)(FileSize64(hFile)-baseoffs); + } + +public: + virtual int GetType() {return TYPE_LOCAL;} + VorbisFile_Local(HANDLE f,const wchar_t * u,bool is_info) : VorbisFile(u,is_info) {hFile=f;} + ~VorbisFile_Local() {CloseHandle(hFile);} +}; + +class VorbisFile_Mem : public VorbisFile +{ + BYTE * block; + UINT size,ptr; +protected: + int f_seek(__int64 offset,int whence) + { + switch(whence) + { + case SEEK_SET: + ptr=(UINT)(offset+baseoffs); + break; + case SEEK_CUR: + ptr+=(UINT)offset; + break; + case SEEK_END: + ptr=size+whence; + break; + } + if (ptr<=size) return 0; + else {ptr=size;return -1;} + } + + size_t f_read(UINT siz,void * out) + { + UINT d=size-ptr; + if (d>siz) d=siz; + memcpy(out,block+ptr,d); + ptr+=d; + return d; + } + + UINT f_tell() + { + return (UINT)(ptr-baseoffs); + } + + UINT FileSize() + { + return (UINT)(size-baseoffs); + } + +public: + virtual int GetType() {return TYPE_LOCAL;} + + VorbisFile_Mem(HANDLE f,const wchar_t * u,bool is_info) : VorbisFile(u,is_info) + { + size=GetFileSize(f,0); + ptr=0; + block=(BYTE*)malloc(size); + DWORD br = 0; + ReadFile(f,block,size,&br,0); + CloseHandle(f); + } + + ~VorbisFile_Mem() {free(block);} +}; + +VorbisFile * VorbisFile::Create(const wchar_t *url, bool is_info) +{ + VorbisFile * r; + if (PathIsURLW(url)) + { + if (is_info) return 0; + r=Create_HTTP(AutoChar(url),is_info); + } + else + { + __int64 baseoffs=0; + HANDLE f=CreateFileW(url,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,0,OPEN_EXISTING,0,0); + if (f==INVALID_HANDLE_VALUE) return 0; + { + DWORD dw = 0, br = 0; + ReadFile(f,&dw,4,&br,0); + if(br==4 && dw=='SggO') + { + SetFilePointer(f,0,0,FILE_BEGIN); + } + else if(br==4 && dw=='FFIR') + { + //RIFF file + DWORD wavhdr = 0, nb = 0; + SetFilePointer(f,4,0,FILE_CURRENT); + ReadFile(f,&wavhdr,4,&nb,0); + if(nb!=4 || wavhdr!='EVAW') + { + goto abort; + } + + //find data starting point + char tmp[1024] = {0}; + ReadFile(f,&tmp,1024,&nb,0); + for(int i=0;i<1020;i++) + if(tmp[i]=='d'&&tmp[i+1]=='a'&&tmp[i+2]=='t'&&tmp[i+3]=='a') + { + baseoffs=i+12+8; + Seek64(f, baseoffs, FILE_BEGIN); + } + + if(!baseoffs) goto abort; + } + else + { + abort: + CloseHandle(f); + return 0; + } + } + + r=cfg_fullbuf ? (VorbisFile*)new VorbisFile_Mem(f,url,is_info) : (VorbisFile*)new VorbisFile_Local(f,url,is_info); + r->setBaseOffset(baseoffs); + } + if (r && !r->init()) + { + delete r; + r=0; + } + return r; +} + +bool VorbisFile::init() +{ + if (ov_open_callbacks(this,&vf,0,0,oc)) return 0; + //TODO bitrate + UINT siz=FileSize(); + double len=Length(); + if (siz>0 && len>0) + { + UINT divisor = (UINT)(len*125.0); + if (divisor) + avg_kbps=siz/divisor; + } + + post_init(); + return 1; +} + +int is_http(const char* url) +{ + return (!_strnicmp(url,"http://",7) || !_strnicmp(url,"https://",8)); +} + +void VorbisFile::set_meta(const vorbis_comment * vc,int links) +{ + if (links == vf.links) + { + int n; + for(n=0;n<links;n++) + { + vorbis_comment_clear(vf.vc+n); + /* + extern void vorbis_comment_init(vorbis_comment *vc); + extern void vorbis_comment_add(vorbis_comment *vc, char *comment); + extern void vorbis_comment_add_tag(vorbis_comment *vc,char *tag, char *contents); + extern char *vorbis_comment_query(vorbis_comment *vc, char *tag, int count); + extern int vorbis_comment_query_count(vorbis_comment *vc, char *tag); + extern void vorbis_comment_clear(vorbis_comment *vc); + */ + } + _ogg_free(vf.vc); + vf.vc = (vorbis_comment*) _ogg_calloc(links,sizeof(vorbis_comment)); + for(n=0;n<links;n++) + { + vorbis_comment_init(vf.vc+n); + int c; + for(c=0;c<vc[n].comments;c++) + { + vorbis_comment_add(vf.vc+n,vc[n].user_comments[c]); + } + vf.vc[n].vendor = _strdup(vc[n].vendor); + } + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/main.h b/Src/Plugins/Input/in_vorbis/main.h new file mode 100644 index 00000000..79c09dae --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/main.h @@ -0,0 +1,220 @@ +#ifndef IN_VORBIS_MAIN_H +#define IN_VORBIS_MAIN_H + +#define WINSOCK_API_LINKAGE + +#ifndef STRICT +#define STRICT +#endif + +#include <windows.h> + +extern int (*warand)(); +extern float (*warandf)(); + +inline void * z_malloc(int x) +{ + void* foo=malloc(x); + if (foo) memset(foo,0,x); + return foo; +} +#include <shlwapi.h> +#include <malloc.h> +#define uitoa(x,y) _itoa(x,y,10) +#define atoui atoi + +#include <vorbis\vorbisfile.h> +#include "c_string.h" +#include "../Winamp/in2.h" + +extern In_Module mod; + +#include "resource.h" + +#define VER L"1.79" +#define _NAME "Nullsoft Vorbis Decoder" + +extern "C" +{ + extern const char *INI_FILE; + extern const wchar_t *INI_DIRECTORY; +} +class CfgVar +{ +private: + String name; + CfgVar * next; + static CfgVar * list; +public: + + static void ReadConfig(); + static void WriteConfig(); + + //helpers + static bool read_struct(const char *inifile, const char *section, const char * name,void * ptr,UINT size); + static void write_struct(const char *inifile, const char *section, const char * name,void * ptr,UINT size); + static void write_int(const char *inifile, const char *section, const char * name,int val); + static int read_int(const char *inifile, const char *section,const char * name,int def); + +protected: + CfgVar(const char * n) : name(n) {next=list;list=this;} + virtual void Read(const char * name)=0; + virtual void Write(const char * name)=0; +}; + +class CfgInt : private CfgVar +{ +private: + int def,value; +public: + CfgInt(const char * name,int _def) : CfgVar(name) {value=def=_def;} + inline int operator=(int x) {value=x;return value;} + inline operator int() {return value;} +private: + virtual void Read(const char * name); + virtual void Write(const char * name); +}; + +class CfgString : private CfgVar, public StringW +{ +private: + StringW def; +public: + CfgString(const char * name,const char * _def) : CfgVar(name), StringW(_def), def(_def) {} +private: + virtual void Read(const char * name); + virtual void Write(const char * name); +}; + +template<class T> +class CfgStructT : private CfgVar +{ +public: + T data; + CfgStructT(const char * name) : CfgVar(name) {} +private: + void Read(const char * name) { read_struct(INI_FILE, "in_vorbis",name,&data,sizeof(data));} + void Write(const char * name) {if (IsValueDefault()) WritePrivateProfileStringA("in_vorbis", name, 0, INI_FILE); else write_struct(INI_FILE, "in_vorbis", name, &data, sizeof(data));} +protected: + virtual bool IsValueDefault() {return 0;} +}; + + +class CfgFont : public CfgStructT<LOGFONT> +{ +private: + void get_def(LOGFONT * f) {memset(f,0,sizeof(LOGFONT));GetObject(GetStockObject(DEFAULT_GUI_FONT),sizeof(LOGFONT),f);} + virtual bool IsValueDefault() + { + LOGFONT t; + get_def(&t); + return !memcmp(&data,&t,sizeof(LOGFONT)); + } +public: + CfgFont(const char * name) : CfgStructT<LOGFONT>(name) + { + get_def(&data); + } +}; + +extern int32_t priority_tab[7]; +extern HINSTANCE hIns; + +extern CfgString cfg_ssave_format,cfg_dumpdir; + +int is_http(const char* url); + +class VorbisFile +{ +protected: + virtual int f_seek(__int64 offset,int whence)=0; + virtual size_t f_read(UINT siz,void * ptr)=0; + virtual UINT f_tell()=0; + static int _f_close(void *); + static int _f_seek(void* rs,__int64 offset,int whence); + static size_t _f_read(void* ptr,size_t size,size_t nmemb,void * rs); + static long _f_tell(void* rs); + static ov_callbacks oc; + static VorbisFile * Create_HTTP(const char * url,bool is_info); + VorbisFile(const wchar_t * u, bool is_info) : url(u) {memset(&vf,0,sizeof(vf));stopping=0;abort_prebuf=0;avg_kbps=0;use_prebuf=0;primary=!is_info; baseoffs=0;} + bool init(); + virtual void post_init() {}; + UINT avg_kbps; + bool Aborting(); + + __int64 baseoffs; +public: + enum {TYPE_LOCAL,TYPE_HTTP}; + virtual int GetType()=0; + virtual bool IsLive() {return 0;} + virtual void do_prebuf() {use_prebuf=1;abort_prebuf=0;}; + StringW url; + String withlp; + String stream_title; + bool stopping,abort_prebuf,use_prebuf; + bool primary;//display status messages or not + OggVorbis_File vf; + UINT get_avg_bitrate() + { + if (avg_kbps>0) return avg_kbps; + vorbis_info * vi=ov_info(&vf,-1); + if (!vi) return 0; + return vi->bitrate_nominal/1000; + } + + const char* get_meta(const char* tag,UINT c); + void set_meta(const vorbis_comment * vc,int links); + + static VorbisFile * Create(const wchar_t * url,bool is_info); + + double Length() {return ov_time_total(&vf,-1);} + double GetPos() {return ov_time_tell(&vf);} + int Seek(double p); + void Status(const wchar_t * zzz); + virtual UINT FileSize()=0; + + virtual ~VorbisFile() {ov_clear(&vf);} + virtual void Idle() {Sleep(10);} + + virtual void setBaseOffset(__int64 offs) { baseoffs=offs; } + + float GetGain(); +}; + +extern VorbisFile * theFile; + +extern StringW cur_file; + +extern CRITICAL_SECTION sync; + +BOOL modify_file(const wchar_t* url,const vorbis_comment * comments,int links); +void winampGetExtendedFileInfoW_Cleanup(void); +void UpdateFileTimeChanged(const wchar_t *fn); +void do_cfg(int s); +bool KeywordMatch(const char *mainString, const char *keyword); + +class Info +{ +public: + Info(const wchar_t *filename); + ~Info(); + bool Save(); + int Error() { return vc==0?1:0; } + int GetNumMetadataItems(); + void EnumMetadata(int n,wchar_t *key,int keylen, wchar_t *val, int vallen); + void RemoveMetadata(wchar_t * key); + void RemoveMetadata(int n); + void SetMetadata(wchar_t *key, wchar_t *val); + void SetMetadata(int n, wchar_t *key, wchar_t *val); + void SetTag(int n,wchar_t *key); // changes the key name +private: + const wchar_t *filename; + vorbis_comment * vc; + int numstreams, stream; +}; + +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = + { 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } }; + +#endif //IN_VORBIS_MAIN_H
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/mkv_vorbis_decoder.cpp b/Src/Plugins/Input/in_vorbis/mkv_vorbis_decoder.cpp new file mode 100644 index 00000000..39f1c1c3 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/mkv_vorbis_decoder.cpp @@ -0,0 +1,194 @@ +#include "mkv_vorbis_decoder.h" +#include "../nsmkv/Lacing.h" +#include "../nsmkv/Cluster.h" +#include <math.h> + + +int MKVDecoderCreator::CreateAudioDecoder(const char *codec_id, + const nsmkv::TrackEntryData *track_entry_data, const nsmkv::AudioData *audio_data, + unsigned int preferred_bits, unsigned int max_channels, bool floating_point, + ifc_mkvaudiodecoder **decoder) +{ + if (!strcmp(codec_id, "A_VORBIS")) + { + MKVVorbis *vorbis = new MKVVorbis; + vorbis_info_init(&vorbis->info); + vorbis_comment_init(&vorbis->comment); + nsmkv::LacingState lacing_state; + if (nsmkv::Lacing::GetState(nsmkv::BlockBinary::XIPH_LACING, (const uint8_t *)track_entry_data->codec_private, track_entry_data->codec_private_len, &lacing_state)) + { + const uint8_t *frame; + size_t frame_len; + uint16_t frame_number=0; + while (nsmkv::Lacing::GetFrame(frame_number, (const uint8_t *)track_entry_data->codec_private, track_entry_data->codec_private_len, &frame, &frame_len, &lacing_state)) + { + ogg_packet packet = {const_cast<uint8_t *>(frame), (long)frame_len, (frame_number==0), 0, 0 /*-1?*/, vorbis->packet_number++}; + int ret = vorbis_synthesis_headerin(&vorbis->info, &vorbis->comment, &packet); + if (ret != 0) + goto bail; + frame_number++; + } + if (vorbis_synthesis_init(&vorbis->dsp, &vorbis->info) == 0 + && vorbis_block_init(&vorbis->dsp, &vorbis->block) == 0) + { + vorbis->bps = preferred_bits?preferred_bits:16; + *decoder = vorbis; + return CREATEDECODER_SUCCESS; + } + } + +bail: + delete vorbis; + return CREATEDECODER_FAILURE; + } + + return CREATEDECODER_NOT_MINE; +} + + +#define CBCLASS MKVDecoderCreator +START_DISPATCH; +CB(CREATE_AUDIO_DECODER, CreateAudioDecoder) +END_DISPATCH; +#undef CBCLASS + +MKVVorbis::MKVVorbis() +{ + bps=16; + packet_number=0; +} + +#define PA_CLIP_( val, min, max )\ + { val = ((val) < (min)) ? (min) : (((val) > (max)) ? (max) : (val)); } + +#if defined(_M_IX86) +static __inline long float_to_long(double t) +{ + long r; + __asm fld t + __asm fistp r + return r; +} +#else +#define float_to_long(x) ((long)( x )) +#endif + +inline static void clip(double &x, double a, double b) +{ + double x1 = fabs (x - a); + double x2 = fabs (x - b); + x = x1 + (a + b); + x -= x2; + x *= 0.5; +} + +static void Float32_To_Int24_Clip(void *destinationBuffer, void *sourceBuffer, size_t count, size_t channels, double gain) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + gain*=65536.*32768.; + while ( count-- ) + { + /* convert to 32 bit and drop the low 8 bits */ + double scaled = *src * gain; + clip( scaled, -2147483648., 2147483647.); + signed long temp = (signed long) scaled; + + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 24); + + src++; + dest += 3*channels; + } +} + +static void Float32_To_Int16_Clip(void *destinationBuffer, void *sourceBuffer, size_t count, size_t channels, double gain) +{ + float *src = (float*)sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + + gain*=32768.0; + while ( count-- ) + { + long samp = float_to_long((*src) * gain/* - 0.5*/); + + PA_CLIP_( samp, -0x8000, 0x7FFF ); + *dest = (signed short) samp; + + src ++; + dest += channels; + } +} + +int MKVVorbis::DecodeBlock(void *inputBuffer, size_t inputBufferBytes, void *outputBuffer, size_t *outputBufferBytes) +{ + uint8_t *out = (uint8_t *)outputBuffer; + ogg_packet packet = {(uint8_t *)inputBuffer, (long)inputBufferBytes, 0, 0, 0 -1, packet_number++}; + int ret = vorbis_synthesis(&block, &packet); + if (ret == 0) + { + vorbis_synthesis_blockin(&dsp,&block); + long channels = info.channels; + float **pcm; + int samples = vorbis_synthesis_pcmout(&dsp, &pcm); + if (samples) + { + switch(bps) + { + case 16: + for(int i=0;i<channels;i++) + { + Float32_To_Int16_Clip(out, pcm[i], samples, channels, 1.0 /*gain*/); + out+=2; + } + break; + case 24: + for(int i=0;i<channels;i++) + { + Float32_To_Int24_Clip(out, pcm[i], samples, channels, 1.0 /*gain*/); + out+=3; + } + break; + } + } + *outputBufferBytes = samples*channels*bps/8; + // let the decoder know we're processed them + vorbis_synthesis_read(&dsp,samples); + return MKV_SUCCESS; + } + return MKV_FAILURE; +} + +int MKVVorbis::GetOutputProperties(unsigned int *sampleRate, unsigned int *channels, unsigned int *bitsPerSample, bool *isFloat) +{ + *sampleRate = info.rate; + *channels = info.channels; + *bitsPerSample = bps; + *isFloat = false; // TODO + return MKV_SUCCESS; +} + +void MKVVorbis::Flush() +{ + vorbis_synthesis_restart(&dsp); +} + +void MKVVorbis::Close() +{ + // TODO: benski> verify + vorbis_info_clear(&info); + vorbis_comment_clear(&comment); + vorbis_dsp_clear(&dsp); + vorbis_block_clear(&block); + delete this; +} +#define CBCLASS MKVVorbis +START_DISPATCH; +//CB(OUTPUT_FRAME_SIZE, OutputFrameSize) +CB(GET_OUTPUT_PROPERTIES, GetOutputProperties) +CB(DECODE_BLOCK, DecodeBlock) +VCB(FLUSH, Flush) +VCB(CLOSE, Close) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/mkv_vorbis_decoder.h b/Src/Plugins/Input/in_vorbis/mkv_vorbis_decoder.h new file mode 100644 index 00000000..0893ab25 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/mkv_vorbis_decoder.h @@ -0,0 +1,41 @@ +#pragma once +#include "../in_mkv/ifc_mkvaudiodecoder.h" +#include "../in_mkv/svc_mkvdecoder.h" +#include <vorbis/codec.h> + +// {6058D315-2F08-4b2f-903E-4C2E6B5EFFA9} +static const GUID mkv_vorbis_guid = +{ 0x6058d315, 0x2f08, 0x4b2f, { 0x90, 0x3e, 0x4c, 0x2e, 0x6b, 0x5e, 0xff, 0xa9 } }; + + +class MKVDecoderCreator : public svc_mkvdecoder +{ +public: + static const char *getServiceName() { return "Vorbis MKV Decoder"; } + static GUID getServiceGuid() { return mkv_vorbis_guid; } + int CreateAudioDecoder(const char *codec_id, + const nsmkv::TrackEntryData *track_entry_data, const nsmkv::AudioData *audio_data, + unsigned int preferred_bits, unsigned int max_channels, bool floating_point, + ifc_mkvaudiodecoder **decoder); +protected: + RECVS_DISPATCH; +}; + +class MKVVorbis : public ifc_mkvaudiodecoder +{ +public: + MKVVorbis(); + int DecodeBlock(void *inputBuffer, size_t inputBufferBytes, void *outputBuffer, size_t *outputBufferBytes); + int GetOutputProperties(unsigned int *sampleRate, unsigned int *channels, unsigned int *bitsPerSample, bool *isFloat); + void Flush(); + void Close(); +//private: + unsigned int bps; + vorbis_info info; + vorbis_dsp_state dsp; + vorbis_block block; + vorbis_comment comment; + ogg_int64_t packet_number; +protected: + RECVS_DISPATCH; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/oggdrop/112.png b/Src/Plugins/Input/in_vorbis/oggdrop/112.png Binary files differnew file mode 100644 index 00000000..4799bd0f --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/oggdrop/112.png diff --git a/Src/Plugins/Input/in_vorbis/oggdrop/113.png b/Src/Plugins/Input/in_vorbis/oggdrop/113.png Binary files differnew file mode 100644 index 00000000..4cd9b4ac --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/oggdrop/113.png diff --git a/Src/Plugins/Input/in_vorbis/oggdrop/114.png b/Src/Plugins/Input/in_vorbis/oggdrop/114.png Binary files differnew file mode 100644 index 00000000..88192596 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/oggdrop/114.png diff --git a/Src/Plugins/Input/in_vorbis/oggdrop/115.png b/Src/Plugins/Input/in_vorbis/oggdrop/115.png Binary files differnew file mode 100644 index 00000000..60c94b0b --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/oggdrop/115.png diff --git a/Src/Plugins/Input/in_vorbis/oggdrop/116.png b/Src/Plugins/Input/in_vorbis/oggdrop/116.png Binary files differnew file mode 100644 index 00000000..1d6fb8a2 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/oggdrop/116.png diff --git a/Src/Plugins/Input/in_vorbis/oggdrop/117.png b/Src/Plugins/Input/in_vorbis/oggdrop/117.png Binary files differnew file mode 100644 index 00000000..135963e7 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/oggdrop/117.png diff --git a/Src/Plugins/Input/in_vorbis/oggdrop/118.png b/Src/Plugins/Input/in_vorbis/oggdrop/118.png Binary files differnew file mode 100644 index 00000000..57a3a1ac --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/oggdrop/118.png diff --git a/Src/Plugins/Input/in_vorbis/oggdrop/119.png b/Src/Plugins/Input/in_vorbis/oggdrop/119.png Binary files differnew file mode 100644 index 00000000..a3baf233 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/oggdrop/119.png diff --git a/Src/Plugins/Input/in_vorbis/oggdrop/120.png b/Src/Plugins/Input/in_vorbis/oggdrop/120.png Binary files differnew file mode 100644 index 00000000..fb6a3ce9 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/oggdrop/120.png diff --git a/Src/Plugins/Input/in_vorbis/oggdrop/121.png b/Src/Plugins/Input/in_vorbis/oggdrop/121.png Binary files differnew file mode 100644 index 00000000..23ff9813 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/oggdrop/121.png diff --git a/Src/Plugins/Input/in_vorbis/oggdrop/122.png b/Src/Plugins/Input/in_vorbis/oggdrop/122.png Binary files differnew file mode 100644 index 00000000..6b52d9e2 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/oggdrop/122.png diff --git a/Src/Plugins/Input/in_vorbis/oggdrop/123.png b/Src/Plugins/Input/in_vorbis/oggdrop/123.png Binary files differnew file mode 100644 index 00000000..60284d13 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/oggdrop/123.png diff --git a/Src/Plugins/Input/in_vorbis/resource.h b/Src/Plugins/Input/in_vorbis/resource.h new file mode 100644 index 00000000..425c40ea --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/resource.h @@ -0,0 +1,177 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ogg.rc +// +#define IDS_NULLSOFT_VORBIS_DECODER_OLD 0 +#define IDS_PLEASE_ENTER_TAG_NAME 1 +#define IDS_TAG_NAME_CONTAINS_INVALID_CHARS 2 +#define IDOKAUTH 3 +#define IDS_ERASE_ALL_FIELDS_ON_LIST 3 +#define IDCANCELAUTH 4 +#define IDS_WARNING 4 +#define IDS_FILE_ERROR 5 +#define IDS_LENGTH 6 +#define IDS_AVERAGE_BITRATE 7 +#define IDS_FILE_SIZE 8 +#define IDS_NOMINAL_BITRATE 9 +#define IDS_MIN_BITRATE 10 +#define IDS_MAX_BITRATE 11 +#define IDS_CHANNELS 12 +#define IDS_SAMPLING_RATE 13 +#define IDS_SERIAL_NUMBER 14 +#define IDS_VERSION 15 +#define IDS_Vendor 16 +#define IDS_VENDOR 16 +#define IDS_TO_SIMPLE_MODE 17 +#define IDS_TO_ADVANCED_MODE 18 +#define IDS_OGG_VORBIS_INFO 19 +#define IDS_WRITE_ERROR 20 +#define IDS_BEST_RPM 21 +#define IDS_ABOUT 22 +#define IDS_LEAVE_AS_IS 24 +#define IDS_REMAP_6_CHANNELS 25 +#define IDS_DOWNMIX_TO_4_CHANNELS 26 +#define IDS_DOWNMIX_TO_2_CHANNELS_DS 27 +#define IDS_DOWNMIX_TO_2_CHANNELS_DS2 28 +#define IDS_DOWNMIX_TO_MONO 29 +#define IDS_CORRECT_FL_FC_FR_BL_BR_LFE 30 +#define IDS_BROKEN_FL_FR_FC_BL_BR_LFE 31 +#define IDS_IDLE 32 +#define IDS_ABOUT_TEXT 32 +#define IDS_LOWEST 33 +#define IDS_BELOW_NORMAL 34 +#define IDS_NORMAL 35 +#define IDS_ABOVE_NORMAL 36 +#define IDS_HIGHEST 37 +#define IDS_TIME_CRITICAL 38 +#define IDS_TITLE_PREFERENCES 39 +#define IDS_DO_NOT_ENABLE_24_BIT_OUTPUT 40 +#define IDS_ONE_TIME_FAQ_REMINDER 41 +#define IDS_RUNNING_ON_NT_OS 42 +#define IDS_RUNNING_ON_WIN9X 43 +#define IDS_NEVER 44 +#define IDS_PORT_80_ONLY 45 +#define IDS_ALWAYS 46 +#define IDS_SELECT_OUTPUT_DIRECTORY 47 +#define IDS_DECODING 48 +#define IDS_DISPLAY 49 +#define IDS_STREAMING 50 +#define IDS_CONNECTING 51 +#define IDS_PREBUFFERING 52 +#define IDS_AUTH_REQUIRED 53 +#define IDS_OGG_FILES 54 +#define IDS_NAME 55 +#define IDS_VALUE 56 +#define IDS_KBPS 57 +#define IDS_STRING2 58 +#define IDS_HZ 58 +#define IDS_GAME_SPEED 59 +#define IDS_STRING1 60 +#define IDS_BYTES 60 +#define IDS_FAMILY_STRING 61 +#define IDC_CONFIG_TAB1 101 +#define IDD_INFO_DLG 102 +#define IDC_CONFIG_TAB2 102 +#define IDC_CONFIG_TAB3 103 +#define IDD_INFO_DLG1 104 +#define IDC_CONFIG_TAB4 104 +#define IDD_ABOUT 112 +#define IDB_BITMAP1 115 +#define IDB_BITMAP2 116 +#define IDB_BITMAP3 117 +#define IDB_BITMAP4 118 +#define IDB_BITMAP5 119 +#define IDB_BITMAP6 120 +#define IDB_BITMAP7 121 +#define IDB_BITMAP8 122 +#define IDB_BITMAP9 123 +#define IDB_BITMAP10 124 +#define IDB_BITMAP11 125 +#define IDB_BITMAP12 126 +#define IDD_HTTPAUTH 128 +#define IDD_INFO 131 +#define IDD_DIALOG1 132 +#define IDD_CONFIG 132 +#define IDD_INFO_DLG_NEW 133 +#define IDD_INFO_PANEL_ADVANCED 134 +#define IDD_INFO_PANEL_SIMPLE 135 +#define IDB_PNG1 136 +#define IDB_PNG2 137 +#define IDB_PNG3 138 +#define IDB_PNG4 139 +#define IDB_PNG5 140 +#define IDB_PNG6 141 +#define IDB_PNG7 142 +#define IDB_PNG8 143 +#define IDB_PNG9 144 +#define IDB_PNG10 145 +#define IDB_PNG11 146 +#define IDB_PNG12 147 +#define IDC_LIST 1001 +#define IDC_NAME 1002 +#define IDC_TITLE 1002 +#define IDC_VALUE 1003 +#define IDC_ARTIST 1003 +#define IDC_HTTP_BSIZE 1004 +#define IDC_ALBUM 1004 +#define IDC_STREAM_SAVE 1005 +#define IDC_GENRE 1005 +#define IDC_FSAVE 1006 +#define IDC_YEAR 1006 +#define IDC_DATE 1006 +#define IDC_CUSTOM1 1007 +#define IDC_MISC 1007 +#define IDC_AVG_BR 1008 +#define IDC_FIX0R 1009 +#define IDC_PROXY 1012 +#define IDC_URL 1012 +#define IDC_SLIDER1 1013 +#define IDC_SLIDER2 1014 +#define IDC_BUTTON_ADD 1015 +#define IDC_BUTTON_DEL 1016 +#define IDC_BUTTON_DELALL 1017 +#define IDC_RPM 1018 +#define IDC_RPM2 1019 +#define IDC_MC6_DM 1020 +#define IDC_MC6_MAP 1021 +#define IDC_SSAVE_FMT 1022 +#define IDC_SSAVE_FMT_DEF 1023 +#define IDC_FULLBUF 1025 +#define IDC_EDITAUTH 1026 +#define IDC_REALM 1027 +#define IDC_TRACK 1028 +#define IDC_STATIC_MISC 1034 +#define IDC_STATIC_TAGS 1035 +#define IDC_STATIC_STD 1036 +#define IDC_STATIC_TRACK 1037 +#define IDC_SEPARATE 1038 +#define IDC_DELETE_ALL 1039 +#define IDC_RG 1040 +#define IDC_RG_MODE 1041 +#define IDC_NOCLIP 1042 +#define IDC_HARDLIMIT 1043 +#define IDC_PREAMP_STAT 1044 +#define IDC_TAB 1045 +#define IDC_MODE_TOGGLE 1053 +#define IDC_NEXT_STREAM 1054 +#define IDC_PREV_STREAM 1055 +#define IDC_STATIC_CS 1056 +#define IDC_HIDE_SPEC 1058 +#define IDC_COMMENT 1060 +#define IDC_ABOUT_TEXT 1061 +#define IDC_OS_BLAH 1062 +#define IDC_REMEMBER_INFOSIZE 1063 +#define IDC_FONTNAME 1064 +#define IDC_PREAMP_RG 1065 +#define IDS_NULLSOFT_VORBIS_DECODER 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 148 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1067 +#define _APS_NEXT_SYMED_VALUE 105 +#endif +#endif diff --git a/Src/Plugins/Input/in_vorbis/rf.h b/Src/Plugins/Input/in_vorbis/rf.h new file mode 100644 index 00000000..70bd85bb --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/rf.h @@ -0,0 +1,108 @@ +#ifndef _RF_H_ +#define _RF_H_ + +//based on Tempura specs. +//NOT compatible with WA3 alphas + +class WReader +{ + protected: + + /* WReader + ** WReader constructor + */ + WReader() : m_player(0) { } + + public: + + /* m_player + ** Filled by Winamp. Pointer to Winamp 3 core interface + */ + /*WPlayer_callback*/ void *m_player; //PP: hack. read_file.dll doesn't call it at all. simply don't touch it + + /* GetDescription + ** Retrieves your plug-in's text description + */ + virtual char *GetDescription() { return "Unknown"; }; + + /* Open + ** Used to open a file, return 0 on success + */ + virtual int Open(char *url, bool *killswitch)=0; + + /* Read + ** Returns number of BYTES read (if < length then eof or killswitch) + */ + virtual int Read(char *buffer, int length, bool *killswitch)=0; + + /* GetLength + ** Returns length of the entire file in BYTES, return -1 on unknown/infinite (as for a stream) + */ + virtual int GetLength(void)=0; + + /* CanSeek + ** Returns 1 if you can skip ahead in the file, 0 if not + */ + virtual int CanSeek(void)=0; //PP: currently available read_file.dll vesions can always seek in any direction + + /* Seek + ** Jump to a certain absolute position + */ + virtual int Seek(int position, bool *killswitch)=0; + + /* GetHeader + ** Retrieve header. Used in read_http to retrieve the HTTP header + */ + virtual char *GetHeader(char *name) { return 0; } + + /* ~WReader + ** WReader virtual destructor + */ + //virtual ~WReader() { } + virtual void Release(int) {}; + //PP: hack - shut up linker when getting rid of evil CRT library; seems to work OK under Tempura +}; + + + + +#define READ_VER 0x100 + +typedef struct +{ + /* version + ** Version revision number + */ + int version; + + /* description + ** Text description of the reader plug-in + */ + char *description; + + /* create + ** Function pointer to create a reader module + */ + WReader *(*create)(); + + /* ismine + ** Determines whether or not a file should be read by this plug-in + */ + int (*ismine)(char *url); + +} reader_source; + +//exported symbol is: +//int readerSource(HINSTANCE,reader_source**); + +/* +(not a part of Tempura specs) +int _stdcall gzip_writefile(char* path,void* buf,DWORD size) - writes a memory block to a GZIP file - in_midi calls it from file info box + +other hacks: +recent versions understand file://... urls, can do partial file access (eg. "partial://00006666-66660000:c:\foo\bar.dat\zzz.wav" (zzz.wav is the "display name" + extension to make winamp select correct plug-in) and auto-detect CD drive letter (eg. #:\x.mp3 will scan all drives for that file; also works with partial:// ) +you can (for an example) build a playlist which will play Unreal soundtrack directly from the game CD on any system +latest read_file.dll is bundled with the midi plug-in: http://www.blorp.com/~peter/zips/in_midi.zip +*/ + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/shaper.cpp b/Src/Plugins/Input/in_vorbis/shaper.cpp new file mode 100644 index 00000000..85779a87 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/shaper.cpp @@ -0,0 +1,245 @@ +#include "Shaper.h" + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795028842 +#endif + +#define RANDBUFLEN 65536 + +#define RINT(x) ((x) >= 0 ? ((int)((x) + 0.5)) : ((int)((x) - 0.5))) + +const int scoeffreq[] = + { + 0, 48000, 44100, 37800, 32000, 22050, 48000, 44100 + }; + + +const int scoeflen[] = + { + 1, 16, 20, 16, 16, 15, 16, 15 + }; + +const int samp[] = + { + 8, 18, 27, 8, 8, 8, 10, 9 + }; + +const double shapercoefs[8][21] = + { + { + -1 + } + , /* triangular dither */ + + { -2.8720729351043701172, 5.0413231849670410156, -6.2442994117736816406, 5.8483986854553222656, + -3.7067542076110839844, 1.0495119094848632812, 1.1830236911773681641, -2.1126792430877685547, + 1.9094531536102294922, -0.99913084506988525391, 0.17090806365013122559, 0.32615602016448974609, + -0.39127644896507263184, 0.26876461505889892578, -0.097676105797290802002, 0.023473845794796943665, + }, /* 48k, N=16, amp=18 */ + + { -2.6773197650909423828, 4.8308925628662109375, -6.570110321044921875, 7.4572014808654785156, + -6.7263274192810058594, 4.8481650352478027344, -2.0412089824676513672, -0.7006359100341796875, + 2.9537565708160400391, -4.0800385475158691406, 4.1845216751098632812, -3.3311812877655029297, + 2.1179926395416259766, -0.879302978515625, 0.031759146600961685181, 0.42382788658142089844, + -0.47882103919982910156, 0.35490813851356506348, -0.17496839165687561035, 0.060908168554306030273, + }, /* 44.1k, N=20, amp=27 */ + + { -1.6335992813110351562, 2.2615492343902587891, -2.4077029228210449219, 2.6341717243194580078, + -2.1440362930297851562, 1.8153258562088012695, -1.0816224813461303711, 0.70302653312683105469, + -0.15991993248462677002, -0.041549518704414367676, 0.29416576027870178223, -0.2518316805362701416, + 0.27766478061676025391, -0.15785403549671173096, 0.10165894031524658203, -0.016833892092108726501, + }, /* 37.8k, N=16 */ + + { -0.82901298999786376953, 0.98922657966613769531, -0.59825712442398071289, 1.0028809309005737305, + -0.59938216209411621094, 0.79502451419830322266, -0.42723315954208374023, 0.54492527246475219727, + -0.30792605876922607422, 0.36871799826622009277, -0.18792048096656799316, 0.2261127084493637085, + -0.10573341697454452515, 0.11435490846633911133, -0.038800679147243499756, 0.040842197835445404053, + }, /* 32k, N=16 */ + + { -0.065229974687099456787, 0.54981261491775512695, 0.40278548002243041992, 0.31783768534660339355, + 0.28201797604560852051, 0.16985194385051727295, 0.15433363616466522217, 0.12507140636444091797, + 0.08903945237398147583, 0.064410120248794555664, 0.047146003693342208862, 0.032805237919092178345, + 0.028495194390416145325, 0.011695005930960178375, 0.011831838637590408325, + }, /* 22.05k, N=15 */ + + { -2.3925774097442626953, 3.4350297451019287109, -3.1853709220886230469, 1.8117271661758422852, + 0.20124770700931549072, -1.4759907722473144531, 1.7210904359817504883, -0.97746700048446655273, + 0.13790138065814971924, 0.38185903429985046387, -0.27421241998672485352, -0.066584214568138122559, + 0.35223302245140075684, -0.37672343850135803223, 0.23964276909828186035, -0.068674825131893157959, + }, /* 48k, N=16, amp=10 */ + + { -2.0833916664123535156, 3.0418450832366943359, -3.2047898769378662109, 2.7571926116943359375, + -1.4978630542755126953, 0.3427594602108001709, 0.71733748912811279297, -1.0737057924270629883, + 1.0225815773010253906, -0.56649994850158691406, 0.20968692004680633545, 0.065378531813621520996, + -0.10322438180446624756, 0.067442022264003753662, 0.00495197344571352005, + }, /* 44.1k, N=15, amp=9 */ + +#if 0 + { -3.0259189605712890625, 6.0268716812133789062, -9.195003509521484375, 11.824929237365722656, + -12.767142295837402344, 11.917946815490722656, -9.1739168167114257812, 5.3712320327758789062, + -1.1393624544143676758, -2.4484779834747314453, 4.9719839096069335938, -6.0392003059387207031, + 5.9359521865844726562, -4.903278350830078125, 3.5527443885803222656, -2.1909697055816650391, + 1.1672389507293701172, -0.4903914332389831543, 0.16519790887832641602, -0.023217858746647834778, + }, /* 44.1k, N=20 */ +#endif + }; + +#define POOLSIZE 97 + +Shaper::Shaper(int freq, int _nch, int min, int max, int _dtype, int pdf, double noiseamp) +{ + int i; + float pool[POOLSIZE] = {0}; + + nch = _nch; + dtype = _dtype; + + for (i = 1;i < 6;i++) if (freq == scoeffreq[i]) break; + /* if ((dtype == 3 || dtype == 4) && i == 6) { + fprintf(stderr,"Warning: ATH based noise shaping for destination frequency %dHz is not available, using triangular dither\n",freq); + }*/ + if (dtype == 2 || i == 6) i = 0; + if (dtype == 4 && (i == 1 || i == 2)) i += 5; + + shaper_type = i; + + shapebuf = (double**)malloc(sizeof(double *) * nch); + shaper_len = scoeflen[shaper_type]; + + for (i = 0;i < nch;i++) + shapebuf[i] = (double*)calloc(shaper_len, sizeof(double)); + + shaper_clipmin = min; + shaper_clipmax = max; + + randbuf = (REAL*)malloc(sizeof(REAL) * RANDBUFLEN); + + for (i = 0;i < POOLSIZE;i++) pool[i] = warandf(); + + switch (pdf) + { + case DITHER_RECTANGLE: // rectangular + for (i = 0;i < RANDBUFLEN;i++) + { + float r; + int p; + + p = warand() % POOLSIZE; + r = pool[p]; pool[p] = warandf(); + randbuf[i] = (REAL)(noiseamp * (((double)r) - 0.5)); + } + break; + + case DITHER_TRIANGLE: + for (i = 0;i < RANDBUFLEN;i++) + { + float r1, r2; + int p; + + p = warand() % POOLSIZE; + r1 = pool[p]; pool[p] = warandf(); + p = warand() % POOLSIZE; + r2 = pool[p]; pool[p] = warandf(); + randbuf[i] = (REAL)(noiseamp * ((((double)r1)) - (((double)r2)))); + } + break; +#if 0 + case DITHER_GAUSSIAN: // gaussian + for (i = 0;i < RANDBUFLEN;i++) + { + int sw = 0; + double t, u; + double r; + int p; + + if (sw == 0) + { + sw = 1; + + p = warand() % POOLSIZE; + r = ((double)pool[p]); pool[p] = warandf(); + + t = sqrt(-2 * log(1 - r)); + + p = warand() % POOLSIZE; + r = ((double)pool[p]); pool[p] = warandf(); + + u = 2 * M_PI * r; + + randbuf[i] = noiseamp * t * cos(u); + } + else + { + sw = 0; + + randbuf[i] = noiseamp * t * sin(u); + } + } + break; +#endif + } + + randptr = 0; + +// if (dtype == 0 || dtype == 1) return 1; + //return samp[shaper_type]; +} + +Shaper::~Shaper() +{ + int i; + + for (i = 0;i < nch;i++) free(shapebuf[i]); + free(shapebuf); + free(randbuf); +} + +int Shaper::do_shaping(double s,/*double *peak,*/int ch) +{ + double u, h; + int i; + + if (dtype == 1) + { + s += randbuf[randptr++ & (RANDBUFLEN-1)]; + + if (s < shaper_clipmin) + { + //double d = (double)s / shaper_clipmin; + //*peak = *peak < d ? d : *peak; + s = shaper_clipmin; + } + if (s > shaper_clipmax) + { + //double d = (double)s / shaper_clipmax; + //*peak = *peak < d ? d : *peak; + s = shaper_clipmax; + } + + return RINT(s); + } + + h = 0; + for (i = 0;i < shaper_len;i++) + h += shapercoefs[shaper_type][i] * shapebuf[ch][i]; + s += h; + u = s; + s += randbuf[randptr++ & (RANDBUFLEN-1)]; + if (s < shaper_clipmin) + { + //double d = (double)s / shaper_clipmin; + //*peak = *peak < d ? d : *peak; + s = shaper_clipmin; + } + if (s > shaper_clipmax) + { + //double d = (double)s / shaper_clipmax; + //*peak = *peak < d ? d : *peak; + s = shaper_clipmax; + } + s = RINT(s); + for (i = shaper_len - 2;i >= 0;i--) shapebuf[ch][i+1] = shapebuf[ch][i]; + shapebuf[ch][0] = s - u; + + return (int)s; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/shaper.h b/Src/Plugins/Input/in_vorbis/shaper.h new file mode 100644 index 00000000..13da7fd2 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/shaper.h @@ -0,0 +1,30 @@ +//from SSRC +#ifndef NULLSOFT_VORBIS_SHAPER_H +#define NULLSOFT_VORBIS_SHAPER_H +#include "main.h" + +typedef float REAL; +enum +{ + DITHER_RECTANGLE=0, + DITHER_TRIANGLE=1, + DITHER_GAUSSIAN=2, +}; +class Shaper +{ + double **shapebuf; + int shaper_type,shaper_len,shaper_clipmin,shaper_clipmax; + REAL *randbuf; + int randptr; + int dtype; + int nch; + + public: + Shaper(int freq,int _nch,int min,int max,int _dtype,int pdf,double noiseamp); + + int do_shaping(double s,/*double *peak,*/int ch); + + ~Shaper(); +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/unihack.cpp b/Src/Plugins/Input/in_vorbis/unihack.cpp new file mode 100644 index 00000000..ada2e3d8 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/unihack.cpp @@ -0,0 +1,2 @@ +#include "main.h" + diff --git a/Src/Plugins/Input/in_vorbis/vcedit.c b/Src/Plugins/Input/in_vorbis/vcedit.c new file mode 100644 index 00000000..25f40cd8 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/vcedit.c @@ -0,0 +1,491 @@ +/* This program is licensed under the GNU Library General Public License, version 2, + * a copy of which is included with this program (LICENCE.LGPL). + * + * (c) 2000-2001 Michael Smith <msmith@labyrinth.net.au> + * + * + * Comment editing backend, suitable for use by nice frontend interfaces. + * + * last modified: $Id: vcedit.c,v 1.3 2013/10/22 14:17:11 dromagod Exp $ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ogg/ogg.h> +#include <vorbis/codec.h> + +#include "vcedit.h" +//#include "i18n.h" + + +#define CHUNKSIZE 4096 + +vcedit_state *vcedit_new_state(void) +{ + vcedit_state *state = malloc(sizeof(vcedit_state)); + memset(state, 0, sizeof(vcedit_state)); + + return state; +} + +char *vcedit_error(vcedit_state *state) +{ + return state->lasterror; +} + +vorbis_comment *vcedit_comments(vcedit_state *state) +{ + return state->vc; +} + +static void vcedit_clear_internals(vcedit_state *state) +{ + if(state->vc) + { + vorbis_comment_clear(state->vc); + free(state->vc); + } + if(state->os) + { + ogg_stream_clear(state->os); + free(state->os); + } + if(state->oy) + { + ogg_sync_clear(state->oy); + free(state->oy); + } + if(state->vendor) + free(state->vendor); + if(state->mainbuf) + free(state->mainbuf); + if(state->bookbuf) + free(state->bookbuf); + if(state->vi) { + vorbis_info_clear(state->vi); + free(state->vi); + } + + memset(state, 0, sizeof(*state)); +} + +void vcedit_clear(vcedit_state *state) +{ + if(state) + { + vcedit_clear_internals(state); + free(state); + } +} + +/* Next two functions pulled straight from libvorbis, apart from one change + * - we don't want to overwrite the vendor string. + */ +static void _v_writestring(oggpack_buffer *o,char *s, int len) +{ + while(len--) + { + oggpack_write(o,*s++,8); + } +} + +static int _commentheader_out(vorbis_comment *vc, char *vendor, ogg_packet *op) +{ + oggpack_buffer opb; + + oggpack_writeinit(&opb); + + /* preamble */ + oggpack_write(&opb,0x03,8); + _v_writestring(&opb,"vorbis", 6); + + /* vendor */ + oggpack_write(&opb,(unsigned long)strlen(vendor),32); + _v_writestring(&opb,vendor, (int)strlen(vendor)); + + /* comments */ + oggpack_write(&opb,vc->comments,32); + if(vc->comments){ + int i; + for(i=0;i<vc->comments;i++){ + if(vc->user_comments[i]){ + oggpack_write(&opb,vc->comment_lengths[i],32); + _v_writestring(&opb,vc->user_comments[i], + vc->comment_lengths[i]); + }else{ + oggpack_write(&opb,0,32); + } + } + } + oggpack_write(&opb,1,1); + + op->packet = _ogg_malloc(oggpack_bytes(&opb)); + memcpy(op->packet, opb.buffer, oggpack_bytes(&opb)); + + op->bytes=oggpack_bytes(&opb); + op->b_o_s=0; + op->e_o_s=0; + op->granulepos=0; + + oggpack_writeclear(&opb); + return 0; +} + +static int _blocksize(vcedit_state *s, ogg_packet *p) +{ + int this = vorbis_packet_blocksize(s->vi, p); + int ret = (this + s->prevW)/4; + + if(!s->prevW) + { + s->prevW = this; + return 0; + } + + s->prevW = this; + return ret; +} + +static int _fetch_next_packet(vcedit_state *s, ogg_packet *p, ogg_page *page) +{ + int result = ogg_stream_packetout(s->os, p); + + if(result > 0) + return 1; + else + { + if(s->eosin) + return 0; + while(ogg_sync_pageout(s->oy, page) <= 0) + { + char *buffer = ogg_sync_buffer(s->oy, CHUNKSIZE); + int bytes = (int)s->read(buffer,1, CHUNKSIZE, s->in); + ogg_sync_wrote(s->oy, bytes); + if(bytes == 0) + return 0; + } + if(ogg_page_eos(page)) + s->eosin = 1; + else if(ogg_page_serialno(page) != s->serial) + { + s->eosin = 1; + s->extrapage = 1; + return 0; + } + + ogg_stream_pagein(s->os, page); + return _fetch_next_packet(s, p, page); + } +} + +int vcedit_open(vcedit_state *state, FILE *in) +{ + return vcedit_open_callbacks(state, (void *)in, + (vcedit_read_func)fread, (vcedit_write_func)fwrite); +} + +int vcedit_open_callbacks(vcedit_state *state, void *in, + vcedit_read_func read_func, vcedit_write_func write_func) +{ + + char *buffer; + int bytes,i; + ogg_packet *header; + ogg_packet header_main; + ogg_packet header_comments; + ogg_packet header_codebooks; + ogg_page og; + + state->in = in; + state->read = read_func; + state->write = write_func; + + state->oy = malloc(sizeof(ogg_sync_state)); + ogg_sync_init(state->oy); + + buffer = ogg_sync_buffer(state->oy, CHUNKSIZE); + bytes = (int)state->read(buffer, 1, CHUNKSIZE, state->in); + + ogg_sync_wrote(state->oy, bytes); + + if(ogg_sync_pageout(state->oy, &og) != 1) + { + if(bytes<CHUNKSIZE) + state->lasterror = "Input truncated or empty."; + else + state->lasterror = "Input is not an Ogg bitstream."; + goto err; + } + + state->serial = ogg_page_serialno(&og); + + state->os = malloc(sizeof(ogg_stream_state)); + ogg_stream_init(state->os, state->serial); + + state->vi = malloc(sizeof(vorbis_info)); + vorbis_info_init(state->vi); + + state->vc = malloc(sizeof(vorbis_comment)); + vorbis_comment_init(state->vc); + + if(ogg_stream_pagein(state->os, &og) < 0) + { + state->lasterror = "Error reading first page of Ogg bitstream."; + goto err; + } + + if(ogg_stream_packetout(state->os, &header_main) != 1) + { + state->lasterror = "Error reading initial header packet."; + goto err; + } + + if(vorbis_synthesis_headerin(state->vi, state->vc, &header_main) < 0) + { + state->lasterror = "Ogg bitstream does not contain vorbis data."; + goto err; + } + + state->mainlen = header_main.bytes; + state->mainbuf = malloc(state->mainlen); + memcpy(state->mainbuf, header_main.packet, header_main.bytes); + + i = 0; + header = &header_comments; + while(i<2) { + while(i<2) { + int result = ogg_sync_pageout(state->oy, &og); + if(result == 0) break; /* Too little data so far */ + else if(result == 1) + { + ogg_stream_pagein(state->os, &og); + while(i<2) + { + result = ogg_stream_packetout(state->os, header); + if(result == 0) break; + if(result == -1) + { + state->lasterror = "Corrupt secondary header."; + goto err; + } + vorbis_synthesis_headerin(state->vi, state->vc, header); + if(i==1) + { + state->booklen = header->bytes; + state->bookbuf = malloc(state->booklen); + memcpy(state->bookbuf, header->packet, + header->bytes); + } + i++; + header = &header_codebooks; + } + } + } + + buffer = ogg_sync_buffer(state->oy, CHUNKSIZE); + bytes = (int)state->read(buffer, 1, CHUNKSIZE, state->in); + if(bytes == 0 && i < 2) + { + state->lasterror = "EOF before end of vorbis headers."; + goto err; + } + ogg_sync_wrote(state->oy, bytes); + } + + /* Copy the vendor tag */ + bytes = (int)strlen(state->vc->vendor); + state->vendor = malloc(bytes +1); + strncpy(state->vendor, state->vc->vendor, bytes); + + /* Headers are done! */ + return 0; + +err: + vcedit_clear_internals(state); + return -1; +} + +int vcedit_write(vcedit_state *state, void *out) +{ + ogg_stream_state streamout; + ogg_packet header_main; + ogg_packet header_comments; + ogg_packet header_codebooks; + + ogg_page ogout, ogin; + ogg_packet op; + ogg_int64_t granpos = 0; + int result; + char *buffer; + int bytes; + int needflush=0, needout=0; + + state->eosin = 0; + state->extrapage = 0; + + header_main.bytes = state->mainlen; + header_main.packet = state->mainbuf; + header_main.b_o_s = 1; + header_main.e_o_s = 0; + header_main.granulepos = 0; + + header_codebooks.bytes = state->booklen; + header_codebooks.packet = state->bookbuf; + header_codebooks.b_o_s = 0; + header_codebooks.e_o_s = 0; + header_codebooks.granulepos = 0; + + ogg_stream_init(&streamout, state->serial); + + _commentheader_out(state->vc, state->vendor, &header_comments); + + ogg_stream_packetin(&streamout, &header_main); + ogg_stream_packetin(&streamout, &header_comments); + ogg_stream_packetin(&streamout, &header_codebooks); + + while((result = ogg_stream_flush(&streamout, &ogout))) + { + if(state->write(ogout.header,1,ogout.header_len, out) != + (size_t) ogout.header_len) + goto cleanup; + if(state->write(ogout.body,1,ogout.body_len, out) != + (size_t) ogout.body_len) + goto cleanup; + } + + while(_fetch_next_packet(state, &op, &ogin)) + { + int size; + size = _blocksize(state, &op); + granpos += size; + + if(needflush) + { + if(ogg_stream_flush(&streamout, &ogout)) + { + if(state->write(ogout.header,1,ogout.header_len, + out) != (size_t) ogout.header_len) + goto cleanup; + if(state->write(ogout.body,1,ogout.body_len, + out) != (size_t) ogout.body_len) + goto cleanup; + } + } + else if(needout) + { + if(ogg_stream_pageout(&streamout, &ogout)) + { + if(state->write(ogout.header,1,ogout.header_len, + out) != (size_t) ogout.header_len) + goto cleanup; + if(state->write(ogout.body,1,ogout.body_len, + out) != (size_t) ogout.body_len) + goto cleanup; + } + } + + needflush=needout=0; + + if(op.granulepos == -1) + { + op.granulepos = granpos; + ogg_stream_packetin(&streamout, &op); + } + else /* granulepos is set, validly. Use it, and force a flush to + account for shortened blocks (vcut) when appropriate */ + { + if(granpos > op.granulepos) + { + granpos = op.granulepos; + ogg_stream_packetin(&streamout, &op); + needflush=1; + } + else + { + ogg_stream_packetin(&streamout, &op); + needout=1; + } + } + } + + streamout.e_o_s = 1; + while(ogg_stream_flush(&streamout, &ogout)) + { + if(state->write(ogout.header,1,ogout.header_len, + out) != (size_t) ogout.header_len) + goto cleanup; + if(state->write(ogout.body,1,ogout.body_len, + out) != (size_t) ogout.body_len) + goto cleanup; + } + + if (state->extrapage) + { + if(state->write(ogin.header,1,ogin.header_len, + out) != (size_t) ogin.header_len) + goto cleanup; + if (state->write(ogin.body,1,ogin.body_len, out) != + (size_t) ogin.body_len) + goto cleanup; + } + + state->eosin=0; /* clear it, because not all paths to here do */ + while(!state->eosin) /* We reached eos, not eof */ + { + /* We copy the rest of the stream (other logical streams) + * through, a page at a time. */ + while(1) + { + result = ogg_sync_pageout(state->oy, &ogout); + if(result==0) + break; + if(result<0) + state->lasterror = "Corrupt or missing data, continuing..."; + else + { + /* Don't bother going through the rest, we can just + * write the page out now */ + if(state->write(ogout.header,1,ogout.header_len, + out) != (size_t) ogout.header_len) { +// fprintf(stderr, "Bumming out\n"); + goto cleanup; + } + if(state->write(ogout.body,1,ogout.body_len, out) != + (size_t) ogout.body_len) { + // fprintf(stderr, "Bumming out 2\n"); + goto cleanup; + } + } + } + buffer = ogg_sync_buffer(state->oy, CHUNKSIZE); + bytes = (int)state->read(buffer,1, CHUNKSIZE, state->in); + ogg_sync_wrote(state->oy, bytes); + if(bytes == 0) + { + state->eosin = 1; + break; + } + } + + +cleanup: + ogg_stream_clear(&streamout); + ogg_packet_clear(&header_comments); + + free(state->mainbuf); + free(state->bookbuf); + state->mainbuf = state->bookbuf = NULL; + + if(!state->eosin) + { + state->lasterror = + "Error writing stream to output. " + "Output stream may be corrupted or truncated."; + return -1; + } + + return 0; +} + diff --git a/Src/Plugins/Input/in_vorbis/vcedit.h b/Src/Plugins/Input/in_vorbis/vcedit.h new file mode 100644 index 00000000..80cc5a6b --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/vcedit.h @@ -0,0 +1,62 @@ +/* This program is licensed under the GNU Library General Public License, version 2, + * a copy of which is included with this program (with filename LICENSE.LGPL). + * + * (c) 2000-2001 Michael Smith <msmith@labyrinth.net.au> + * + * VCEdit header. + * + * last modified: $ID:$ + */ + +#ifndef __VCEDIT_H +#define __VCEDIT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> +#include <ogg/ogg.h> +#include <vorbis/codec.h> + +typedef size_t (*vcedit_read_func)(void *, size_t, size_t, void *); +typedef size_t (*vcedit_write_func)(const void *, size_t, size_t, void *); + +typedef struct { + ogg_sync_state *oy; + ogg_stream_state *os; + + vorbis_comment *vc; + vorbis_info *vi; + + vcedit_read_func read; + vcedit_write_func write; + + void *in; + long serial; + unsigned char *mainbuf; + unsigned char *bookbuf; + int mainlen; + int booklen; + char *lasterror; + char *vendor; + int prevW; + int extrapage; + int eosin; +} vcedit_state; + +extern vcedit_state * vcedit_new_state(void); +extern void vcedit_clear(vcedit_state *state); +extern vorbis_comment * vcedit_comments(vcedit_state *state); +extern int vcedit_open(vcedit_state *state, FILE *in); +extern int vcedit_open_callbacks(vcedit_state *state, void *in, + vcedit_read_func read_func, vcedit_write_func write_func); +extern int vcedit_write(vcedit_state *state, void *out); +extern char * vcedit_error(vcedit_state *state); + +#ifdef __cplusplus +} +#endif + +#endif /* __VCEDIT_H */ + diff --git a/Src/Plugins/Input/in_vorbis/version.rc2 b/Src/Plugins/Input/in_vorbis/version.rc2 new file mode 100644 index 00000000..b6a16388 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,79,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", "1,79,0,0" + VALUE "InternalName", "Nullsoft Vorbis Decoder" + VALUE "LegalCopyright", "Copyright © 2001-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_vorbis.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_vorbis/wa2.cpp b/Src/Plugins/Input/in_vorbis/wa2.cpp new file mode 100644 index 00000000..3920a0ee --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/wa2.cpp @@ -0,0 +1,1097 @@ +#include "main.h" +#include "genres.h" +#include "decoder.h" +#include "api__in_vorbis.h" +#include "../Winamp/wa_ipc.h" +#include "../nu/Singleton.h" +#include "mkv_vorbis_decoder.h" +#include <shlwapi.h> +#include "../nu/AutoWide.h" +#include "../nu/AutoChar.h" +#include <strsafe.h> +#include <api/service/waservicefactory.h> + +template <class api_T> +void ServiceBuild(api_T *&api_t, GUID factoryGUID_t) +{ + if (mod.service) + { + waServiceFactory *factory = mod.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 (mod.service && api_t) + { + waServiceFactory *factory = mod.service->service_getServiceByGuid(factoryGUID_t); + if (factory) + factory->releaseInterface(api_t); + } + api_t = NULL; +} + +VorbisFile * theFile = 0; + +extern CfgInt cfg_abr,cfg_httpseek2; + +OSVERSIONINFO os_ver = {0}; + +static int pos_ms; +static int seek_to=-1; +static int length; +static bool kill; + +StringW stat_disp; + +void show_stat(const wchar_t* txt) +{ + if (txt) + { + stat_disp=txt; + PostMessage(mod.hMainWindow,WM_USER,0,243); + } + else + stat_disp=L""; +} + +static int is_out_open; +static int paused; +static int volume=255; +static int pan=0; +StringW cur_file; + +CRITICAL_SECTION sync; + +HANDLE hThread=0; + +void Config(HWND); +void About(HWND p); +void do_cfg(int s); +void GetFileInfo(const in_char *file, wchar_t *title, int *len); + +const char *INI_FILE=0; +const wchar_t *INI_DIRECTORY=0; +int (*warand)()=0; +float (*warandf)()=0; + +api_application *WASABI_API_APP = 0; +// wasabi based services for localisation support +api_language *WASABI_API_LNG = 0; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; +api_memmgr* WASABI_API_MEMMGR = 0; +api_config *AGAVE_API_CONFIG=0; + +static MKVDecoderCreator mkvCreator; +static SingletonServiceFactory<svc_mkvdecoder, MKVDecoderCreator> mkvFactory; + +void SetFileExtensions(void) +{ + static char fileExtensionsString[1200] = {0}; // "OGG\0Ogg files (*.OGG)\0" + char* end = 0; + StringCchCopyExA(fileExtensionsString, 1200, "OGG;OGA", &end, 0, 0); + StringCchCopyExA(end+1, 1200, WASABI_API_LNGSTRING(IDS_OGG_FILES), 0, 0, 0); + mod.FileExtensions = fileExtensionsString; +} + +int Init() +{ + if (!IsWindow(mod.hMainWindow)) + return IN_INIT_FAILURE; + + mod.UsesOutputPlug|=8; + + warand = (int (*)())SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GET_RANDFUNC); + warandf = (float (*)())SendMessage(mod.hMainWindow, WM_WA_IPC, 1, IPC_GET_RANDFUNC); + + ServiceBuild(WASABI_API_MEMMGR, memMgrApiServiceGuid); + ServiceBuild(AGAVE_API_CONFIG, AgaveConfigGUID); + // loader so that we can get the localisation service api for use + ServiceBuild(WASABI_API_LNG, languageApiGUID); + ServiceBuild(WASABI_API_APP, applicationApiServiceGuid); + + mkvFactory.Register(mod.service, &mkvCreator); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(mod.hDllInstance,InVorbisLangGUID); + + static wchar_t szDescription[256]; + StringCchPrintfW(szDescription,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_VORBIS_DECODER),VER); + mod.description = (char*)szDescription; + + SetFileExtensions(); + + INI_FILE = (const char *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETINIFILE); + INI_DIRECTORY = (const wchar_t *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETINIDIRECTORYW); + + os_ver.dwOSVersionInfoSize=sizeof(os_ver); + GetVersionEx(&os_ver); + + InitializeCriticalSection(&sync); + do_cfg(0); + return IN_INIT_SUCCESS; +} + +void Quit() +{ + winampGetExtendedFileInfoW_Cleanup(); + DeleteCriticalSection(&sync); + mkvFactory.Deregister(mod.service); + ServiceRelease(WASABI_API_MEMMGR, memMgrApiServiceGuid); + ServiceRelease(AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceRelease(WASABI_API_LNG, languageApiGUID); + ServiceRelease(WASABI_API_APP, applicationApiServiceGuid); +} + +int GetLength() +{ + return length; +} + +int IsOurFile(const in_char *fn) +{ + if (PathIsURLW(fn)) + { + const wchar_t *foo=wcsrchr(fn,L'.'); + return foo ? !_wcsicmp(foo,L".ogg") : 0; + } + else return 0; +} + +static UINT kbps_disp; + +static void out_close() +{ + if (is_out_open) + { + mod.outMod->Close(); + mod.SAVSADeInit(); + is_out_open=0; + } +} + +static bool need_full_setinfo; + +static int out_open(const Decoder &dec) +{ + int max_l=mod.outMod->Open(dec.sr,dec.nch,dec.bps,-1,-1); + if (max_l<0) return 0; + mod.outMod->SetVolume(-666); + mod.outMod->SetPan(pan); + mod.SAVSAInit(max_l,dec.sr); + mod.VSASetInfo(dec.sr,dec.nch); + + is_out_open=1; + need_full_setinfo=1; + return 1; +} + +void Decoder::wa2_setinfo(UINT cur) +{ + UINT disp=file->get_avg_bitrate(); + if (!cfg_abr) + { + disp=cur; + } + if ((disp && disp!=kbps_disp) || need_full_setinfo) + { + kbps_disp=disp; + if (need_full_setinfo) + { + mod.SetInfo(disp,sr/1000,nch,1); + need_full_setinfo=0; + } + else mod.SetInfo(disp,-1,-1,1); + } +} + +static bool need_movefile; +static void process_movefile(); + +void alloc_buffers(Decoder & dec,short ** visbuf,char ** sample_buf,int * s_size) +{ + *s_size=576 * (dec.bps>>3) * dec.nch; + + if (*sample_buf) *sample_buf=(char*)realloc(*sample_buf,*s_size*2); + else *sample_buf=(char*)malloc(*s_size*2); + + if (dec.bps>16) + { + int vs=576*2*dec.nch; + if (*visbuf) *visbuf=(short*)realloc(*visbuf,vs); + else *visbuf=(short*)malloc(vs); + } + else if (*visbuf) {free(*visbuf);*visbuf=0;} +} + +static DWORD WINAPI PlayThread(Decoder &dec) +{ + int pos_base=0; + int samp_wr=0; + int done=0; + int upd=0; + __int64 brate; + int br_div,br_t; + short* visbuf=0; + char *sample_buf=0; + int retries=0; + int s_size=0; + + pos_ms=0; + + { + int r; + r=dec.play_init(); + if (r) + { + if (!kill) Sleep(50); + if (!kill) Sleep(50); + if (!kill) Sleep(50); + if (!kill) Sleep(50); + if (!kill) + { + if (r==2) PostMessage(mod.hMainWindow,WM_WA_MPEG_EOF,0,0); + else PostMessage(mod.hMainWindow,WM_COMMAND,40047,0); + } + delete &dec; + return 0; + } + theFile->do_prebuf(); + } + + brate=0; + br_div=0; + upd=0; + + alloc_buffers(dec,&visbuf,&sample_buf,&s_size); + + //int f_type=theFile->GetType(); + bool is_live=theFile->IsLive(); + + while(!kill) + { + if (!theFile) break;//ugh + if (seek_to!= -1) + { + UINT _st=seek_to; + int r=1; + seek_to=-1; + if (theFile) + { + theFile->use_prebuf=0; + int link=theFile->vf.current_link; + r=dec.Seek((double)_st*0.001); + if (link!=theFile->vf.current_link) PostMessage(mod.hMainWindow,WM_USER,0,243); + } + else r=1; + if (!r) + { + pos_base=pos_ms=_st; + mod.outMod->Flush(pos_ms); + samp_wr=0; + done=0; + theFile->do_prebuf(); + } + } + + if (need_movefile && paused)//HACK, prevent stupid lockup + { + process_movefile(); + if (!theFile) break;//#@! + dec.file=theFile; + dec.Flush(); + } + + if (done) + { + // mod.outMod->CanWrite(); + if (!mod.outMod->IsPlaying()) + { + PostMessage(mod.hMainWindow,WM_WA_MPEG_EOF,0,0); + break; + } + Sleep(10); + } + else if (mod.outMod->CanWrite() >= (s_size<<(mod.dsp_isactive()?1:0))) + { + int l=0; + while(1) + { + if (!dec.need_reopen) + { + l+=dec.Read(s_size-l,sample_buf+l); + if (l>=s_size) break; + + int link=theFile->vf.current_link; + if (need_movefile)//safe not to flush here + { + process_movefile(); + if (!theFile) break;//#@! + dec.file=theFile; + } + if (!dec.DoFrame()) break; + if (kill) break; + + if (link!=theFile->vf.current_link) + { + PostMessage(mod.hMainWindow,WM_USER,0,243); + } + br_t=ov_bitrate_instant(&theFile->vf); + if (br_t>0) + { + int i = dec.DataAvailable()/((dec.bps/8)*dec.nch); + br_div+=i; + brate+=(__int64)(br_t*i); + } + if (need_full_setinfo || (!((++upd)%200) && br_div)) + { + if (!br_div) {br_div=1;brate=theFile->get_avg_bitrate();} + dec.wa2_setinfo((int)((__int64)brate/(__int64)br_div/(__int64)1000)); + brate=0; + br_div=0; + } + } + if (dec.need_reopen) + {//blargh, new PCM format + if (l>0) break;//got samples to play, we'll deal with this later + //l=0; + while(!kill && mod.outMod->IsPlaying()) Sleep(1); + if (kill) break; + out_close(); + if (!out_open(dec))//boo + { + PostMessage(mod.hMainWindow,WM_COMMAND,40047,0); + kill=1; + break; + } + alloc_buffers(dec,&visbuf,&sample_buf,&s_size); + dec.need_reopen=0; + } + } + + if (kill || !theFile) break; + + if (l<=0 && (!is_live || (--retries)<0)) + { + mod.outMod->Write(sample_buf,0); + done=1; + } + else if (l<=0) + { + int r; + out_close(); + EnterCriticalSection(&sync); + delete theFile; + theFile=0; + LeaveCriticalSection(&sync); + if (sample_buf) + { + free(sample_buf); + sample_buf=0; + } + + r=dec.play_init(); + + if (r) + { + mod.outMod->Write(sample_buf,0); + done=1; + } + else + { + theFile->do_prebuf(); + } + } + else + { + if (l<s_size) memset(sample_buf+l,dec.bps==8 ? 0x80 : 0,s_size-l); + char * vis=sample_buf; + UINT vis_bps=dec.bps; + if (dec.bps>16) + { + UINT n; + UINT d=dec.bps>>3; + char * foo=sample_buf+d-2; + for(n=0;n<576*dec.nch;n++) + { + visbuf[n]=*(short*)foo; + foo+=d; + } + vis=(char*)visbuf; + vis_bps=16; + } + mod.SAAddPCMData(vis,dec.nch,vis_bps,pos_ms); + mod.VSAAddPCMData(vis,dec.nch,vis_bps,pos_ms); + + if (mod.dsp_isactive()) + { + l=(l<<3)/(dec.bps*dec.nch); + l=mod.dsp_dosamples((short*)sample_buf,l,dec.bps,dec.nch,dec.sr); + l*=(dec.nch*dec.bps)>>3; + } + if (kill) break; + mod.outMod->Write((char*)sample_buf,l); + + samp_wr+=(8*l)/(dec.bps*dec.nch); + pos_ms=pos_base+MulDiv(1000,samp_wr,dec.sr); + } + } + else + { + theFile->Idle(); + } + } + + // out_close(); + // gay gapless plugins puke, need to call this from stop + // ok, hetero (out_wave v2.x / out_ds v1.4+) gapless plugins wouldn't puke anymore + + if (theFile) + { + VorbisFile * t=theFile; + EnterCriticalSection(&sync); + theFile=0; + LeaveCriticalSection(&sync); + delete t; + } + + if (sample_buf) + { + free(sample_buf); + sample_buf=0; + } + + if (need_movefile) process_movefile(); + + /* if (!kill) + { + CloseHandle(hThread); + hThread=0; + }*/ + + if (visbuf) free(visbuf); + + delete &dec; + return 0; +} + +static StringW move_src,move_dst; +static bool mf_ret; + +static void do_movefile() +{ + mf_ret=1; + winampGetExtendedFileInfoW_Cleanup(); + if (!DeleteFileW(move_dst)) mf_ret=0; + else + { + if (!MoveFileW(move_src,move_dst)) + { + if (!CopyFileW(move_src,move_dst,0)) mf_ret=0; + DeleteFileW(move_src); + } + } +} + +static void process_movefile() +{ + if (theFile) + { + StringW f_path; + f_path.AddString(theFile->url); + double pos=theFile->GetPos(); + EnterCriticalSection(&sync); + delete theFile; + theFile=0; + + do_movefile(); + + theFile=VorbisFile::Create(f_path,0); + LeaveCriticalSection(&sync); + if (theFile) + { + theFile->Seek(pos); + } + } + else do_movefile(); + need_movefile=0; +} + +bool sync_movefile(const wchar_t * src,const wchar_t * dst)//called from info_.cpp +{ + move_src=src; + move_dst=dst; + need_movefile=1; + if (!theFile) process_movefile(); + else + { + while(need_movefile && hThread) Sleep(1); + if (need_movefile) process_movefile();//shouldnt really happen + move_src=L""; + move_dst=L""; + PostMessage(mod.hMainWindow,WM_USER,0,243); + } + + return mf_ret; +} + + +int Decoder::play_init()//still messy +{ + if (play_inited) return 0; + + kbps_disp=0; + + VorbisFile * t=VorbisFile::Create(cur_file,0); + if (!t) + { +#ifdef _DEBUG + OutputDebugString(L"can't open file\n"); +#endif + // if (scream) MessageBox(mod.hMainWindow,"error opening file",0,MB_ICONERROR); + return 2; + } + Init(t); + if (!out_open(*this)) + { +#ifdef _DEBUG + OutputDebugString(L"can't open output\n"); +#endif + delete t; + return 1; + } + + EnterCriticalSection(&sync); + theFile=t; + LeaveCriticalSection(&sync); + + wa2_setinfo(theFile->get_avg_bitrate()); + + { + double v=theFile->Length(); + if (v==OV_EINVAL || v<=0) length=-1; + else length=(int)(v*1000.0); + } + + play_inited=1; + + return 0; +} + +int Play(const in_char *fn) +{ + seek_to=-1; + kill=0; + length=0; + paused=0; + + show_stat(0); + + EnterCriticalSection(&sync); + cur_file=fn; + LeaveCriticalSection(&sync); + + Decoder * dec=new Decoder; + + if (!PathIsURLW(fn)) + { + mod.is_seekable=1; +#if 1 + int rv=dec->play_init(); + if (rv) + { + delete dec; + if (rv==2) return -1; + return 1; + } +#endif + } + else mod.is_seekable=cfg_httpseek2; + + { + DWORD id; + hThread=CreateThread(0,0,(LPTHREAD_START_ROUTINE)PlayThread,dec,CREATE_SUSPENDED,&id); + } + + if (hThread) + { + SetThreadPriority(hThread, (int)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + ResumeThread(hThread); + return 0; + } + else + { + out_close(); + delete dec; + return 1; + } +} + +void Pause() +{ + if (!paused) + { + mod.outMod->Pause(1); + paused=1; + } +} + +void UnPause() +{ + if (paused) + { + mod.outMod->Pause(0); + paused=0; + } +} + +int IsPaused() +{ + return paused; +} + +void Stop() +{ + if (hThread) + { + kill=1; + EnterCriticalSection(&sync); + if (theFile) theFile->stopping=1; + LeaveCriticalSection(&sync); + if (WaitForSingleObject(hThread,10000)!=WAIT_OBJECT_0) + { + TerminateThread(hThread,0); + //MessageBox(mod.hMainWindow,"error asking thread to die",0,MB_ICONERROR); + } + CloseHandle(hThread); + hThread=0; + out_close(); + } + show_stat(0); + winampGetExtendedFileInfoW_Cleanup(); +} + +void EQSet(int on, char data[10], int preamp) +{ +} + +int GetOutputTime() +{ + return pos_ms+(mod.outMod->GetOutputTime()-mod.outMod->GetWrittenTime()); +} + +void SetOutputTime(int t) +{ + seek_to=t; + EnterCriticalSection(&sync); + if (theFile) theFile->abort_prebuf=1; + LeaveCriticalSection(&sync); +} + +void SetVolume(int v) +{ + mod.outMod->SetVolume(volume=v); +} + +void SetPan(int p) +{ + mod.outMod->SetPan(pan=p); +} + +//int InfoBox(char *file, HWND parent); //old +int RunInfoDlg(const in_char * url,HWND parent); + +In_Module mod= +{ + IN_VER_RET, + "nullsoft(in_vorbis.dll)", + 0,0, + 0, + 1, + 1, + + Config, + About, + + Init, + Quit, + + GetFileInfo, + RunInfoDlg, + + 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 &mod; + } +} + +void VorbisFile::Status(const wchar_t * zzz) +{ + if (primary) + show_stat(zzz); +} + +bool VorbisFile::Aborting() +{ + return stopping || (primary && kill); +} + + +Info::Info(const wchar_t *filename) : filename(filename), vc(0) +{ + VorbisFile * vf = VorbisFile::Create(filename,true); + if(!vf) + return; + + numstreams = vf->vf.links; + if(numstreams) + { + // now copy the comment section to our own memory... + stream = vf->vf.current_link; // this is the stream we're editing... + vc = (vorbis_comment*)calloc(sizeof(vorbis_comment),numstreams); + + for(int i=0; i<numstreams; i++) + { // one comment section per stream + vorbis_comment *c = ov_comment(&vf->vf,i); + + vc[i].comments = c->comments; + vc[i].user_comments = (char **)malloc(sizeof(char*)*c->comments); + vc[i].comment_lengths = (int *)malloc(sizeof(int)*c->comments); + + for(int j=0;j<vc[i].comments;j++) + { // copy the comments over + vc[i].user_comments[j] = _strdup(c->user_comments[j]); + vc[i].comment_lengths[j] = c->comment_lengths[j]; + } + vc[i].vendor=_strdup(c->vendor); + } + } + delete vf; +} + +Info::~Info() +{ + if(vc) { + for(int i=0; i < numstreams; i++) + vorbis_comment_clear(&vc[i]); + free(vc); + } +} + +bool Info::Save() +{ + return !!modify_file(filename,vc,numstreams); +} + +int Info::GetNumMetadataItems() +{ + return vc[stream].comments; +} + +void Info::EnumMetadata(int n, wchar_t *key, int keylen, wchar_t *val, int vallen) +{ + if(keylen) key[0]=0; + if(vallen) val[0]=0; + if(!vc) return; + if(!vc[stream].user_comments[n]) return; + AutoWide comment(vc[stream].user_comments[n],CP_UTF8); + const wchar_t* eq = wcschr((const wchar_t*)comment,L'='); + if(eq) + { + if(keylen) lstrcpynW(key,comment,(int)min(eq - comment + 1,keylen)); + if(vallen) lstrcpynW(val,eq+1,vallen); + } + else + { + if(keylen) lstrcpynW(key,L"COMMENT",keylen); + if(vallen) lstrcpynW(val,comment,vallen); + } +} + +void Info::RemoveMetadata(wchar_t * key) +{ + wchar_t k[256] = {0}; + for(int i=0; i<GetNumMetadataItems(); i++) + { + EnumMetadata(i,k,256,0,0); + if(_wcsicmp(k,key)==0) + RemoveMetadata(i); + } +} + +void Info::RemoveMetadata(int n) +{ + if(!vc) return; + free(vc[stream].user_comments[n]); + + for(int i=n+1; i<vc[stream].comments; i++) + { + vc[stream].user_comments[i-1] = vc[stream].user_comments[i]; + if(vc[stream].comment_lengths) + vc[stream].comment_lengths[i-1] = vc[stream].comment_lengths[i]; + } + vc[stream].comments--; + vc[stream].user_comments = (char**)realloc(vc[stream].user_comments,sizeof(vc[stream].user_comments[0]) * vc[stream].comments); + if(vc[stream].comment_lengths) + vc[stream].comment_lengths = (int*)realloc(vc[stream].comment_lengths,sizeof(vc[stream].comment_lengths[0]) * vc[stream].comments); +} + +void Info::SetMetadata(wchar_t *key, wchar_t *val) +{ + bool set=false; + wchar_t k[256] = {0}; + for(int i=0; i<GetNumMetadataItems(); i++) + { + EnumMetadata(i,k,256,0,0); + if(_wcsicmp(k,key)==0) + { + SetMetadata(i,key,val); + set=true; + } + } + if(!set) + { + int n = vc[stream].comments++; + vc[stream].user_comments = (char**)realloc(vc[stream].user_comments,sizeof(vc[stream].user_comments[0]) * vc[stream].comments); + if(vc[stream].comment_lengths) + vc[stream].comment_lengths = (int*)realloc(vc[stream].comment_lengths,sizeof(vc[stream].comment_lengths[0]) * vc[stream].comments); + vc[stream].user_comments[n] = NULL; + SetMetadata(n,key,val); + } +} + +void Info::SetMetadata(int n, wchar_t *key, wchar_t *val) +{ + AutoChar k(key,CP_UTF8); + AutoChar v(val,CP_UTF8); + + int l = (int)(strlen(k)+strlen(v)+2); + char * c = (char*)malloc(l); + StringCchPrintfA(c,l,"%s=%s",(char*)k,(char*)v); + + if(vc[stream].user_comments[n]) + free(vc[stream].user_comments[n]); + + vc[stream].user_comments[n] = c; + if(vc[stream].comment_lengths) + vc[stream].comment_lengths[n] = l-1; +} + +void Info::SetTag(int n,wchar_t *key) // changes the key name +{ + wchar_t val[2048] = {0}; + EnumMetadata(n,NULL,0,val,2048); + SetMetadata(n,key,val); +} + +Info *setMetadata = 0; + +extern "C" +{ + static wchar_t m_lastfn[2048]; + + #define START_TAG_ALIAS(name, alias) if (KeywordMatch(data, name)) lookup=alias + #define TAG_ALIAS(name, alias) else if (KeywordMatch(data, name)) lookup=alias + + __declspec( dllexport ) int winampSetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *val) + { + if (!setMetadata || setMetadata && wcscmp(fn,m_lastfn)) + { + if (setMetadata) + { + delete setMetadata; + setMetadata = 0; + } + + setMetadata = new Info(fn); + if(setMetadata->Error()) + { + delete setMetadata; + m_lastfn[0] = 0; + return 0; + } + lstrcpynW(m_lastfn,fn, 2048); + } + + wchar_t *lookup=0; + START_TAG_ALIAS("artist", L"ARTIST"); + TAG_ALIAS("title", L"TITLE"); + TAG_ALIAS("album", L"ALBUM"); + TAG_ALIAS("genre", L"GENRE"); + TAG_ALIAS("comment", L"COMMENT"); + TAG_ALIAS("year", L"DATE"); + TAG_ALIAS("track", L"TRACKNUMBER"); + TAG_ALIAS("albumartist", L"ALBUM ARTIST"); + TAG_ALIAS("composer", L"COMPOSER"); + TAG_ALIAS("disc", L"DISCNUMBER"); + TAG_ALIAS("publisher", L"PUBLISHER"); + TAG_ALIAS("conductor", L"CONDUCTOR"); + TAG_ALIAS("tool", L"ENCODED-BY"); + TAG_ALIAS("replaygain_track_gain", L"REPLAYGAIN_TRACK_GAIN"); + TAG_ALIAS("replaygain_track_peak", L"REPLAYGAIN_TRACK_PEAK"); + TAG_ALIAS("replaygain_album_gain", L"REPLAYGAIN_ALBUM_GAIN"); + TAG_ALIAS("replaygain_album_peak", L"REPLAYGAIN_ALBUM_PEAK"); + TAG_ALIAS("GracenoteFileID", L"GRACENOTEFILEID"); + TAG_ALIAS("GracenoteExtData", L"GRACENOTEEXTDATA"); + TAG_ALIAS("bpm", L"BPM"); + TAG_ALIAS("remixing", L"REMIXING"); + TAG_ALIAS("subtitle", L"VERSION"); + TAG_ALIAS("isrc", L"ISRC"); + TAG_ALIAS("category", L"CATEGORY"); + TAG_ALIAS("rating", L"RATING"); + TAG_ALIAS("producer", L"PRODUCER"); + + if (!lookup) + return 0; + +#if 0 + if (val && *val) + { + if(KeywordMatch("rating",data)) + { + wchar_t temp[128] = {0}; + StringCchPrintfW(temp, 128, L"%u", _wtoi(val)*20); + val=temp; + } + } + AutoChar utf8(val, CP_UTF8); + + for(int i=0;i<m_vc->comments;i++) + { + char *c=m_vc[m_curstream].user_comments[i]; + if(!c) continue; + char *p=strchr(c,'='); + if (p && *p) + { + if(strlen(data) == (p-c) && !_strnicmp(c,data,p-c)) + { + //found! + if (val && val[0]) + { + int added_buf_len = strlen(utf8)+strlen(lookup)+2; + m_vc[m_curstream].user_comments[i]=(char *)realloc(m_vc[m_curstream].user_comments[i],added_buf_len); + StringCchPrintfA(m_vc[m_curstream].user_comments[i],added_buf_len,"%s=%s",lookup,(char *)utf8); + m_vc[m_curstream].comment_lengths[i]=strlen(m_vc[m_curstream].user_comments[i]); + } + else + { + free(m_vc[m_curstream].user_comments[i]); + m_vc[m_curstream].user_comments[i]=0; + m_vc[m_curstream].comment_lengths[i]=0; + } + return 1; + } + } + } + + //not found, so create new field + if (val && val[0]) + { + int k=m_vc[m_curstream].comments++; + m_vc[m_curstream].user_comments=(char **)realloc(m_vc[m_curstream].user_comments,sizeof(char*)*m_vc[m_curstream].comments); + m_vc[m_curstream].comment_lengths=(int *)realloc(m_vc[m_curstream].comment_lengths,sizeof(int)*m_vc[m_curstream].comments); + int added_buf_len = strlen(utf8)+strlen(lookup)+2; + m_vc[m_curstream].user_comments[k]=(char *)malloc(added_buf_len); + StringCchPrintfA(m_vc[m_curstream].user_comments[k],added_buf_len,"%s=%s",lookup,(char *)utf8); + m_vc[m_curstream].comment_lengths[k]=strlen(m_vc[m_curstream].user_comments[k]); + } +#endif + + if (val && *val) + { + if(KeywordMatch("rating",data)) + { + wchar_t temp[128] = {0}; + StringCchPrintfW(temp, 128, L"%u", _wtoi(val)*20); + setMetadata->SetMetadata(lookup, temp); + } + else + { + setMetadata->SetMetadata(lookup, val); + } + } + else + { + setMetadata->RemoveMetadata(lookup); + if(KeywordMatch("comment",data)) + { + // need to remove this one also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + setMetadata->RemoveMetadata(L"DESCRIPTION"); + } + else if(KeywordMatch("year",data)) + { + // need to remove this one also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + setMetadata->RemoveMetadata(L"YEAR"); + } + else if(KeywordMatch("track",data)) + { + // need to remove this one also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + setMetadata->RemoveMetadata(L"TRACK"); + } + else if(KeywordMatch("albumartist",data)) + { + // need to remove these two, also, or else it's gonna look like delete doesn't work + // if the file was tagged using these alternate fields + setMetadata->RemoveMetadata(L"ALBUMARTIST"); + setMetadata->RemoveMetadata(L"ENSEMBLE"); + } + else if(KeywordMatch("publisher",data)) + { + // need to remove this one also, or else it's gonna look like delete doesn't work + // if the file was tagged using this alternate field + setMetadata->RemoveMetadata(L"ORGANIZATION"); + } + else if(KeywordMatch("category",data)) + { + // need to remove these two also, or else it's gonna look like delete doesn't work + // if the file was tagged using these alternate fields + setMetadata->RemoveMetadata(L"CONTENTGROUP"); + setMetadata->RemoveMetadata(L"GROUPING"); + } + } + + return 1; + } + + __declspec( dllexport ) int winampWriteExtendedFileInfo() + { + if(!setMetadata) return 0; + + bool ret = setMetadata->Save(); + delete setMetadata; + setMetadata = 0; + + // update last modified so we're not re-queried on our own updates + UpdateFileTimeChanged(m_lastfn); + + return ret; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_vorbis/winnt_helper.cpp b/Src/Plugins/Input/in_vorbis/winnt_helper.cpp new file mode 100644 index 00000000..da44ca42 --- /dev/null +++ b/Src/Plugins/Input/in_vorbis/winnt_helper.cpp @@ -0,0 +1,2 @@ +#include "main.h" +#include "api__in_vorbis.h" diff --git a/Src/Plugins/Input/in_wave/AudioThread.cpp b/Src/Plugins/Input/in_wave/AudioThread.cpp new file mode 100644 index 00000000..b6e8676c --- /dev/null +++ b/Src/Plugins/Input/in_wave/AudioThread.cpp @@ -0,0 +1,330 @@ +#include <windows.h> +#include "main.h" +#include "../Winamp/wa_ipc.h" +#include "config.h" +#include "api__in_wave.h" +#include <shlwapi.h> +#include "VirtualIO.h" + +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = + { 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } }; + +HANDLE audioThread = INVALID_HANDLE_VALUE; +DWORD WINAPI ThreadProcedure( void *data ); + +#define kill events[0] +#define running events[1] + +HANDLE stopped = 0; +HANDLE events[ 2 ] = { 0 }; + +static size_t bufferSize = 0; +static char *audioBuffer = 0; +static int frameSize = 0; // in bytes +static int endOfFile = 0; +static int bits = 0; +static SF_INFO info; +static void *reader = 0; + + +int CalcBits() +{ + switch (info.format & SF_FORMAT_SUBMASK) + { + case SF_FORMAT_DOUBLE: + return 64; + + case SF_FORMAT_PCM_32: + case SF_FORMAT_FLOAT: + return 32; + + case SF_FORMAT_DWVW_24: + case SF_FORMAT_PCM_24: + return 24; + + case SF_FORMAT_DPCM_16: + case SF_FORMAT_DWVW_16: + case SF_FORMAT_PCM_16: + return 16; + + //case SF_FORMAT_PCM_S8: // cut, because 8bits is assumed unsigned + case SF_FORMAT_PCM_U8: + case SF_FORMAT_DPCM_8: + return 8; + + default: return 16; + } +} + +int CalcBitRate( const SF_INFO *info ) +{ + switch ( info->format & SF_FORMAT_SUBMASK ) + { + case SF_FORMAT_PCM_S8: + case SF_FORMAT_PCM_U8: + case SF_FORMAT_DPCM_8: + return MulDiv( 8 * info->channels, info->samplerate, 1000 ); + case SF_FORMAT_DWVW_12: + return MulDiv( 12 * info->channels, info->samplerate, 1000 ); + case SF_FORMAT_DPCM_16: + case SF_FORMAT_DWVW_16: + case SF_FORMAT_PCM_16: + return MulDiv( 16 * info->channels, info->samplerate, 1000 ); + case SF_FORMAT_DWVW_24: + case SF_FORMAT_PCM_24: + return MulDiv( 24 * info->channels, info->samplerate, 1000 ); + case SF_FORMAT_PCM_32: + case SF_FORMAT_FLOAT: + return MulDiv( 32 * info->channels, info->samplerate, 1000 ); + case SF_FORMAT_DOUBLE: + return MulDiv( 64 * info->channels, info->samplerate, 1000 ); + + case SF_FORMAT_G721_32: + return 32; + + case SF_FORMAT_G723_24: + return 24; + + case SF_FORMAT_G723_40: + return 40; + case SF_FORMAT_MS_ADPCM: + case SF_FORMAT_VOX_ADPCM: + case SF_FORMAT_IMA_ADPCM: + return MulDiv( 4 * info->channels, info->samplerate, 1000 ); + default: + return MulDiv( 16 * info->channels, info->samplerate, 1000 ); + } +} + + +void CALLBACK APCSeek( ULONG_PTR p_data ) +{ + endOfFile = 0; + + int time_in_ms = (int)p_data; + int frames = MulDiv( time_in_ms, info.samplerate, 1000 ); // TODO: verify calculation + + sf_seek( sndFile, frames, SEEK_SET ); + + plugin.outMod->Flush( time_in_ms ); +} + +void CALLBACK APCPause( ULONG_PTR p_data ) +{ + int pause = (int)p_data; + if ( pause ) + ResetEvent( running ); + else + SetEvent( running ); + + plugin.outMod->Pause( !!pause ); +} + +void CALLBACK APCStart( ULONG_PTR p_data ) +{ + endOfFile = 0; + const wchar_t *file = (const wchar_t *)p_data; + + info.format = 0; + if ( PathIsURLW( file ) ) + { + reader = CreateReader( file ); + if ( reader ) + sndFile = sf_open_virtual( &httpIO, SFM_READ, &info, reader ); + } + else // It's a local file + { + sndFile = sf_wchar_open( file, SFM_READ, &info ); + } + + if ( !sndFile ) + { + if ( WaitForSingleObject( kill, 200 ) == WAIT_TIMEOUT ) + PostMessage( plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0 ); + + return; + } + + currentSongLength = MulDiv( (int)info.frames, 1000, info.samplerate ); // TODO: is this correct? + switch ( info.format & SF_FORMAT_SUBMASK ) + { + case SF_FORMAT_FLOAT: + case SF_FORMAT_DOUBLE: + sf_command( sndFile, SFC_SET_SCALE_FLOAT_INT_READ, NULL, SF_TRUE ); + break; + } + + bits = CalcBits(); + + size_t config_bits = AGAVE_API_CONFIG->GetUnsigned( playbackConfigGroupGUID, L"bits", 16 ); + if ( config_bits == 16 && config_bits < (size_t)bits ) + bits = (int)config_bits; + + if ( bits < 16 && config_upsample8bit ) + bits = 16; + + int latency = plugin.outMod->Open( info.samplerate, info.channels, bits, -1, -1 ); + if ( latency < 0 ) + { + sf_close( sndFile ); + if ( reader ) + DestroyReader( reader ); + + reader = 0; + sndFile = NULL; + + if ( WaitForSingleObject( kill, 200 ) == WAIT_TIMEOUT ) + PostMessage( plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0 ); + + return; + } + + frameSize = ( bits / 8 ) * info.channels; + + plugin.SAVSAInit( latency, info.samplerate ); + plugin.VSASetInfo( info.samplerate, info.channels ); + + int bitrate = CalcBitRate( &info ); + + plugin.SetInfo( bitrate, info.samplerate / 1000, info.channels, 1 ); + plugin.is_seekable = info.seekable; + + plugin.outMod->SetVolume( volume ); + plugin.outMod->SetPan( pan ); + + size_t requiredBufferSize = 576 * frameSize * 2; // * 2 for dsp bullshit + if ( requiredBufferSize > bufferSize ) + { + free( audioBuffer ); + + audioBuffer = (char *)calloc( requiredBufferSize, sizeof( char ) ); + bufferSize = requiredBufferSize; + } + + SetEvent( running ); +} + +void CALLBACK APCStop( ULONG_PTR p_data ) +{ + if ( sndFile ) + { + sf_close( sndFile ); + if ( reader ) + DestroyReader( reader ); + + reader = 0; + sndFile = NULL; + + } + + ResetEvent( running ); + SetEvent( stopped ); +} + + +void Kill() +{ + SetEvent( kill ); +} + +void AudioThreadInit() +{ + DWORD id; + + kill = CreateEvent( NULL, TRUE, FALSE, NULL ); + running = CreateEvent( NULL, TRUE, FALSE, NULL ); + stopped = CreateEvent( NULL, FALSE, FALSE, NULL ); + audioThread = CreateThread( NULL, 0, ThreadProcedure, 0, 0, &id ); + + if ( audioThread ) + SetThreadPriority( audioThread, (int)AGAVE_API_CONFIG->GetInt( playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST ) ); +} + +void AudioThreadQuit() +{ + free( audioBuffer ); + audioBuffer = 0; + + bufferSize = 0; + + CloseHandle( running ); + running = 0; + + CloseHandle( kill ); + kill = 0; + + CloseHandle( stopped ); + stopped = 0; + + CloseHandle( audioThread ); + audioThread = 0; +} + + +DWORD WINAPI ThreadProcedure( void *data ) +{ + DWORD result; + sf_count_t framesRead; + while ( true ) + { + result = WaitForMultipleObjectsEx( 2, events, FALSE, INFINITE, TRUE ); + if ( result == WAIT_OBJECT_0 ) // kill thread + return 0; + + if ( result == ( WAIT_OBJECT_0 + 1 ) ) + { + if ( endOfFile ) // if we hit the end of file previously ... + { + if ( plugin.outMod->IsPlaying() ) // see if we're still going + SleepEx( 10, TRUE ); // sleep for a bit + else // yay done playing + { + PostMessage( plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0 ); // tell winamp we're stopped + // don't shut down completely yet (mpegeof will trigger a call to stop) + ResetEvent( running ); // but we can at least sit in waitformultipleobjects ... + } + } + else if ( plugin.outMod->CanWrite() > ( 576 * frameSize ) ) + { + switch ( bits ) + { + case 16: + framesRead = sf_readf_short( sndFile, (short *)audioBuffer, 576 ); + break; + case 32: + framesRead = sf_readf_int( sndFile, (int *)audioBuffer, 576 ); + break; + default: + framesRead = sf_read_raw( sndFile, (int *)audioBuffer, 576 * frameSize ) / frameSize; + break; + } + + + if ( framesRead == 0 ) + { + endOfFile = 1; + + plugin.outMod->Write( NULL, 0 ); + } + else + { + framesRead = plugin.dsp_dosamples( (short *)audioBuffer, (int)framesRead, bits, info.channels, info.samplerate ); + if ( framesRead >= 576 ) + { + int timestamp = plugin.outMod->GetWrittenTime(); + + plugin.SAAddPCMData( (char *)audioBuffer, info.channels, bits, timestamp ); + plugin.VSAAddPCMData( (char *)audioBuffer, info.channels, bits, timestamp ); + } + + plugin.outMod->Write( audioBuffer, (int)framesRead * frameSize ); + } + } + else + { + SleepEx( 10, TRUE ); + } + } + } +} diff --git a/Src/Plugins/Input/in_wave/AudioThread.h b/Src/Plugins/Input/in_wave/AudioThread.h new file mode 100644 index 00000000..5679e44a --- /dev/null +++ b/Src/Plugins/Input/in_wave/AudioThread.h @@ -0,0 +1,19 @@ +#ifndef NULLSOFT_AUDIOTHREADH +#define NULLSOFT_AUDIOTHREADH + +#include <windows.h> + +VOID CALLBACK APCSeek( ULONG_PTR p_data ); +VOID CALLBACK APCPause( ULONG_PTR p_data ); +VOID CALLBACK APCStart( ULONG_PTR p_data ); +VOID CALLBACK APCStop( ULONG_PTR p_data ); + +void Kill(); +void AudioThreadInit(); +void AudioThreadQuit(); + +extern HANDLE audioThread; +extern HANDLE stopped; +extern HANDLE events[2]; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wave/ExtendedRead.cpp b/Src/Plugins/Input/in_wave/ExtendedRead.cpp new file mode 100644 index 00000000..44bed186 --- /dev/null +++ b/Src/Plugins/Input/in_wave/ExtendedRead.cpp @@ -0,0 +1,80 @@ +#include "main.h" +#include <stddef.h> + +struct ExtendedRead +{ + SF_INFO info; + SNDFILE *soundFile; + int bits; + int frameSize; +}; + +extern "C" +{ + //returns handle!=0 if successful, 0 if error + //size will return the final nb of bytes written to the output, -1 if unknown + __declspec( dllexport ) intptr_t winampGetExtendedRead_openW( const wchar_t *fn, int *size, int *bps, int *nch, int *srate ) + { + ExtendedRead *extRead = (ExtendedRead *)calloc( 1, sizeof( ExtendedRead ) ); + + extRead->info.format = 0; + extRead->soundFile = sf_wchar_open( fn, SFM_READ, &extRead->info ); + if ( !extRead->soundFile ) + { + free( extRead ); + return 0; + } + + switch ( extRead->info.format & SF_FORMAT_SUBMASK ) + { + case SF_FORMAT_FLOAT: + case SF_FORMAT_DOUBLE: + sf_command( extRead->soundFile, SFC_SET_SCALE_FLOAT_INT_READ, NULL, SF_TRUE ); + break; + } + + extRead->bits = 16; // TODO: calculate bits per sample (what we want to use, not necessarily what the file has) + + *bps = extRead->bits; + *nch = extRead->info.channels; + *srate = extRead->info.samplerate; + + extRead->frameSize = ( extRead->bits / 8 ) * extRead->info.channels; + + *size = (int)extRead->info.frames * extRead->frameSize; // TODO: is this correct? + + return (intptr_t)extRead; + } + + //returns nb of bytes read. -1 if read error (like CD ejected). if (ret<len), EOF is assumed + __declspec( dllexport ) intptr_t winampGetExtendedRead_getData( intptr_t handle, char *dest, int len, int *killswitch ) + { + ExtendedRead *extRead = (ExtendedRead *)handle; + + sf_count_t framesRead = sf_readf_short( extRead->soundFile, (short *)dest, len / extRead->frameSize ); + + return (int)framesRead * extRead->frameSize; + } + + // return nonzero on success, zero on failure. + __declspec( dllexport ) int winampGetExtendedRead_setTime( intptr_t handle, int time_in_ms ) + { + ExtendedRead *extRead = (ExtendedRead *)handle; + if ( !extRead->info.seekable ) + return 0; + + int frames = MulDiv( time_in_ms, extRead->info.samplerate, 1000 ); // TODO: verify calculation + + sf_seek( extRead->soundFile, frames, SEEK_SET ); + + return 1; + } + + __declspec( dllexport ) void winampGetExtendedRead_close( intptr_t handle ) + { + ExtendedRead *extRead = (ExtendedRead *)handle; + sf_close( extRead->soundFile ); + + free( extRead ); + } +} diff --git a/Src/Plugins/Input/in_wave/RawReader.cpp b/Src/Plugins/Input/in_wave/RawReader.cpp new file mode 100644 index 00000000..c20cb85f --- /dev/null +++ b/Src/Plugins/Input/in_wave/RawReader.cpp @@ -0,0 +1,86 @@ +#include "main.h" +#include "RawReader.h" +#include <shlwapi.h> + /* +bool IsMyExtension(const wchar_t *filename) +{ + const wchar_t *ext = PathFindExtension(filename); + if (ext && *ext) + { + ext++; + return fileTypes.GetAVType(ext) != -1; + } + return false; +}*/ + +int RawMediaReaderService::CreateRawMediaReader(const wchar_t *filename, ifc_raw_media_reader **out_reader) +{ + // TODO:if (IsMyExtension(filename)) + if (!_wcsicmp(L".WAV", PathFindExtensionW(filename))) + { + RawMediaReader *raw_reader = new RawMediaReader(); + if (!raw_reader) + { + return NErr_OutOfMemory; + } + + int ret = raw_reader->Initialize(filename); + if (ret != NErr_Success) + { + delete raw_reader; + return ret; + } + + *out_reader = raw_reader; + return NErr_Success; + } + else + { + return NErr_False; + } +} + +#define CBCLASS RawMediaReaderService +START_DISPATCH; +CB( CREATERAWMEDIAREADER, CreateRawMediaReader ) +END_DISPATCH; +#undef CBCLASS + + +RawMediaReader::~RawMediaReader() +{ + if (soundFile) + sf_close(soundFile); +} + +int RawMediaReader::Initialize(const wchar_t *filename) +{ + info.format = 0; + soundFile = sf_wchar_open(filename, SFM_READ, &info); + if (!soundFile) + return NErr_FileNotFound; + + return NErr_Success; +} + +int RawMediaReader::Read(void *out_buffer, size_t buffer_size, size_t *bytes_read) +{ + sf_count_t sf_read = sf_read_raw(soundFile, out_buffer, buffer_size); + if (sf_read == 0) + return NErr_EndOfFile; + *bytes_read = (size_t)sf_read; + return NErr_Success; +} + +size_t RawMediaReader::Release() +{ + delete this; + return 0; +} + +#define CBCLASS RawMediaReader +START_DISPATCH; +CB(RELEASE, Release); +CB(RAW_READ, Read); +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wave/RawReader.h b/Src/Plugins/Input/in_wave/RawReader.h new file mode 100644 index 00000000..55697cf2 --- /dev/null +++ b/Src/Plugins/Input/in_wave/RawReader.h @@ -0,0 +1,39 @@ +#pragma once +#include "../Agave/DecodeFile/svc_raw_media_reader.h" +#include "../Agave/DecodeFile/ifc_raw_media_reader.h" +#include "main.h" + +// {4FB808DC-C327-4999-9822-BDDDE20F44B0} +static const GUID sndfile_raw_reader_guid = +{ 0x4fb808dc, 0xc327, 0x4999, { 0x98, 0x22, 0xbd, 0xdd, 0xe2, 0xf, 0x44, 0xb0 } }; + + +class RawMediaReaderService : public svc_raw_media_reader +{ +public: + static const char *getServiceName() { return "SndFile Raw Reader"; } + static GUID getServiceGuid() { return sndfile_raw_reader_guid; } + + int CreateRawMediaReader(const wchar_t *filename, ifc_raw_media_reader **reader); + +protected: + RECVS_DISPATCH; +}; + +class RawMediaReader : public ifc_raw_media_reader +{ +public: + RawMediaReader() {} + ~RawMediaReader(); + + int Initialize(const wchar_t *filename); + int Read(void *buffer, size_t buffer_size, size_t *bytes_read); + size_t Release(); + +protected: + RECVS_DISPATCH; + +private: + SNDFILE *soundFile = NULL; + SF_INFO info; +}; diff --git a/Src/Plugins/Input/in_wave/VirtualIO.cpp b/Src/Plugins/Input/in_wave/VirtualIO.cpp new file mode 100644 index 00000000..b09c2523 --- /dev/null +++ b/Src/Plugins/Input/in_wave/VirtualIO.cpp @@ -0,0 +1,98 @@ +#include "VirtualIO.h" +#include "api__in_wave.h" +#include <api/service/waservicefactory.h> +#include "../nu/AutoWide.h" +#include <assert.h> +#include <windows.h> + +// TODO: extend this to use api_filereader +// instead of just the HTTP reader directly +// then we could use this is a workaround for unicode filenames + +sf_count_t VirtualGetFileLength( void *user_data ) +{ + svc_fileReader *reader = (svc_fileReader *)user_data; + + return reader->getLength(); +} + +sf_count_t VirtualSeek( sf_count_t offset, int whence, void *user_data ) +{ + svc_fileReader *reader = (svc_fileReader *)user_data; + switch ( whence ) + { + case SEEK_SET: + reader->seek( offset ); + break; + case SEEK_CUR: + { + uint64_t cur = reader->getPos(); + reader->seek( offset + cur ); + } + break; + case SEEK_END: + { + uint64_t total = reader->getLength(); + reader->seek( total + offset ); + } + break; + + } + + return reader->getPos(); +} + +sf_count_t VirtualRead( void *ptr, sf_count_t count, void *user_data ) +{ + svc_fileReader *reader = (svc_fileReader *)user_data; + + return reader->read( (int8_t *)ptr, (size_t)count ); +} + +sf_count_t VirtualWrite( const void *ptr, sf_count_t count, void *user_data ) +{ + svc_fileReader *reader = (svc_fileReader *)user_data; + + return reader->write( (int8_t *)ptr, (size_t)count ); +} + +sf_count_t VirtualTell( void *user_data ) +{ + svc_fileReader *reader = (svc_fileReader *)user_data; + + return reader->getPos(); +} + +SF_VIRTUAL_IO httpIO = +{ + VirtualGetFileLength, + VirtualSeek, + VirtualRead, + VirtualWrite, + VirtualTell +}; + +static const GUID HTTPReaderGUID = +{ 0xbc10fa00, 0x53f5, 0x4032, { 0xa0, 0x09, 0x2, 0x2b, 0x87, 0xec, 0x34, 0x04 } }; + +void *CreateReader( const wchar_t *url ) +{ + static waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid( HTTPReaderGUID ); + if ( sf ) + { + svc_fileReader *l_reader = (svc_fileReader *)sf->getInterface(); + if ( l_reader ) + l_reader->open( url ); + + return l_reader; + } + else + return 0; +} + +void DestroyReader( void *reader ) +{ + static waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid( HTTPReaderGUID ); + if ( sf ) + sf->releaseInterface( reader ); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wave/VirtualIO.h b/Src/Plugins/Input/in_wave/VirtualIO.h new file mode 100644 index 00000000..00be557d --- /dev/null +++ b/Src/Plugins/Input/in_wave/VirtualIO.h @@ -0,0 +1,10 @@ +#ifndef NULLSOFT_IN_WAVE_VIRTUALIO_H +#define NULLSOFT_IN_WAVE_VIRTUALIO_H +#include "main.h" +#include <api/service/svcs/svc_fileread.h> + +extern SF_VIRTUAL_IO httpIO, unicode_io; +void *CreateReader(const wchar_t *url); +void DestroyReader(void *reader); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wave/api__in_wave.h b/Src/Plugins/Input/in_wave/api__in_wave.h new file mode 100644 index 00000000..5bd2eb75 --- /dev/null +++ b/Src/Plugins/Input/in_wave/api__in_wave.h @@ -0,0 +1,10 @@ +#ifndef NULLSOFT_IN_WAVE_API_H +#define NULLSOFT_IN_WAVE_API_H + +#include "../Agave/Config/api_config.h" +extern api_config *configApi; +#define AGAVE_API_CONFIG configApi + +#define WASABI_API_SVC plugin.service + +#endif // !NULLSOFT_IN_WAVE_API_H
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wave/config.cpp b/Src/Plugins/Input/in_wave/config.cpp new file mode 100644 index 00000000..9e5a82af --- /dev/null +++ b/Src/Plugins/Input/in_wave/config.cpp @@ -0,0 +1,128 @@ +#include "main.h" +#include "config.h" +#include "resource.h" + +char config_extensions[1024] = {0}; +bool config_upsample8bit=false; + +static int ExtensionInList(HWND hwndDlg, int id, const char *string) +{ + return (int)SendMessageA(GetDlgItem(hwndDlg, id), LB_FINDSTRINGEXACT, 0, (LPARAM)string); +} + +void FillExtensionList( HWND hwndDlg ) +{ + int numTypes; + sf_command( 0, SFC_GET_FORMAT_MAJOR_COUNT, &numTypes, sizeof( numTypes ) ); + SF_FORMAT_INFO info; + for ( int i = 0; i < numTypes; i++ ) + { + info.format = i; + sf_command( 0, SFC_GET_FORMAT_MAJOR, &info, sizeof( info ) ); + if ( !_strcmpi( info.extension, "mpc" ) ) + continue; + + if ( ExtensionInList( hwndDlg, IDC_EXTENSION_LIST, info.extension ) == LB_ERR ) + { + LRESULT index = SendMessageA( GetDlgItem( hwndDlg, IDC_EXTENSION_LIST ), LB_ADDSTRING, 0, (LPARAM)info.extension ); + if ( ExtensionExists( info.extension, config_extensions ) ) + SendMessage( GetDlgItem( hwndDlg, IDC_EXTENSION_LIST ), LB_SETSEL, TRUE, index ); + } + } +} + +void FillExtensionsEditControl( HWND hwndDlg ) +{ + char extensions[ 256 ] = ""; + char temp[ 20 ] = { 0 }; + + char *s = config_extensions; + + while ( s && *s ) + { + lstrcpynA( temp, s, 20 ); + + char *scan = temp; + while ( scan && *scan && *scan != ';' ) + scan = CharNextA( scan ); + + *scan = 0; + + if ( ExtensionInList( hwndDlg, IDC_EXTENSION_LIST, temp ) == LB_ERR ) + { + if ( *extensions ) + lstrcatA( extensions, ";" ); + + lstrcatA( extensions, temp ); + } + + s += lstrlenA( temp ); + if ( *s == ';' ) + s = CharNextA( s ); + } + + SetDlgItemTextA( hwndDlg, IDC_ADDITIONAL_EXTENSIONS, extensions ); +} + +void Preferences_Init( HWND hwndDlg ) +{ + SendMessageA( GetDlgItem( hwndDlg, IDC_OUTPUTBITS ), CB_ADDSTRING, 0, (LPARAM)"16 bit" ); // TODO: string table + SendMessageA( GetDlgItem( hwndDlg, IDC_OUTPUTBITS ), CB_ADDSTRING, 0, (LPARAM)"32 bit" ); // TODO: string table + + FillExtensionList( hwndDlg ); + FillExtensionsEditControl( hwndDlg ); +} + + +void Preferences_OnOK( HWND hwndDlg ) +{ + config_extensions[ 0 ] = 0; + LRESULT num = SendMessage( GetDlgItem( hwndDlg, IDC_EXTENSION_LIST ), LB_GETCOUNT, 0, 0 ); + for ( int i = 0; i < num; i++ ) + { + if ( SendMessage( GetDlgItem( hwndDlg, IDC_EXTENSION_LIST ), LB_GETSEL, i, 0 ) > 0 ) + { + char thisExtension[ 256 ] = { 0 }; + if ( config_extensions[ 0 ] ) + lstrcatA( config_extensions, ";" ); + + SendMessageA( GetDlgItem( hwndDlg, IDC_EXTENSION_LIST ), LB_GETTEXT, i, (LPARAM)thisExtension ); + lstrcatA( config_extensions, thisExtension ); + } + } + + char additional[ 1024 ] = { 0 }; + GetDlgItemTextA( hwndDlg, IDC_ADDITIONAL_EXTENSIONS, additional, 1024 ); + if ( additional[ 0 ] ) + { + if ( config_extensions[ 0 ] ) + lstrcatA( config_extensions, ";" ); + + lstrcatA( config_extensions, additional ); + } + + SetFileExtensions( config_extensions ); +} + +BOOL CALLBACK PreferencesDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + switch ( uMsg ) + { + case WM_INITDIALOG: + Preferences_Init( hwndDlg ); + break; + case WM_COMMAND: + switch ( LOWORD( wParam ) ) + { + case IDOK: + Preferences_OnOK( hwndDlg ); + EndDialog( hwndDlg, 0 ); + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + } + } + + return 0; +} diff --git a/Src/Plugins/Input/in_wave/config.h b/Src/Plugins/Input/in_wave/config.h new file mode 100644 index 00000000..9163c8ab --- /dev/null +++ b/Src/Plugins/Input/in_wave/config.h @@ -0,0 +1,6 @@ +#ifndef NULLSOFT_IN_WAVE_CONFIG_H +#define NULLSOFT_IN_WAVE_CONFIG_H + +extern char config_extensions[1024]; +extern bool config_upsample8bit; +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wave/extensions.cpp b/Src/Plugins/Input/in_wave/extensions.cpp new file mode 100644 index 00000000..998c734b --- /dev/null +++ b/Src/Plugins/Input/in_wave/extensions.cpp @@ -0,0 +1,183 @@ +#include "main.h" +#include "resource.h" +#include "config.h" +#include "assert.h" +#include "../Agave/Language/api_language.h" +#include "../nu/AutoWide.h" + +#include <strsafe.h> + +char defaultExtensions[1024] = {0}; +char fileExtensionsString[1200] = {0}; + +int SizeAvailableTypes() +{ + int size = 0; + int numTypes = 0; + + sf_command( 0, SFC_GET_FORMAT_MAJOR_COUNT, &numTypes, sizeof( numTypes ) ); + + SF_FORMAT_INFO info; + for ( int i = 0; i < numTypes; i++ ) + { + info.format = i; + sf_command( 0, SFC_GET_FORMAT_MAJOR, &info, sizeof( info ) ); + size += lstrlenA( info.extension ) + 1 /* ; or \0 */; + //size+=lstrlen(info.name) + 1 /* \0 */; + } + + return size; +} + +int ExtensionExists( const char *ext, const char *extensionList ) +{ + size_t len = lstrlenA( ext ); + char temp[ 20 ] = { 0 }; + const char *s = extensionList; + + while ( s && *s ) + { + lstrcpynA( temp, s, 20 ); + + char *scan = temp; + while ( scan && *scan && *scan != ';' ) + scan = CharNextA( scan ); + + *scan = 0; + + if ( !lstrcmpiA( temp, ext ) ) + return 1; + + s += lstrlenA( temp ); + if ( *s == ';' ) + s = CharNextA( s ); + } + + return 0; +} + +// TODO: sort default extension list +void BuildDefaultExtensions() +{ + // if we want a light extension list, here it is: lstrcpyn(defaultExtensions, "WAV;AIFF;VOC;AU;AIF;AIFC;SND"); + int size = SizeAvailableTypes() + 1; + assert( size < 1024 ); // TODO + + char *extPtr = defaultExtensions; + size_t extPtrCch = 1023; + extPtr[ 0 ] = 0; + + int numTypes = 0; + + sf_command( 0, SFC_GET_FORMAT_MAJOR_COUNT, &numTypes, sizeof( numTypes ) ); + + SF_FORMAT_INFO info = { 0 }; + + for ( int i = 0; i < numTypes; i++ ) + { + info.format = i; + + sf_command( 0, SFC_GET_FORMAT_MAJOR, &info, sizeof( info ) ); + if ( !_strcmpi( info.extension, "mpc" ) ) + continue; + + if ( ExtensionExists( info.extension, defaultExtensions ) ) + continue; + + if ( *CharPrevA( defaultExtensions, extPtr ) ) + StringCchCatExA( extPtr, extPtrCch, ";", &extPtr, &extPtrCch, 0 ); + + StringCchCatExA( extPtr, extPtrCch, info.extension, &extPtr, &extPtrCch, 0 ); + } +} + +void SetFileExtensions( const char *extList ) +{ + ZeroMemory( &fileExtensionsString, sizeof( fileExtensionsString ) ); + + char *end = 0; + + StringCchCopyExA( fileExtensionsString, 1200, extList, &end, 0, 0 ); + StringCchCopyExA( end + 1, 1200, WASABI_API_LNGSTRING( IDS_SOUND_FILES ), 0, 0, 0 ); + + plugin.FileExtensions = fileExtensionsString; +} + +static const wchar_t *pExtList[]={L"AIFF",L"AIF",L"AU",L"AVR",L"CAF",L"HTK",L"IFF",L"MAT",L"PAF",L"PVF",L"RAW",L"RF64",L"SD2",L"SDS",L"SF",L"VOC",L"W64",L"WAV",L"WVE",L"XI"}; +static const int pExtDescIdList[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}; +static const int pExtDescList[] = +{ + IDS_FAMILY_STRING_AAIF, + IDS_FAMILY_STRING_AAIF, + IDS_FAMILY_STRING_AU, + IDS_FAMILY_STRING_AVR, + IDS_FAMILY_STRING_ACA, + IDS_FAMILY_STRING_HMMTS, + IDS_FAMILY_STRING_AAIF, + IDS_FAMILY_STRING_MATLAB, + IDS_FAMILY_STRING_PARIS, + IDS_FAMILY_STRING_PVF, + IDS_FAMILY_STRING_HEADERLESS_RAW, + IDS_FAMILY_STRING_RF64, + IDS_FAMILY_STRING_SDII, + IDS_FAMILY_STRING_RAW_MIDI_SAMPLE_DUMP, + IDS_FAMILY_STRING_IRCAM, + IDS_FAMILY_STRING_CVOC, + IDS_FAMILY_STRING_SFWOW64, + IDS_FAMILY_STRING_MSWAV, + IDS_FAMILY_STRING_MSWAV, + IDS_FAMILY_STRING_FT2WAV, +}; + +BOOL GetExtensionName( LPCWSTR pszExt, LPWSTR pszDest, INT cchDest ) +{ + INT maxCount, index; + DWORD lcid; + SF_FORMAT_INFO info; + + lcid = MAKELCID( MAKELANGID( LANG_ENGLISH, SUBLANG_ENGLISH_US ), SORT_DEFAULT ); + // first scan our list if there is no entry ask libsnd. we do this cause libsnd format description sucks + + for ( index = sizeof( pExtList ) / sizeof( wchar_t * ) - 1; index >= 0 && CSTR_EQUAL != CompareStringW( lcid, NORM_IGNORECASE, pszExt, -1, pExtList[ index ], -1 ); index-- ); + if ( index >= 0 && S_OK == StringCchCopyW( pszDest, cchDest, WASABI_API_LNGSTRINGW( pExtDescList[ pExtDescIdList[ index ] ] ) ) ) return TRUE; + + sf_command( 0, SFC_GET_FORMAT_MAJOR_COUNT, &maxCount, sizeof( maxCount ) ); + for ( index = 0; index < maxCount; index++ ) + { + info.format = index; + sf_command( 0, SFC_GET_FORMAT_MAJOR, &info, sizeof( info ) ); + if ( CSTR_EQUAL == CompareStringW( lcid, NORM_IGNORECASE, pszExt, -1, AutoWide( info.extension ), -1 ) ) + { + INT len1, len2; + len1 = lstrlenA( info.extension ); + len2 = lstrlenA( info.name ); + if ( len2 > len1 ) + { + if ( CSTR_EQUAL == CompareStringA( lcid, NORM_IGNORECASE, info.extension, len1, info.name, len1 ) ) + { + info.name += len1; + len2 -= len1; + } + + if ( len2 > 0 && ' ' == *info.name ) + { + info.name++; + len2--; + } + + if ( len2 > 0 && '(' == *info.name ) + { + info.name++; + len2--; + } + + if ( len2 > 0 && ')' == info.name[ len2 - 1 ] ) + len2--; + } + + return ( S_OK == StringCchCopyNW( pszDest, cchDest, AutoWide( info.name ), len2 ) ); + } + } + + return FALSE; +} diff --git a/Src/Plugins/Input/in_wave/in_wave.rc b/Src/Plugins/Input/in_wave/in_wave.rc new file mode 100644 index 00000000..7a674704 --- /dev/null +++ b/Src/Plugins/Input/in_wave/in_wave.rc @@ -0,0 +1,144 @@ +// 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_CONFIG DIALOGEX 0, 0, 125, 244 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Waveform Decoder" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Built-in Extensions",IDC_STATIC,4,4,99,8 + LISTBOX IDC_EXTENSION_LIST,4,16,117,163,LBS_MULTIPLESEL | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + LTEXT "Additional Extensions\nseparated with semi-colons\ne.g. aifc;snd",IDC_STATIC,4,183,117,24 + EDITTEXT IDC_ADDITIONAL_EXTENSIONS,4,211,117,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,4,227,50,13 + PUSHBUTTON "Cancel",IDCANCEL,71,227,50,13 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_CONFIG, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 121 + TOPMARGIN, 4 + BOTTOMMARGIN, 240 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_WAVEFORM_DECODER "Nullsoft Waveform Decoder v%s" + 65535 "{96374982-0142-41a5-AEDE-244505C45D30}" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_WAVEFORM_DECODER_OLD "Nullsoft Waveform Decoder" + IDS_SOUND_FILES "Sound Files" + IDS_INFO_STR_FMT "%s\n%u Channel(s)\n%u Hz" + IDS_FILE_INFORMATION "Waveform File Information" + IDS_FAMILY_STRING_AAIF "Apple Audio Interchange File" + IDS_FAMILY_STRING_AU "AU Format Sound File" + IDS_FAMILY_STRING_AVR "Audio Visual Research File" + IDS_FAMILY_STRING_ACA "Apple Core Audio Format" + IDS_FAMILY_STRING_HMMTS "Hidden Markov Model Toolkit Speech Recognition File" + IDS_FAMILY_STRING_MATLAB "Matlab Audio Format" + IDS_FAMILY_STRING_PARIS "Paris Audio File" + IDS_FAMILY_STRING_PVF "Portable Voice Format File" + IDS_FAMILY_STRING_HEADERLESS_RAW "Headerless RAW Waveform" + IDS_FAMILY_STRING_SDII "Sound Designer II Audio File" +END + +STRINGTABLE +BEGIN + IDS_FAMILY_STRING_RAW_MIDI_SAMPLE_DUMP + "Raw Midi Sample Dump Standard File" + IDS_FAMILY_STRING_IRCAM "IRCAM Sound File" + IDS_FAMILY_STRING_CVOC "Creative VOC Format" + IDS_FAMILY_STRING_SFWOW64 "Soundforge Wow 64 Format" + IDS_FAMILY_STRING_MSWAV "Microsoft Wave Sound Format" + IDS_FAMILY_STRING_FT2WAV "Fasttracker 2 Waveform" + IDS_ABOUT_TEXT "%s\n© 2005-2023 Winamp SA\nWritten by: Ben Allison\nBuild date: %hs\n\nUses %hs\n© Erik de Castro Lopo" + IDS_FAMILY_STRING_RF64 "RIFF 64 Broadcast Wave Format" +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_wave/in_wave.sln b/Src/Plugins/Input/in_wave/in_wave.sln new file mode 100644 index 00000000..d17d6f36 --- /dev/null +++ b/Src/Plugins/Input/in_wave/in_wave.sln @@ -0,0 +1,40 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29424.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_wave", "in_wave.vcxproj", "{03366121-F8F5-4845-8EDD-B0EEC955282F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsndfile", "..\libsndfile\libsndfile.vcxproj", "{6BFA7E64-2074-4885-A685-2772AF31E288}" +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 + {03366121-F8F5-4845-8EDD-B0EEC955282F}.Debug|Win32.ActiveCfg = Debug|Win32 + {03366121-F8F5-4845-8EDD-B0EEC955282F}.Debug|Win32.Build.0 = Debug|Win32 + {03366121-F8F5-4845-8EDD-B0EEC955282F}.Debug|x64.ActiveCfg = Debug|x64 + {03366121-F8F5-4845-8EDD-B0EEC955282F}.Debug|x64.Build.0 = Debug|x64 + {03366121-F8F5-4845-8EDD-B0EEC955282F}.Release|Win32.ActiveCfg = Release|Win32 + {03366121-F8F5-4845-8EDD-B0EEC955282F}.Release|Win32.Build.0 = Release|Win32 + {03366121-F8F5-4845-8EDD-B0EEC955282F}.Release|x64.ActiveCfg = Release|x64 + {03366121-F8F5-4845-8EDD-B0EEC955282F}.Release|x64.Build.0 = Release|x64 + {6BFA7E64-2074-4885-A685-2772AF31E288}.Debug|Win32.ActiveCfg = Debug|Win32 + {6BFA7E64-2074-4885-A685-2772AF31E288}.Debug|Win32.Build.0 = Debug|Win32 + {6BFA7E64-2074-4885-A685-2772AF31E288}.Debug|x64.ActiveCfg = Debug|x64 + {6BFA7E64-2074-4885-A685-2772AF31E288}.Debug|x64.Build.0 = Debug|x64 + {6BFA7E64-2074-4885-A685-2772AF31E288}.Release|Win32.ActiveCfg = Release|Win32 + {6BFA7E64-2074-4885-A685-2772AF31E288}.Release|Win32.Build.0 = Release|Win32 + {6BFA7E64-2074-4885-A685-2772AF31E288}.Release|x64.ActiveCfg = Release|x64 + {6BFA7E64-2074-4885-A685-2772AF31E288}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E0AC775B-F8AF-40E3-A215-1D903C9B8C3F} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_wave/in_wave.vcxproj b/Src/Plugins/Input/in_wave/in_wave.vcxproj new file mode 100644 index 00000000..f502afbe --- /dev/null +++ b/Src/Plugins/Input/in_wave/in_wave.vcxproj @@ -0,0 +1,282 @@ +<?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>{03366121-F8F5-4845-8EDD-B0EEC955282F}</ProjectGuid> + <RootNamespace>in_wave</RootNamespace> + <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" 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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|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> + <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"> + <VcpkgEnableManifest>false</VcpkgEnableManifest> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;IN_WAVE_EXPORTS;_WIN32_WINNT=0x601;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> + <DelayLoadDLLs>%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <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\ +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_WAVE_EXPORTS;_WIN32_WINNT=0x601;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> + <DelayLoadDLLs>%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <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\ +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> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;IN_WAVE_EXPORTS;_WIN32_WINNT=0x601;UNICODE_INPUT_PLUGIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <ObjectFileName>$(IntDir)</ObjectFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4244;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>%(DelayLoadDLLs)</DelayLoadDLLs> + <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> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;IN_WAVE_EXPORTS;_WIN32_WINNT=0x601;UNICODE_INPUT_PLUGIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <ObjectFileName>$(IntDir)</ObjectFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4244;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>%(DelayLoadDLLs)</DelayLoadDLLs> + <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> + <ProjectReference Include="..\..\..\replicant\jnetlib\jnetlib.vcxproj"> + <Project>{e105a0a2-7391-47c5-86ac-718003524c3d}</Project> + </ProjectReference> + <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj"> + <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <ClInclude Include="api__in_wave.h" /> + <ClInclude Include="AudioThread.h" /> + <ClInclude Include="config.h" /> + <ClInclude Include="main.h" /> + <ClInclude Include="RawReader.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="VirtualIO.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="AudioThread.cpp" /> + <ClCompile Include="config.cpp" /> + <ClCompile Include="ExtendedRead.cpp" /> + <ClCompile Include="extensions.cpp" /> + <ClCompile Include="main.cpp" /> + <ClCompile Include="RawReader.cpp" /> + <ClCompile Include="VirtualIO.cpp" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_wave.rc" /> + </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_wave/in_wave.vcxproj.filters b/Src/Plugins/Input/in_wave/in_wave.vcxproj.filters new file mode 100644 index 00000000..2729af0f --- /dev/null +++ b/Src/Plugins/Input/in_wave/in_wave.vcxproj.filters @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="AudioThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="config.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedRead.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="extensions.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="RawReader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="VirtualIO.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="api__in_wave.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="AudioThread.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="config.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="main.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="RawReader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="VirtualIO.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{5702576b-51d6-4db9-81de-3bbe5a177a43}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{29eaf111-75c2-4703-ae85-69f56a51561e}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{aeb6933e-bfc2-47ba-ad37-db67bbcf0ab5}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_wave.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wave/main.cpp b/Src/Plugins/Input/in_wave/main.cpp new file mode 100644 index 00000000..235f1616 --- /dev/null +++ b/Src/Plugins/Input/in_wave/main.cpp @@ -0,0 +1,437 @@ +//#define PLUGIN_NAME "Nullsoft Waveform Decoder" +#define PLUGIN_VERSION L"3.27" + +#include "../Winamp/in2.h" +#include "../Winamp/wa_ipc.h" +#include "main.h" +#include "AudioThread.h" +#include "resource.h" +#include "config.h" +#include "api__in_wave.h" +#include <shlwapi.h> +#include "../Agave/Language/api_language.h" +#include <api/service/waservicefactory.h> +#include "../nu/ns_wc.h" +#include "../nu/AutoWide.h" +#include "../nu/AutoCharFn.h" +#include "VirtualIO.h" +#include <strsafe.h> +#include "../nu/Singleton.h" +#include "RawReader.h" + +api_config *AGAVE_API_CONFIG = NULL; + +// wasabi based services for localisation support +api_language *WASABI_API_LNG = NULL; + +HINSTANCE WASABI_API_LNG_HINST = 0; +HINSTANCE WASABI_API_ORIG_HINST = 0; + +static RawMediaReaderService raw_media_reader_service; +static SingletonServiceFactory<svc_raw_media_reader, RawMediaReaderService> raw_factory; + +template <class api_T> +void ServiceBuild( api_T *&api_t, GUID factoryGUID_t ) +{ + if ( WASABI_API_SVC ) + { + waServiceFactory *factory = WASABI_API_SVC->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 ( WASABI_API_SVC && api_t ) + { + waServiceFactory *factory = WASABI_API_SVC->service_getServiceByGuid( factoryGUID_t ); + if ( factory ) + factory->releaseInterface( api_t ); + } + + api_t = NULL; +} + +volatile int currentSongLength = 0; +SNDFILE *sndFile = NULL; + +wchar_t curFile[MAX_PATH*4] = L""; + +char *INI_FILE; + +class SoundFile +{ +public: + SoundFile( const wchar_t *filename, int mode, SF_INFO *info ) + { + info->format = 0; + //reader = CreateUnicodeReader(filename); + //if (reader) + //sndFile = sf_open_virtual(&unicode_io, SFM_READ, info, reader); + sndFile = sf_wchar_open( filename, SFM_READ, info ); + } + ~SoundFile() + { + if ( sndFile ) + sf_close( sndFile ); + //if (reader) + //DestroyUnicodeReader(reader); + sndFile = NULL; + } + + operator SNDFILE *() { return sndFile; } + SNDFILE *operator ->() { return sndFile; } + operator bool() { return !!sndFile; } + bool operator !() { return !sndFile; } + + SNDFILE *sndFile = NULL; + //void *reader; +}; + +void Config( HWND hwnd ) +{ + WASABI_API_DIALOGBOXW( IDD_CONFIG, hwnd, PreferencesDialogProc ); +} + +int DoAboutMessageBox( HWND parent, wchar_t *title, wchar_t *message ) +{ + MSGBOXPARAMSW msgbx = { sizeof( MSGBOXPARAMSW ),0 }; + msgbx.lpszText = message; + msgbx.lpszCaption = title; + msgbx.lpszIcon = MAKEINTRESOURCEW( 102 ); + msgbx.hInstance = GetModuleHandle( 0 ); + msgbx.dwStyle = MB_USERICON; + msgbx.hwndOwner = parent; + + return MessageBoxIndirectW( &msgbx ); +} + +void About( HWND hwndParent ) +{ + wchar_t message[ 1024 ] = { 0 }, text[ 1024 ] = { 0 }; + char ver[ 128 ] = { 0 }; + + sf_command( 0, SFC_GET_LIB_VERSION, ver, 128 ); + + WASABI_API_LNGSTRINGW_BUF( IDS_NULLSOFT_WAVEFORM_DECODER_OLD, text, 1024 ); + + StringCchPrintfW( message, 1024, WASABI_API_LNGSTRINGW( IDS_ABOUT_TEXT ), plugin.description, __DATE__, ver ); + + DoAboutMessageBox( hwndParent, text, message ); +} + +int Init() +{ + if ( !IsWindow( plugin.hMainWindow ) ) + return IN_INIT_FAILURE; + + ServiceBuild( AGAVE_API_CONFIG, AgaveConfigGUID ); + + // loader so that we can get the localisation service api for use + ServiceBuild( WASABI_API_LNG, languageApiGUID ); + raw_factory.Register( WASABI_API_SVC, &raw_media_reader_service ); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG( plugin.hDllInstance, InWavLangGUID ); + + static wchar_t szDescription[ 256 ]; + StringCchPrintfW( szDescription, 256, WASABI_API_LNGSTRINGW( IDS_NULLSOFT_WAVEFORM_DECODER ), PLUGIN_VERSION ); + plugin.description = (char *)szDescription; + + INI_FILE = (char *)SendMessage( plugin.hMainWindow, WM_WA_IPC, 0, IPC_GETINIFILE ); + BuildDefaultExtensions(); + + GetPrivateProfileStringA( "in_wave", "extensions", defaultExtensions, config_extensions, 1024, INI_FILE ); + SetFileExtensions( config_extensions ); + + return IN_INIT_SUCCESS; +} + +void Quit() +{ + if ( lstrcmpiA( config_extensions, defaultExtensions ) ) + WritePrivateProfileStringA( "in_wave", "extensions", config_extensions, INI_FILE ); + else + WritePrivateProfileStringA( "in_wave", "extensions", 0, INI_FILE ); + + ServiceRelease( AGAVE_API_CONFIG, AgaveConfigGUID ); + WASABI_API_SVC->service_deregister( &raw_factory ); +} + +void GetFileInfo( const wchar_t *file, wchar_t *title, int *length_in_ms ) +{ + SNDFILE *tempSndFile = 0; + SF_INFO info; + info.format = 0; + const wchar_t *fn = ( file && file[ 0 ] ) ? file : curFile; + + tempSndFile = sf_wchar_open( fn, SFM_READ, &info ); + if ( tempSndFile ) + { + if ( length_in_ms ) + { + *length_in_ms = MulDiv( (int)info.frames, 1000, info.samplerate ); // TODO: is this correct? + if ( !file || !file[ 0 ] ) + currentSongLength = *length_in_ms; + } + + if ( title ) + { + const char *meta = sf_get_string( tempSndFile, SF_STR_TITLE ); + if ( meta && meta[ 0 ] ) + MultiByteToWideCharSZ( CP_UTF8, 0, meta, -1, title, GETFILEINFO_TITLE_LENGTH ); + else + { + lstrcpynW( title, fn, GETFILEINFO_TITLE_LENGTH ); + PathStripPathW( title ); + } + } + + sf_close( tempSndFile ); + } + else + { + *length_in_ms = -1; + if ( title ) + { + lstrcpynW( title, fn, GETFILEINFO_TITLE_LENGTH ); + PathStripPathW( title ); + } + } +} + +int InfoBox( const wchar_t *file, HWND hwndParent ) +{ + SNDFILE *metaFile = 0; + SF_INFO info; + info.format = 0; + metaFile = sf_wchar_open( file, SFM_READ, &info ); + if ( metaFile ) + { + SF_FORMAT_INFO formatInfo; + formatInfo.format = info.format & SF_FORMAT_SUBMASK; + sf_command( 0, SFC_GET_FORMAT_INFO, &formatInfo, sizeof( formatInfo ) ); + + char temp[ 1024 ] = { 0 }; + StringCchPrintfA( temp, 1024, WASABI_API_LNGSTRING( IDS_INFO_STR_FMT ), formatInfo.name, info.channels, info.samplerate ); + MessageBoxA( NULL, temp, WASABI_API_LNGSTRING( IDS_FILE_INFORMATION ), MB_OK ); + sf_close( metaFile ); + } + + return INFOBOX_UNCHANGED; +} + +int IsOurFile( const wchar_t *file ) +{ + return 0; +} + +int Play( const wchar_t *file ) +{ + AudioThreadInit(); + lstrcpynW( curFile, file, MAX_PATH * 4 ); + QueueUserAPC( APCStart, audioThread, (ULONG_PTR)curFile ); + + return 0; +} + +static int paused = 0; + +void Pause() +{ + paused = 1; + QueueUserAPC( APCPause, audioThread, (ULONG_PTR)1 ); +} + +void UnPause() +{ + paused = 0; + QueueUserAPC( APCPause, audioThread, (ULONG_PTR)0 ); +} + +int IsPaused() +{ + return paused; +} + +void Stop() +{ + QueueUserAPC( APCStop, audioThread, (ULONG_PTR)0 ); + + WaitForSingleObject( stopped, INFINITE ); + + plugin.outMod->Close(); + plugin.SAVSADeInit(); + + Kill(); + + WaitForSingleObject( audioThread, INFINITE ); + + AudioThreadQuit(); +} + +int GetLength() +{ + return currentSongLength; +} + +int GetOutputTime() +{ + if ( plugin.outMod ) + return plugin.outMod->GetOutputTime(); + else + return 0; +} + +void SetOutputTime( int time_in_ms ) +{ + QueueUserAPC( APCSeek, audioThread, (ULONG_PTR)time_in_ms ); +} + +int pan = 0; +int volume = -666; + +void SetVolume( int _volume ) +{ + volume = _volume; + if ( plugin.outMod ) + plugin.outMod->SetVolume( volume ); +} + +void SetPan( int _pan ) +{ + pan = _pan; + if ( plugin.outMod ) + plugin.outMod->SetPan( pan ); +} + +void EQSet( int on, char data[ 10 ], int preamp ) +{} + +In_Module plugin = { + IN_VER_RET, + "nullsoft(in_wave.dll)", + 0, + 0, + 0, + 1, + 1, + Config, + 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; +} + +inline bool KeywordMatch(const char *mainString, const char *keyword) +{ + return !lstrcmpiA(mainString, keyword); +} + +extern "C" __declspec( dllexport ) int winampGetExtendedFileInfoW( const wchar_t *fn, const char *data, wchar_t *dest, int destlen ) +{ + if ( KeywordMatch( data, "type" ) ) + { + StringCchCopyW( dest, destlen, L"0" ); + + return 1; + } + + if ( KeywordMatch( data, "family" ) ) + { + LPCWSTR ext = PathFindExtensionW( fn ); + if ( L'.' != *ext ) + return 0; + + return GetExtensionName( ++ext, dest, destlen ); + } + + if ( KeywordMatch( data, "mime" ) ) + { + LPCWSTR ext = PathFindExtensionW( fn ); + if ( ext && !_wcsicmp( ext, L".wav" ) ) + { + StringCchCopyW( dest, destlen, L"audio/wav" ); + + return 1; + } + + return 0; + } + + if ( !fn || ( fn && !fn[ 0 ] ) ) + return 0; + + SF_INFO info; + SoundFile metaFile( fn, SFM_READ, &info ); + if ( !metaFile ) + return 0; + + dest[ 0 ] = 0; + if ( KeywordMatch( data, "artist" ) ) + { + const char *meta = sf_get_string( metaFile, SF_STR_ARTIST ); + if ( meta ) + lstrcpynW( dest, AutoWide( meta ), destlen ); + } + else if ( KeywordMatch( data, "title" ) ) + { + const char *meta = sf_get_string( metaFile, SF_STR_TITLE ); + if ( meta ) + lstrcpynW( dest, AutoWide( meta ), destlen ); + } + else if ( KeywordMatch( data, "comment" ) ) + { + const char *meta = sf_get_string( metaFile, SF_STR_COMMENT ); + if ( meta ) + lstrcpynW( dest, AutoWide( meta ), destlen ); + } + else if ( KeywordMatch( data, "bitrate" ) ) + { + int br = CalcBitRate( &info ); + if ( br ) + StringCchPrintfW( dest, destlen, L"%d", br ); + } + else if ( KeywordMatch( data, "length" ) ) + { + uint64_t length = info.frames * 1000 / info.samplerate; + StringCchPrintfW( dest, destlen, L"%I64u", length ); + } + else + return 0; + + return 1; +} diff --git a/Src/Plugins/Input/in_wave/main.h b/Src/Plugins/Input/in_wave/main.h new file mode 100644 index 00000000..2614325f --- /dev/null +++ b/Src/Plugins/Input/in_wave/main.h @@ -0,0 +1,24 @@ +#ifndef NULLSOFT_IN_WAVE_MAINH +#define NULLSOFT_IN_WAVE_MAINH + +extern volatile int currentSongLength; +#define ENABLE_SNDFILE_WINDOWS_PROTOTYPES +#include "sndfile.h" +extern SNDFILE *sndFile; +#include "../Winamp/in2.h" +extern In_Module plugin; + +extern int pan, volume; + +#include <windows.h> +BOOL CALLBACK PreferencesDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +int ExtensionExists(const char *ext, const char *extensionList); + +void BuildDefaultExtensions(); + +extern char defaultExtensions[1024]; +void SetFileExtensions(const char *extList); +int CalcBitRate(const SF_INFO *info); +BOOL GetExtensionName(LPCWSTR pszExt, LPWSTR pszDest, INT cchDest); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wave/resource.h b/Src/Plugins/Input/in_wave/resource.h new file mode 100644 index 00000000..861b0d8e --- /dev/null +++ b/Src/Plugins/Input/in_wave/resource.h @@ -0,0 +1,47 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by in_wave.rc +// +#define IDS_NULLSOFT_WAVEFORM_DECODER_OLD 0 +#define IDS_WAVEFORM_DECODER 1 +#define IDS_ABOUT_STR 2 +#define IDS_SOUND_FILES 3 +#define IDS_INFO_STR_FMT 4 +#define IDS_STRING5 5 +#define IDS_WAVE_U_MS 6 +#define IDS_FAMILY_STRING_AAIF 6 +#define IDS_FAMILY_STRING_AU 7 +#define IDS_FAMILY_STRING_AVR 8 +#define IDS_FAMILY_STRING_ACA 9 +#define IDS_FAMILY_STRING_HMMTS 10 +#define IDS_FAMILY_STRING_MATLAB 11 +#define IDS_FAMILY_STRING_PARIS 12 +#define IDS_FAMILY_STRING_PVF 13 +#define IDS_FAMILY_STRING_HEADERLESS_RAW 14 +#define IDS_FAMILY_STRING_SDII 15 +#define IDS_FAMILY_STRING_RAW_MIDI_SAMPLE_DUMP 16 +#define IDS_FAMILY_STRING_IRCAM 17 +#define IDS_FAMILY_STRING_CVOC 18 +#define IDS_FAMILY_STRING_SFWOW64 19 +#define IDS_FAMILY_STRING_MSWAV 20 +#define IDS_FAMILY_STRING_FT2WAV 21 +#define IDS_ABOUT_TEXT 22 +#define IDS_FAMILY_STRING_RF64 23 +#define IDS_FILE_INFORMATION 24 +#define IDD_CONFIG 101 +#define IDC_EXTENSION_LIST 1001 +#define IDC_EDIT1 1002 +#define IDC_ADDITIONAL_EXTENSIONS 1002 +#define IDC_OUTPUTBITS 1003 +#define IDS_NULLSOFT_WAVEFORM_DECODER 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 25 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1004 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_wave/version.rc2 b/Src/Plugins/Input/in_wave/version.rc2 new file mode 100644 index 00000000..e7ecb3e9 --- /dev/null +++ b/Src/Plugins/Input/in_wave/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 3,27,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", "3,27,0,0" + VALUE "InternalName", "Nullsoft Waveform Decoder" + VALUE "LegalCopyright", "Copyright © 2005-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_wave.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_wmvdrm/ASXLoader.cpp b/Src/Plugins/Input/in_wmvdrm/ASXLoader.cpp new file mode 100644 index 00000000..485e0f77 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/ASXLoader.cpp @@ -0,0 +1,490 @@ +#include "main.h" +#include "api.h" +#include "ASXLoader.h" +#include <stdio.h> +#include "../nu/AutoWide.h" +#include "../xml/ifc_xmlreadercallback.h" +#include "../xml/obj_xml.h" +#include "api.h" +#include <api/service/waservicefactory.h> +#include "../../..\Components\wac_network\wac_network_http_receiver_api.h" +#include "../nu/AutoChar.h" +#include "../Winamp/strutil.h" +#include <strsafe.h> +#include "XMLString.h" + +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); +} + +class ASXInfo : public ifc_plentryinfo +{ +public: + ASXInfo() + { + memset( returnTemp, 0, sizeof( returnTemp ) ); + } + + const wchar_t *GetExtendedInfo( const wchar_t *parameter ) + { + if ( !_wcsicmp( parameter, L"context" ) ) + { + if ( isRadio ) + return L"radio"; + } + else if ( !_wcsicmp( parameter, L"repeat" ) ) + { + if ( repeat ) + { + StringCchPrintfW( returnTemp, 20, L"%d", repeat ); + + return returnTemp; + } + } + + return 0; + } + + bool isRadio = false; + int repeat = 0; + +protected: + RECVS_DISPATCH; + + wchar_t returnTemp[ 20 ]; +}; + +#define CBCLASS ASXInfo +START_DISPATCH; +CB( IFC_PLENTRYINFO_GETEXTENDEDINFO, GetExtendedInfo ) +END_DISPATCH; +#undef CBCLASS + +class ASXXML : public ifc_xmlreadercallback +{ +public: + ASXXML(ifc_playlistloadercallback *_playlist, const wchar_t *_root, obj_xml *_parser) : playlist(_playlist), rootPath(_root), parser(_parser) + { + } + + void OnFileHelper(ifc_playlistloadercallback *playlist, const wchar_t *filename, const wchar_t *title, int length, ifc_plentryinfo *extraInfo) + { + if (wcsstr(filename, L"://") || PathIsRootW(filename)) + { + playlist->OnFile(filename, title, length, extraInfo); + } + else + { + wchar_t fullPath[MAX_PATH] = {0}, canonicalizedPath[MAX_PATH] = {0}; + PathCombineW(fullPath, rootPath, filename); + PathCanonicalizeW(canonicalizedPath, fullPath); + playlist->OnFile(canonicalizedPath, title, length, extraInfo); + } + } + + void StartTag(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params) + { + if (!_wcsicmp(xmltag, L"ENTRYREF")) + { + const wchar_t *url = params->getItemValue(L"HREF"); + + const wchar_t *titleHack = params->getItemValue(L"CLIENTBIND"); + int lengthHack = -1; + wchar_t titleBuf[256] = L""; + + if (titleHack) + { + // get the length out of the parantheses + StringCchCopyW(titleBuf, 256, titleHack); + wchar_t *end = titleBuf + lstrlenW(titleBuf); + while (end && *end && *end != '(' && end != titleBuf) + end = CharPrevW(titleBuf, end); + + *end = 0; + end++; + lengthHack = _wtoi(end); + } + + wchar_t filename[FILENAME_SIZE] = {0}; + if (wcschr(url, L'?')) + StringCchPrintfW(filename, FILENAME_SIZE, L"%s&=.asx", url); + else + StringCchPrintfW(filename, FILENAME_SIZE, L"%s?.asx", url); + + OnFileHelper(playlist, filename, titleBuf, lengthHack*1000, &info); + + } + else if (!_wcsicmp(xmlpath, L"ASX\fENTRY\fREF") || !_wcsicmp(xmlpath, L"ASX\fREPEAT\fENTRY\fREF")) + { + const wchar_t *track = params->getItemValue(L"HREF"); + wchar_t fullTitle[128] = {0}, fullFilename[FILENAME_SIZE] = {0}; + // if there is no extension given, we need to add ?.wma or &=.wma to the end of the URL + // this could be 2 lines of code if that wasn't the case :( + if (track) + { + const wchar_t *trackTitle = 0; + if (title.GetString()[0] && artist.GetString()[0]) + { + StringCchPrintfW(fullTitle, 128, L"%s - %s", artist.GetString(), title.GetString()); + trackTitle = fullTitle; + } + if (!_wcsnicmp(track, L"http://", 7)) + { + const wchar_t *end = scanstr_backcW(track, L"/.", 0); + if (!end || *end == L'/') + { + if (wcschr(track, L'?')) + StringCchPrintfW(fullFilename, FILENAME_SIZE, L"%s&=.wma", track); + else + StringCchPrintfW(fullFilename, FILENAME_SIZE, L"%s?.wma", track); + track = fullFilename; + } + } + + OnFileHelper(playlist, track, trackTitle, -1, &info); + } + } + else if (!_wcsicmp(xmltag, L"REPEAT")) + { + const wchar_t *param; + if (param = params->getItemValue(L"count")) + { + info.repeat = _wtoi(param); + } + else + info.repeat = -1; + } + else if (!_wcsicmp(xmltag, L"PARAM")) + { + const wchar_t *param; + if (param = params->getItemValue(L"name")) + { + if (!_wcsicmp(param, L"context")) + { + const wchar_t * value = params->getItemValue(L"value"); + if (!_wcsicmp(value, L"station")) + info.isRadio = true; + } + else if (!_wcsicmp(param, L"encoding")) + { + const wchar_t *value=params->getItemValue(L"value"); + if (value) + parser->xmlreader_setEncoding(value); // I hope we can set it on the fly like this! + } + } + } + } + + void EndTag(const wchar_t *xmlpath, const wchar_t *xmltag) + { + if (!_wcsicmp(xmltag, L"REPEAT")) + { + info.repeat = 0; + } + } + ifc_playlistloadercallback *playlist; + XMLString title, artist; + ASXInfo info; + const wchar_t *rootPath; + obj_xml *parser; +protected: + RECVS_DISPATCH; + +}; + +#define CBCLASS ASXXML +START_DISPATCH; +VCB(ONSTARTELEMENT, StartTag) +VCB(ONENDELEMENT, EndTag) +END_DISPATCH; +#undef CBCLASS + + +/* +TODO: + +don't add tracks until all parts of the "ENTRY" tag are processed. There are some ASX playlists where the title comes AFTER the ref's + +maybe have separate XML callbacks for metadata (title, author, shit like that) so that logic can be separated from the main ASX logic +*/ + +// ASX isn't really XML. That means we need to URL-encode the text so our XML parser doesn't choke +// if microsoft followed standards, the world would be a better place. +int ASXLoader::GayASX_to_XML_converter(obj_xml *parser, char *buffer, int len) +{ + // benski> I have no idea if ASX is always ASCII, or if it's UTF-8 or what. + // but really I can't be bothered with Microsoft's lameness right now, so we'll assume it's local code page for the time being + char *start = buffer; + int sofar = 0; + for (int i = 0;i < len;i++) + { + if (buffer[i] == '&') + { + if (sofar) + { + if (parser->xmlreader_feed(start, sofar) != API_XML_SUCCESS) + return API_XML_FAILURE; + } + + if (parser->xmlreader_feed("&", 5) != API_XML_SUCCESS) // no null terminator + return API_XML_FAILURE; + start = &buffer[i + 1]; + sofar = 0; + } + else + { + /** + * ok, this might look really weird + * but ASX doesn't have case sensitivity + * so lots of playlists have things like + * <title>This is the title</Title> + * and so we have to accomodate + * for this nonsense + */ + + if (inTag && !inQuotes) + buffer[i] = toupper(buffer[i]); + + if (buffer[i] == '>') + { + inTag=false; + } + else if (buffer[i] == '<') + { + inTag=true; + } + + // dro> only do uppercase handling on parts of the tag not inbetween quotes + // (some servers just don't like having the urls case messed with, the swines) + if (buffer[i] == '"') + { + if(!inQuotes) + inQuotes=true; + else + inQuotes=false; + } + + sofar++; + } + } + if (sofar && parser->xmlreader_feed(start, sofar) != API_XML_SUCCESS) + return API_XML_FAILURE; + OutputDebugStringA(buffer); + return API_XML_SUCCESS; +} + + +#define HTTP_BUFFER_SIZE 16384 +int ASXLoader::FeedXMLHTTP(api_httpreceiver *http, obj_xml *parser, bool *noData) +{ + char downloadedData[HTTP_BUFFER_SIZE] = {0}; + int xmlResult = API_XML_SUCCESS; + int downloadSize = http->get_bytes(downloadedData, HTTP_BUFFER_SIZE); + if (downloadSize) + { + xmlResult = GayASX_to_XML_converter(parser, downloadedData, downloadSize); + *noData=false; + } + else + *noData = true; + + return xmlResult; +} + +void ASXLoader::RunXMLDownload(api_httpreceiver *http, obj_xml *parser) +{ +int ret; + bool noData; + do + { + Sleep(50); + ret = http->run(); + if (FeedXMLHTTP(http, parser, &noData) != API_XML_SUCCESS) + return ; + } + while (ret == HTTPRECEIVER_RUN_OK); + + // finish off the data + do + { + if (FeedXMLHTTP(http, parser, &noData) != API_XML_SUCCESS) + return ; + } while (!noData); + + parser->xmlreader_feed(0, 0); +} + +int ASXLoader::LoadFile(obj_xml *parser, const wchar_t *filename) +{ + HANDLE file = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL); + + if (file == INVALID_HANDLE_VALUE) + return IFC_PLAYLISTLOADER_FAILED; + + while (true) + { + char data[1024] = {0}; + DWORD bytesRead = 0; + if (ReadFile(file, data, 1024, &bytesRead, NULL) && bytesRead) + { + if (GayASX_to_XML_converter(parser, data, bytesRead) != API_XML_SUCCESS) + { + CloseHandle(file); + return IFC_PLAYLISTLOADER_FAILED; + } + } + else + break; + } + + CloseHandle(file); + if (parser->xmlreader_feed(0, 0) != API_XML_SUCCESS) + return IFC_PLAYLISTLOADER_FAILED; + + return IFC_PLAYLISTLOADER_SUCCESS; +} + + +int ASXLoader::LoadURL(obj_xml *parser, const wchar_t *url) +{ + api_httpreceiver *http = 0; + waServiceFactory *sf = plugin.service->service_getServiceByGuid(httpreceiverGUID); + if (sf) http = (api_httpreceiver *)sf->getInterface(); + + if (!http) + return IFC_PLAYLISTLOADER_FAILED; + http->AllowCompression(); + http->open(API_DNS_AUTODNS, HTTP_BUFFER_SIZE, winamp.GetProxy()); + SetUserAgent(http); + http->connect(AutoChar(url)); + int ret; + + do + { + Sleep(10); + ret = http->run(); + if (ret == -1) // connection failed + break; + + // ---- check our reply code ---- + int replycode = http->getreplycode(); + switch (replycode) + { + case 0: + case 100: + break; + case 200: + { + RunXMLDownload(http, parser); + sf->releaseInterface(http); + return IFC_PLAYLISTLOADER_SUCCESS; + } + break; + default: + sf->releaseInterface(http); + return IFC_PLAYLISTLOADER_FAILED; + } + } + while (ret == HTTPRECEIVER_RUN_OK); + //const char *er = http->geterrorstr(); + sf->releaseInterface(http); + return IFC_PLAYLISTLOADER_FAILED; +} + +static int loadasxv2fn(const wchar_t *filename, ifc_playlistloadercallback *playlist) +{ + int i=1; + wchar_t ref[FILENAME_SIZE] = {0}; + wchar_t key[100] = {0}; + while (1) + { + StringCchPrintfW(key, 100, L"Ref%d", i++); + GetPrivateProfileStringW(L"Reference", key, L"?", ref, FILENAME_SIZE, filename); + if (!lstrcmpiW(ref, L"?")) + break; + else + { + if (!_wcsnicmp(ref, L"http://", 7)) + { + const wchar_t *end = scanstr_backcW(ref, L"/.", 0); + if (!end || *end == L'/') + { + if (wcschr(ref, L'?')) + StringCchCatW(ref, FILENAME_SIZE, L"&=.wma"); + else + StringCchCatW(ref, FILENAME_SIZE, L"?.wma"); + } + } + + playlist->OnFile(ref, 0, 0, 0); + } + + } + return IFC_PLAYLISTLOADER_SUCCESS; +} + +int ASXLoader::Load(const wchar_t *filename, ifc_playlistloadercallback *playlist) +{ + obj_xml *parser = 0; + waServiceFactory *parserFactory = 0; + + HANDLE quickTest = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0); + if (quickTest != INVALID_HANDLE_VALUE) + { + char reference[11] = {0}; + DWORD bytesRead=0; + ReadFile(quickTest, reference, 11, &bytesRead, 0); + CloseHandle(quickTest); + if (bytesRead == 11 && !_strnicmp(reference, "[Reference]", 11)) + return loadasxv2fn(filename, playlist); + } + + parserFactory = plugin.service->service_getServiceByGuid(obj_xmlGUID); + if (parserFactory) + parser = (obj_xml *)parserFactory->getInterface(); + + if (parser) + { + wchar_t rootPath[MAX_PATH] = {0}; + const wchar_t *callbackPath = playlist->GetBasePath(); + if (callbackPath) + lstrcpynW(rootPath, callbackPath, MAX_PATH); + else + { + lstrcpynW(rootPath, filename, MAX_PATH); + PathRemoveFileSpecW(rootPath); + } + + ASXXML asxXml(playlist, rootPath, parser); + parser->xmlreader_registerCallback(L"ASX\f*", &asxXml); + parser->xmlreader_registerCallback(L"ASX\fENTRY\fTITLE", &asxXml.title); + parser->xmlreader_registerCallback(L"ASX\fENTRY\fAUTHOR", &asxXml.artist); + parser->xmlreader_open(); + parser->xmlreader_setEncoding(L"windows-1252"); + + int ret; + if (wcsstr(filename, L"://")) + ret = LoadURL(parser, filename); + else + ret = LoadFile(parser, filename); + + parser->xmlreader_unregisterCallback(&asxXml); + parser->xmlreader_unregisterCallback(&asxXml.title); + parser->xmlreader_unregisterCallback(&asxXml.artist); + parser->xmlreader_close(); + parserFactory->releaseInterface(parser); + return ret; + } + + + return IFC_PLAYLISTLOADER_FAILED; +} + +#define CBCLASS ASXLoader +START_DISPATCH; +CB(IFC_PLAYLISTLOADER_LOAD, Load) +END_DISPATCH; +#undef CBCLASS diff --git a/Src/Plugins/Input/in_wmvdrm/ASXLoader.h b/Src/Plugins/Input/in_wmvdrm/ASXLoader.h new file mode 100644 index 00000000..c562014b --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/ASXLoader.h @@ -0,0 +1,29 @@ +#ifndef NULLSOFT_PLAYLIST_ASX_LOADER_H +#define NULLSOFT_PLAYLIST_ASX_LOADER_H + +#include "../playlist/ifc_playlistloader.h" +#include "../playlist/ifc_playlistloadercallback.h" +#include <stdio.h> + +class obj_xml; +class api_httpreceiver; +class ASXLoader : public ifc_playlistloader +{ +public: + ASXLoader() : inTag(false), inQuotes(false) {} + int Load(const wchar_t *filename, ifc_playlistloadercallback *playlist); + +private: + int LoadURL(obj_xml *parser, const wchar_t *url); + int LoadFile(obj_xml *parser, const wchar_t *filename); + void RunXMLDownload(api_httpreceiver *http, obj_xml *parser); + int FeedXMLHTTP(api_httpreceiver *http, obj_xml *parser, bool *noData); + int GayASX_to_XML_converter(obj_xml *parser, char *buffer, int len); + + +protected: + bool inTag; + bool inQuotes; + RECVS_DISPATCH; +}; +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/AlbumArt.cpp b/Src/Plugins/Input/in_wmvdrm/AlbumArt.cpp new file mode 100644 index 00000000..a1dc08d2 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/AlbumArt.cpp @@ -0,0 +1,202 @@ +#include "main.h" +#include "../nu/AutoWide.h" +#include "AlbumArt.h" +#include "util.h" +#include <shlwapi.h> +#include <strsafe.h> + +bool ASF_AlbumArtProvider::IsMine(const wchar_t *filename) +{ + const wchar_t *ext = PathFindExtension(filename); + if (ext && *ext) + { + ext++; + return fileTypes.GetAVType(ext) != -1; + } + return false; +} + +int ASF_AlbumArtProvider::ProviderType() +{ + return ALBUMARTPROVIDER_TYPE_EMBEDDED; +} + +bool NameToAPICType(const wchar_t *name, int &num) +{ + if (!name || !*name) // default to cover + num=0x3; + else if (!_wcsicmp(name, L"fileicon")) // 32x32 pixels 'file icon' (PNG only) + num=0x1; + else if (!_wcsicmp(name, L"icon")) // Other file icon + num=0x2; + else if (!_wcsicmp(name, L"cover")) // Cover (front) + num=0x3; + else if (!_wcsicmp(name, L"back")) // Cover (back) + num=0x4; + else if (!_wcsicmp(name, L"leaflet")) // Leaflet page + num=0x5; + else if (!_wcsicmp(name, L"media")) // Media (e.g. lable side of CD) + num=0x6; + else if (!_wcsicmp(name, L"leadartist")) //Lead artist/lead performer/soloist + num=0x7; + else if (!_wcsicmp(name, L"artist")) // Artist/performer + num=0x8; + else if (!_wcsicmp(name, L"conductor")) // Conductor + num=0x9; + else if (!_wcsicmp(name, L"band")) // Band/Orchestra + num=0xA; + else if (!_wcsicmp(name, L"composer")) // Composer + num=0xB; + else if (!_wcsicmp(name, L"lyricist")) // Lyricist/text writer + num=0xC; + else if (!_wcsicmp(name, L"location")) // Recording Location + num=0xD; + else if (!_wcsicmp(name, L"recording")) // During recording + num=0xE; + else if (!_wcsicmp(name, L"performance")) // During performance + num=0xF; + else if (!_wcsicmp(name, L"preview")) // Movie/video screen capture + num=0x10; + else if (!_wcsicmp(name, L"fish")) // A bright coloured fish + num=0x11; + else if (!_wcsicmp(name, L"illustration")) // Illustration + num=0x12; + else if (!_wcsicmp(name, L"artistlogo")) // Band/artist logotype + num=0x13; + else if (!_wcsicmp(name, L"publisherlogo")) // Publisher/Studio logotype + num=0x14; + else + return false; + return true; +} + + +int ASF_AlbumArtProvider::GetAlbumArtData(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType) +{ + int pictype; + if (NameToAPICType(type, pictype)) + { + WMInformation wm(filename); + if (wm.GetPicture(bits, len, mimeType, pictype)) + return ALBUMARTPROVIDER_SUCCESS; + } + + return ALBUMARTPROVIDER_FAILURE; +} + +int ASF_AlbumArtProvider::SetAlbumArtData(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType) +{ + int pictype; + if (NameToAPICType(type, pictype)) + { + WMInformation wm(filename); + if (!wm.MakeWritable(filename)) + return ALBUMARTPROVIDER_READONLY; // can't write + + if (wm.SetPicture(bits, len, mimeType, pictype)) + { + wm.Flush(); + return ALBUMARTPROVIDER_SUCCESS; + } + } + + return ALBUMARTPROVIDER_FAILURE; +} + +int ASF_AlbumArtProvider::DeleteAlbumArt(const wchar_t *filename, const wchar_t *type) +{ + int pictype; + if (NameToAPICType(type, pictype)) + { + WMInformation wm(filename); + if (!wm.MakeWritable(filename)) + { + if (wm.HasPicture(pictype)) + return ALBUMARTPROVIDER_READONLY; // can't write + else + return ALBUMARTPROVIDER_FAILURE; + } + + if (wm.DeletePicture(pictype)) + { + wm.Flush(); + return ALBUMARTPROVIDER_SUCCESS; + } + } + + return ALBUMARTPROVIDER_FAILURE; +} + +#define CBCLASS ASF_AlbumArtProvider +START_DISPATCH; +CB(SVC_ALBUMARTPROVIDER_PROVIDERTYPE, ProviderType); +CB(SVC_ALBUMARTPROVIDER_GETALBUMARTDATA, GetAlbumArtData); +CB(SVC_ALBUMARTPROVIDER_SETALBUMARTDATA, SetAlbumArtData); +CB(SVC_ALBUMARTPROVIDER_DELETEALBUMART, DeleteAlbumArt); +CB(SVC_ALBUMARTPROVIDER_ISMINE, IsMine); +END_DISPATCH; +#undef CBCLASS + +static ASF_AlbumArtProvider albumArtProvider; + +// {B4184902-EE79-4015-B9A4-76209C6153FA} +static const GUID asf_albumartproviderGUID = +{ 0xb4184902, 0xee79, 0x4015, { 0xb9, 0xa4, 0x76, 0x20, 0x9c, 0x61, 0x53, 0xfa } }; + + +FOURCC AlbumArtFactory::GetServiceType() +{ + return svc_albumArtProvider::SERVICETYPE; +} + +const char *AlbumArtFactory::GetServiceName() +{ + return "ASF Album Art Provider"; +} + +GUID AlbumArtFactory::GetGUID() +{ + return asf_albumartproviderGUID; +} + +void *AlbumArtFactory::GetInterface(int global_lock) +{ + return &albumArtProvider; +} + +int AlbumArtFactory::SupportNonLockingInterface() +{ + return 1; +} + +int AlbumArtFactory::ReleaseInterface(void *ifc) +{ + //plugin.service->service_unlock(ifc); + return 1; +} + +const char *AlbumArtFactory::GetTestString() +{ + return 0; +} + +int AlbumArtFactory::ServiceNotify(int msg, int param1, int param2) +{ + return 1; +} + +#ifdef CBCLASS +#undef CBCLASS +#endif + +#define CBCLASS AlbumArtFactory +START_DISPATCH; +CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType) +CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName) +CB(WASERVICEFACTORY_GETGUID, GetGUID) +CB(WASERVICEFACTORY_GETINTERFACE, GetInterface) +CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface) +CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface) +CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString) +CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify) +END_DISPATCH; diff --git a/Src/Plugins/Input/in_wmvdrm/AlbumArt.h b/Src/Plugins/Input/in_wmvdrm/AlbumArt.h new file mode 100644 index 00000000..129377c3 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/AlbumArt.h @@ -0,0 +1,39 @@ +#ifndef NULLSOFT_IN_MP3_ALBUMART_H +#define NULLSOFT_IN_MP3_ALBUMART_H + +#include "../Agave/AlbumArt/svc_albumArtProvider.h" + +class ASF_AlbumArtProvider : public svc_albumArtProvider +{ +public: + bool IsMine(const wchar_t *filename); + int ProviderType(); + // implementation note: use WASABI_API_MEMMGR to alloc bits and mimetype, so that the recipient can free through that + int GetAlbumArtData(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType); + int SetAlbumArtData(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType); + int DeleteAlbumArt(const wchar_t *filename, const wchar_t *type); +protected: + RECVS_DISPATCH; +}; + +#include <api/service/waservicefactory.h> +#include <api/service/services.h> + +class AlbumArtFactory : public waServiceFactory +{ +public: + FOURCC GetServiceType(); + const char *GetServiceName(); + GUID GetGUID(); + void *GetInterface(int global_lock); + int SupportNonLockingInterface(); + int ReleaseInterface(void *ifc); + const char *GetTestString(); + int ServiceNotify(int msg, int param1, int param2); + +protected: + RECVS_DISPATCH; +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/AllocLayer.cpp b/Src/Plugins/Input/in_wmvdrm/AllocLayer.cpp new file mode 100644 index 00000000..8c9c8efc --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/AllocLayer.cpp @@ -0,0 +1 @@ +#include "AllocLayer.h"
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/AllocLayer.h b/Src/Plugins/Input/in_wmvdrm/AllocLayer.h new file mode 100644 index 00000000..ed07a3bd --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/AllocLayer.h @@ -0,0 +1,80 @@ +#ifndef NULLSOFT_ALLOCLAYERH +#define NULLSOFT_ALLOCLAYERH + +#include "WMHandler.h" +#include "BufferPool.h" +#include <cassert> +class AllocLayer : public WMHandler +{ +public: + AllocLayer(IWMReader *reader) + : readerAdvanced(0), + listenOutput( -1), + maxSize(0) + + { + reader->QueryInterface(&readerAdvanced); + + } + ~AllocLayer() + { + if (readerAdvanced) + { + readerAdvanced->Release(); + readerAdvanced = 0; + } + } + + void Listen(long output) + { + listenOutput = output; + if (output != -1) + { + readerAdvanced->SetAllocateForOutput(listenOutput, TRUE); + readerAdvanced->GetMaxOutputSampleSize(listenOutput, &maxSize); + assert(maxSize>0); + pool.SetAllocSize(maxSize); + } + } + + void Listen(long output, long numBuffers) + { + listenOutput = output; + if (output != -1) + { + readerAdvanced->SetAllocateForOutput(listenOutput, TRUE); + readerAdvanced->GetMaxOutputSampleSize(listenOutput, &maxSize); + assert(maxSize>0); + pool.SetAllocSize(maxSize); + pool.PreAllocate(numBuffers); + pool.limit=numBuffers; + + } + } + + void FreeBuffers() + { + pool.FreeBuffers(); + } + + + BufferPool pool; +private: + + void AllocateOutput(long outputNum, long bufferSize, INSSBuffer *&buffer) + { + if (outputNum == listenOutput) + { + assert(maxSize >= bufferSize); + buffer = pool.GetBuffer(bufferSize); + } + else + WMHandler::AllocateOutput(outputNum, bufferSize, buffer); // let other handlers have a shot at it first. + } + + // WMHandler + long listenOutput; + DWORD maxSize; + IWMReaderAdvanced *readerAdvanced; +}; +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/AudioFormat.cpp b/Src/Plugins/Input/in_wmvdrm/AudioFormat.cpp new file mode 100644 index 00000000..7af75b24 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/AudioFormat.cpp @@ -0,0 +1,61 @@ +#include "main.h" +#include "AudioLayer.h" + +unsigned long AudioFormat::AudioSamplesToMilliseconds(unsigned long samples) +{ + return MulDiv(samples, 1000, SampleRate()); +} + +unsigned long AudioFormat::AudioBytesToMilliseconds(unsigned long bytes) +{ + return MulDiv(AudioBytesToSamples(bytes), 1000, SampleRate()); +} + +unsigned long AudioFormat::AudioMillisecondsToBytes(DWORD milliseconds) +{ + return AudioSamplesToBytes(MulDiv(milliseconds, SampleRate(), 1000)); +} + +unsigned long AudioFormat::AudioDurationToBytes(QWORD duration) +{ + // TODO: potential integer overflow + return AudioSamplesToBytes(MulDiv((int)duration, SampleRate(), 1000*10000)); +} + +unsigned long AudioFormat::AudioBytesToSamples(unsigned long bytes) +{ + return bytes / waveFormat->Format.nBlockAlign; +} + +unsigned long AudioFormat::AudioSamplesToBytes(unsigned long samples) +{ + return samples * waveFormat->Format.nBlockAlign; +} + +long AudioFormat::Channels() +{ + return waveFormat->Format.nChannels; +} + +long AudioFormat::ValidBits() +{ + if (waveFormat->Format.wFormatTag == WAVE_FORMAT_PCM) + { + return waveFormat->Format.wBitsPerSample; + } + if (waveFormat->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) + { + return waveFormat->Samples.wValidBitsPerSample; + } + return 0; +} + +long AudioFormat::BitSize() +{ + return waveFormat->Format.wBitsPerSample; +} + +long AudioFormat::SampleRate() +{ + return waveFormat->Format.nSamplesPerSec; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/AudioFormat.h b/Src/Plugins/Input/in_wmvdrm/AudioFormat.h new file mode 100644 index 00000000..81aceb39 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/AudioFormat.h @@ -0,0 +1,45 @@ +#ifndef NULLSOFT_IN_WMVDRM_AUDIOFORMAT_H +#define NULLSOFT_IN_WMVDRM_AUDIOFORMAT_H + +#include <mmreg.h> +#include <wmsdk.h> + +class AudioFormat +{ +public: + AudioFormat() : waveFormat(0) + { + } + ~AudioFormat() + { + delete [] waveFormat; + } + unsigned long AudioBytesToSamples(unsigned long bytes); + unsigned long AudioSamplesToBytes(unsigned long samples); + unsigned long AudioBytesToMilliseconds(unsigned long bytes); + unsigned long AudioMillisecondsToBytes(DWORD milliseconds); + unsigned long AudioDurationToBytes(QWORD duration); + unsigned long AudioSamplesToMilliseconds(unsigned long samples); + long Channels(); + long ValidBits(); + long BitSize(); + long SampleRate(); +//protected: + void Open(WM_MEDIA_TYPE *mediaType) + { + delete[] waveFormat; + waveFormat = (WAVEFORMATEXTENSIBLE *) new unsigned char[mediaType->cbFormat]; + memcpy(waveFormat, mediaType->pbFormat, mediaType->cbFormat); + } + + void Close() + { + delete [] waveFormat; + waveFormat=0; + } + +private: + WAVEFORMATEXTENSIBLE *waveFormat; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/AudioLayer.cpp b/Src/Plugins/Input/in_wmvdrm/AudioLayer.cpp new file mode 100644 index 00000000..c02f30d4 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/AudioLayer.cpp @@ -0,0 +1,253 @@ +#include "Main.h" +#include "AudioLayer.h" +#include "VideoLayer.h" +#include <Mmreg.h> +#include <cassert> +#include "util.h" +#include "config.h" +#include "AudioThread.h" +#include "api.h" + + +#pragma warning(disable:4355) // warning C4355: 'this' : used in base member initializer list + +AudioLayer::AudioLayer(IWMReader *_reader) + : reader(_reader), audioOutputNum( -1), + reader2(0), offset(0), new_offset(0), + startPosition(0), videoCatchup(0), + opened(false), killSwitch(0), + audioThread(this), latency(0) +{ + reader->AddRef(); + if (FAILED(reader->QueryInterface(&reader2))) + reader2 = 0; + + killSwitch = CreateEvent(NULL, TRUE, FALSE, NULL); +} + +void AudioLayer::Opened() +{ + + ResetEvent(killSwitch); + if (AudioLayer::OpenAudio()) + { + ResetEvent(killSwitch); + + BOOL dedicatedThread = config_audio_dedicated_thread ? TRUE : FALSE; + reader2->SetOutputSetting(audioOutputNum, g_wszDedicatedDeliveryThread, WMT_TYPE_BOOL, (BYTE *) & dedicatedThread, sizeof(dedicatedThread)); + + BOOL outOfOrder = config_audio_outoforder ? TRUE : FALSE; + reader2->SetOutputSetting(audioOutputNum, g_wszDeliverOnReceive, WMT_TYPE_BOOL, (BYTE *) & outOfOrder, sizeof(outOfOrder)); + + BOOL justInTime = config_lowmemory ? TRUE : FALSE; + reader2->SetOutputSetting(audioOutputNum, g_wszJustInTimeDecode, WMT_TYPE_BOOL, (BYTE *) & justInTime, sizeof(justInTime)); + + opened = true; + offset = ((QWORD)latency) * 10000; + new_offset = config_audio_early ? (latency + config_audio_early_pad) : 0; + reader2->SetOutputSetting(audioOutputNum, g_wszEarlyDataDelivery, WMT_TYPE_DWORD, (BYTE *) & new_offset , sizeof(new_offset)); + + winamp.OpenViz(latency, SampleRate()); + } + + WMHandler::Opened(); +} + +void AudioLayer::Started() +{ + ResetEvent(killSwitch); + if (opened) + { + audioThread.Start(&First()); + } + + WMHandler::Started(); +} + +void AudioLayer::Stopped() +{ + if (opened) + audioThread.Stop(); + WMHandler::Stopped(); + +} + +WM_MEDIA_TYPE *NewMediaType(IWMOutputMediaProps *props) +{ + DWORD mediaTypeSize; + props->GetMediaType(0, &mediaTypeSize); + WM_MEDIA_TYPE *mediaType = (WM_MEDIA_TYPE *)new unsigned char[mediaTypeSize]; + props->GetMediaType(mediaType, &mediaTypeSize); + return mediaType; +} + +bool AudioLayer::OpenAudio() +{ + audioOutputNum = -1; + DWORD numOutputs, output, format, numFormats; + IWMOutputMediaProps *formatProperties; + GUID mediaType; + if (FAILED((reader->GetOutputCount(&numOutputs)))) + return false; + for (output = 0;output < numOutputs;output++) + { + HRESULT hr; + DWORD speakerConfig = config_audio_num_channels; + + if (AGAVE_API_CONFIG && AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"mono", false)) // force mono? + speakerConfig = DSSPEAKER_MONO; + else if (AGAVE_API_CONFIG && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"surround", true)) // is surround disallowed? + speakerConfig = DSSPEAKER_STEREO; + + hr = reader2->SetOutputSetting(output, g_wszSpeakerConfig, WMT_TYPE_DWORD, (BYTE *) & speakerConfig, sizeof(speakerConfig)); + assert(hr == S_OK); + + BOOL discreteChannels = TRUE; + hr = reader2->SetOutputSetting(output, g_wszEnableDiscreteOutput, WMT_TYPE_BOOL, (BYTE *) & discreteChannels , sizeof(discreteChannels )); + assert(hr == S_OK); + + if (FAILED(reader->GetOutputFormatCount(output, &numFormats))) + continue; + for (format = 0;format < numFormats;format++) + { + + reader->GetOutputFormat(output, format, &formatProperties); + formatProperties->GetType(&mediaType); + if (mediaType == WMMEDIATYPE_Audio) + { + + WM_MEDIA_TYPE *mediaType = NewMediaType(formatProperties); + if (mediaType->subtype == WMMEDIASUBTYPE_PCM) + { + if (AGAVE_API_CONFIG) + { + unsigned int bits = AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"bits", 16); + + WAVEFORMATEXTENSIBLE *waveFormat = (WAVEFORMATEXTENSIBLE *) mediaType->pbFormat; + if (waveFormat->Format.cbSize >= 22) + waveFormat->Samples.wValidBitsPerSample=bits; + waveFormat->Format.wBitsPerSample=bits; + waveFormat->Format.nBlockAlign = (waveFormat->Format.wBitsPerSample / 8) * waveFormat->Format.nChannels; + waveFormat->Format.nAvgBytesPerSec=waveFormat->Format.nSamplesPerSec * waveFormat->Format.nBlockAlign; + if (FAILED(formatProperties->SetMediaType(mediaType))) + { + // blah, just use the default settings then + delete[] mediaType; + mediaType = NewMediaType(formatProperties); + } + } + AudioFormat::Open(mediaType); + delete mediaType; + bool video = false; + First().HasVideo(video); + + // this is needed to prevent an audio glitch on first playback + if (out) + { + extern WMDRM mod; + out->SetVolume(mod.GetVolume()); + out->SetPan(mod.GetPan()); + } + + latency = out->Open(SampleRate(), Channels(), ValidBits(), (video ? -666 : -1), -1); + + if (latency >= 0) + { + audioOutputNum = output; + reader->SetOutputProps(audioOutputNum, formatProperties); + formatProperties->Release(); + return true; + } + else + { + formatProperties->Release(); + AudioFormat::Close(); + continue; + } + } + + delete mediaType; + formatProperties->Release(); + } + } + } + return false; +} + +void AudioLayer::SampleReceived(QWORD &timeStamp, QWORD &duration, unsigned long &outputNum, unsigned long &flags, INSSBuffer *&sample) +{ + if (outputNum == audioOutputNum) + { + if (WaitForSingleObject(killSwitch, 0) == WAIT_OBJECT_0) + return ; + + if (videoCatchup) + { + videoCatchup = videoCatchup / 20000; + videoCatchup = min(videoCatchup, offset / 40000); + unsigned int num = (unsigned int) (videoCatchup / VIDEO_ACCEPTABLE_JITTER_MS); + while (num--) + { + if (WaitForSingleObject(killSwitch, VIDEO_ACCEPTABLE_JITTER_MS) == WAIT_OBJECT_0) + return ; + + } + + videoCatchup = 0; + } + + while (!audioThread.AddBuffer(sample, timeStamp, flags, false)) + { + if (WaitForSingleObject(killSwitch, VIDEO_ACCEPTABLE_JITTER_MS) == WAIT_OBJECT_0) + break; + } + } + else + WMHandler::SampleReceived(timeStamp, duration, outputNum, flags, sample); +} + +void AudioLayer::VideoCatchup(QWORD time) +{ + videoCatchup = time; + WMHandler::VideoCatchup(time); +} + +void AudioLayer::Closed() +{ + if (opened) + { + out->Close(); + winamp.CloseViz(); + } + opened = false; + + //AudioFormat::Close(); + WMHandler::Closed(); +} + +AudioLayer::~AudioLayer() +{ + audioThread.Kill(); + if (reader2) + reader2->Release(); + if (reader) + reader->Release(); + CloseHandle(killSwitch); +} + +void AudioLayer::Kill() +{ + SetEvent(killSwitch); + if (opened) + audioThread.SignalStop(); + WMHandler::Kill(); + + if (opened) + audioThread.WaitForStop(); +} + +void AudioLayer::EndOfFile() +{ + if (!opened || audioThread.EndOfFile()) + WMHandler::EndOfFile(); +} diff --git a/Src/Plugins/Input/in_wmvdrm/AudioLayer.h b/Src/Plugins/Input/in_wmvdrm/AudioLayer.h new file mode 100644 index 00000000..2f836530 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/AudioLayer.h @@ -0,0 +1,51 @@ +#ifndef NULLSOFT_AUDIOLAYERH +#define NULLSOFT_AUDIOLAYERH + +#include "WMHandler.h" +#include <mmreg.h> +#include "AudioThread.h" +#include "AudioFormat.h" + +class AudioLayer : public WMHandler, public AudioFormat +{ +public: + AudioLayer(IWMReader *_reader); + ~AudioLayer(); +bool IsOpen() +{ + return opened; +} + void Kill(); + bool OpenAudio(); + + void StartAudioThread(); +private: + // WMHandler events + + void Opened(); + void SampleReceived(QWORD &timeStamp, QWORD &duration, unsigned long &outputNum, unsigned long &flags, INSSBuffer *&sample); + void VideoCatchup(QWORD time); + void Closed(); + void EndOfFile(); + void Started(); + void Stopped(); + + // other people's data + IWMReader *reader; + + // our data + QWORD startPosition; + + int audioOutputNum; + IWMReaderAdvanced2 *reader2; + QWORD offset; + DWORD new_offset; + QWORD videoCatchup; + bool opened; + HANDLE killSwitch; + int latency; + + AudioThread audioThread; +}; + +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/AudioThread.cpp b/Src/Plugins/Input/in_wmvdrm/AudioThread.cpp new file mode 100644 index 00000000..b034c636 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/AudioThread.cpp @@ -0,0 +1,129 @@ +#include "Main.h" +#include "AudioThread.h" +#include "AudioLayer.h" +#include <assert.h> + +extern unsigned long endTime; + +DWORD WINAPI AudThread_stub(void *ptr) +{ + ((AudioThread *)ptr)->AudThread(); + return 0; +} + +void AudioThread::Start(WMHandler *_output) +{ + assert(_output); + output = _output; + eof=0; + ResetEvent(stopped); + QueueUserAPC(MediaThread_StartAPC, thread, reinterpret_cast<ULONG_PTR>(static_cast<MediaThread *>(this))); +} + +AudioThread::AudioThread(AudioLayer *audio) : output(0), audioLayer(audio) +{ + DWORD id; + thread = CreateThread(NULL, 256*1024, AudThread_stub, (void *)this, NULL, &id); + SetThreadPriority(thread, AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); +} + +void AudioThread::AudThread() +{ + int endbreak=0; + while (true) + { + switch (WaitForSingleObjectEx(killEvent, wait, TRUE)) + { + case WAIT_OBJECT_0: + //StopAPC(); + return; + + case WAIT_TIMEOUT: + { + if (buffers.empty() || endbreak) + { + SetEvent(bufferFreed); + + if (eof==1) + { + eof=2; + output->EndOfFile(); + } + endbreak = 0; + continue; + } + + MediaBuffer *buffer = buffers.front(); + DWORD length; + void *data; + buffer->buffer->GetBufferAndLength((BYTE **)&data, &length); + + //if (out->CanWrite() >= length) + { + QWORD timestamptemp = buffer->timestamp/10000LL; + DWORD timestamp = static_cast<DWORD>(timestamptemp); + + if (buffer->flags & WM_SF_DISCONTINUITY) + { + // fill with silence! + int msToFill = timestamp - out->GetWrittenTime(); // TODO: maybe use microsoft's time resolution? + if (msToFill > 0 && msToFill < 2000) + { + int bytes = audioLayer->AudioMillisecondsToBytes(msToFill); + __int8 *zeroes = (__int8 *)calloc(bytes, 1); + if (zeroes) + { + output->AudioDataReceived(zeroes, bytes, timestamp); + free(zeroes); + } + else + { + out->Flush(timestamp); + } + } + else if (msToFill > 0) + { + out->Flush(timestamp); + } + } + + output->AudioDataReceived(data, length, timestamp); + + // TODO seen a few crash reports failing around here + // might be the cause of the random wma fails + // but crash dump doesn't help too much afaict + try { + buffer->buffer->Release(); + delete buffer; + } catch (...) {} + + //buffers.pop_front(); + if (buffers.size()) + { + buffers.erase(buffers.begin()); + } + + unsigned long x = endTime; + if (x && timestamp > x) + { + eof = 1; // reached the end baby.... + endbreak = 1; + } + } + if (buffers.size() < config_audio_cache_frames) + SetEvent(bufferFreed); + } + continue; + + default: + continue; + } + } +} + +void AudioThread::AddAPC(MediaBuffer *buffer) +{ + OrderedInsert(buffer); + if (buffers.size() >= config_audio_cache_frames) + ResetEvent(bufferFreed); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/AudioThread.h b/Src/Plugins/Input/in_wmvdrm/AudioThread.h new file mode 100644 index 00000000..4192d604 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/AudioThread.h @@ -0,0 +1,40 @@ +#ifndef NULLSOFT_AUDIOTHREADH +#define NULLSOFT_AUDIOTHREADH + +#include "WMHandler.h" +#include "MediaThread.h" +#include <wmsdk.h> + +class AudioLayer; + +class AudioThread : public MediaThread +{ +public: + AudioThread(AudioLayer *audio); + void Start(WMHandler *output); + + /* AddBuffers put an audio buffer in the queue + it returns true if it was added + it returns false if it was NOT added. it is up to YOU (the caller) to sleep for a while and call again + */ + void AudThread(); + bool EndOfFile() + { + if (buffers.empty()) // if the buffers are empty, then our thread might never get a chance to signal EOF + return true; + + if (eof) + return true; + eof=1; + + return false; + } + +private: + void AddAPC(MediaBuffer *); + int eof; + WMHandler *output; + AudioLayer *audioLayer; + +}; +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/AutoChar.h b/Src/Plugins/Input/in_wmvdrm/AutoChar.h new file mode 100644 index 00000000..46cd840e --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/AutoChar.h @@ -0,0 +1,43 @@ +#ifndef NULLSOFT_AUTOCHARH +#define NULLSOFT_AUTOCHARH + +class AutoChar +{ +public: + AutoChar(const wchar_t *convert) : allocated(false), narrow(0) + { + // review maybe CP_UTF8? + + int size = WideCharToMultiByte(CP_ACP, 0, convert, -1, 0, 0, NULL, NULL); + if (!size) + return; + + narrow = new char[size]; + allocated=true; + + if (!WideCharToMultiByte(CP_ACP, 0, convert, -1, narrow, size, NULL, NULL)) + { + delete [] narrow; + narrow=0; + allocated=false; + } + } + ~AutoChar() + { + if (allocated) + { + delete [] narrow; + narrow=0; + allocated=false; + } + } + operator char *() + { + return narrow; + } +private: + bool allocated; + char *narrow; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/AutoWide.h b/Src/Plugins/Input/in_wmvdrm/AutoWide.h new file mode 100644 index 00000000..9248dda6 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/AutoWide.h @@ -0,0 +1,41 @@ +#ifndef AUTOWIDEH +#define AUTOWIDEH + +class AutoWide +{ +public: + AutoWide(const char *convert) : allocated(false), wide(0) + { + // review maybe CP_UTF8? + int size = MultiByteToWideChar(CP_ACP, 0, convert, -1, 0,0); + if (!size) + return; + + wide = new unsigned short[size]; + allocated=true; + if (!MultiByteToWideChar(CP_ACP, 0, convert, -1, wide,size)) + { + delete wide; + wide=0; + allocated=false; + } + } + ~AutoWide() + { + if (allocated) + { + delete wide; + wide=0; + allocated=false; + } + } + operator unsigned short *() + { + return wide; + } +private: + bool allocated; + unsigned short *wide; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/BufferLayer.cpp b/Src/Plugins/Input/in_wmvdrm/BufferLayer.cpp new file mode 100644 index 00000000..d41a7cfd --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/BufferLayer.cpp @@ -0,0 +1,95 @@ +#include "BufferLayer.h" +#include "Main.h" +#include "resource.h" +#define killEvent events[0] +#define startEvent events[1] + +enum +{ + KILL_EVENT = 0, + START_EVENT = 1, +}; + +DWORD WINAPI BufferLayer::BufThread_stub(void *ptr) +{ + ((BufferLayer *)ptr)->BufThread(); + return 0; +} + +BufferLayer::BufferLayer(IWMReader *reader) : reader2(0), buffering(false) +{ + if (FAILED(reader->QueryInterface(&reader2))) + reader2 = 0; + + startEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + killEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + DWORD id; + thread = CreateThread(NULL, 128*1024, BufThread_stub, (void *)this, NULL, &id); +} + +BufferLayer::~BufferLayer() +{ + SetEvent(killEvent); + ResetEvent(startEvent); + WaitForSingleObject(thread, INFINITE); + if (reader2) reader2->Release(); reader2 = 0; +} + + +void BufferLayer::BufferingStarted() +{ + winamp.SetStatus(WASABI_API_LNGSTRINGW(IDS_BUFFERING)); + buffering=true; + SetEvent(startEvent); + WMHandler::BufferingStarted(); +} + +void BufferLayer::BufferingStopped() +{ + winamp.SetStatus(L""); + buffering=false; + ResetEvent(startEvent); + WMHandler::BufferingStopped(); +} + +int BufferLayer::Wait() +{ + if (WaitForSingleObject(killEvent, 0) == WAIT_OBJECT_0) + return KILL_EVENT; + + return WaitForMultipleObjects(2, events, FALSE, INFINITE) - WAIT_OBJECT_0; + +} + +void BufferLayer::BufThread() +{ + do + { + switch (Wait()) + { + case KILL_EVENT: + return ; + case START_EVENT: + { + if (reader2) + { + DWORD percent; + QWORD throwAway; + if (SUCCEEDED(reader2->GetBufferProgress(&percent, &throwAway))) + winamp.Buffering(percent, WASABI_API_LNGSTRINGW(IDS_BUFFERING)); + + } + Sleep(10); + } + continue; + } + } + while (true); +} + +void BufferLayer::OpenFailed() +{ + ResetEvent(startEvent); + WMHandler::OpenFailed(); +} + diff --git a/Src/Plugins/Input/in_wmvdrm/BufferLayer.h b/Src/Plugins/Input/in_wmvdrm/BufferLayer.h new file mode 100644 index 00000000..fdfccdbc --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/BufferLayer.h @@ -0,0 +1,26 @@ +#ifndef NULLSOFT_BUFFERLAYERH +#define NULLSOFT_BUFFERLAYERH + +#include "WMHandler.h" + +class BufferLayer : public WMHandler +{ +public: + BufferLayer(IWMReader *reader); + ~BufferLayer(); + +protected: + void BufferingStarted(); + void BufferingStopped(); + void OpenFailed(); + +private: + static DWORD WINAPI BufThread_stub(void *ptr); + void BufThread(); + int Wait(); + HANDLE events[2]; + IWMReaderAdvanced2 *reader2; + HANDLE thread; + volatile bool buffering; +}; +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/BufferPool.h b/Src/Plugins/Input/in_wmvdrm/BufferPool.h new file mode 100644 index 00000000..b4c1ec2a --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/BufferPool.h @@ -0,0 +1,203 @@ +#ifndef NULLSOFT_BUFFERPOOLH +#define NULLSOFT_BUFFERPOOLH + +#include <wmsdk.h> +#include "../nu/AutoLock.h" +#include <deque> +#include <iostream> +#include <cassert> +using namespace Nullsoft::Utility; +class Buffer; +class Pool +{ +public: + virtual void ReturnBuffer(Buffer *buffer) = 0; +}; + +class Buffer : public INSSBuffer +{ +public: + Buffer(size_t _size, Pool *_pool) + : size(_size), + length(0), + pool(_pool), + refCount(1) + { + buffer = new unsigned char[size]; + } + + ~Buffer() + { + delete[] buffer; + } + + ULONG STDMETHODCALLTYPE AddRef() + { + return ++refCount; + } + + ULONG STDMETHODCALLTYPE Release() + { + assert(refCount > 0); + + if (--refCount == 0) + { + length = 0; + pool->ReturnBuffer(this); + } + return refCount; + } + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ppvObject) + { + if (IID_INSSBuffer == iid) + { + *ppvObject = static_cast<INSSBuffer *>(this); + AddRef(); + return S_OK; + } + else + { + *ppvObject = 0; + return E_NOINTERFACE; + } + } + + HRESULT STDMETHODCALLTYPE GetBuffer(BYTE **ppdwBuffer) + { + *ppdwBuffer = (BYTE *)buffer; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE GetBufferAndLength(BYTE **ppdwBuffer, DWORD *pdwLength) + { + *ppdwBuffer = (BYTE *)buffer; + *pdwLength = length; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE GetLength(DWORD *pdwLength) + { + *pdwLength = length; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE GetMaxLength(DWORD *pdwLength) + { + *pdwLength = size; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE SetLength(DWORD dwLength) + { + length = dwLength; + return S_OK; + } + + bool CanFit(size_t sizeCompare) + { + return (sizeCompare <= size); + } + size_t size, length; + Pool *pool; + int refCount; + unsigned char *buffer; +}; + +class BufferPool : public Pool +{ + typedef std::deque<Buffer *> PoolList; +public: + long limit; + BufferPool() : allocSize(0), + poolSize(0), + limit(0) + {} + + ~BufferPool() + { + FreeBuffers(); + } + void FreeBuffers() + { + + AutoLock lock (bufferGuard); + while (!pool.empty()) + { + Buffer *buff = pool.front(); + delete buff; + pool.pop_front(); + poolSize = 0; + } + } + void ReturnBuffer(Buffer *buffer) + { + AutoLock lock (bufferGuard); + pool.push_back(buffer); + } + Buffer *SearchForBuffer(size_t size) + { + PoolList::iterator itr; + AutoLock lock (bufferGuard); + for (itr = pool.begin();itr != pool.end();itr++) + { + if ((*itr)->CanFit(size)) + { + Buffer *buff = *itr; + pool.erase(itr); + buff->AddRef(); + return buff; + } + } + return 0; + } + + void PreAllocate(size_t count) + { + if (!allocSize) + return ; + for (size_t i = 0;i != count;i++) + { + AutoLock lock (bufferGuard); + pool.push_back( new Buffer(allocSize , this)); + } + } + INSSBuffer *GetBuffer(size_t size) + { + Buffer *buff = SearchForBuffer(size); + + /* + while (!buff && poolSize>=limit && limit) + { + Sleep(1); + buff = SearchForBuffer(size); + }*/ + + if (!buff) + { + poolSize++; + std::cerr << "poolsize = " << poolSize << std::endl; + buff = new Buffer(allocSize ? allocSize : size, this); + } + return buff; + } + + void test() + { + std::cerr << "pool.size() == " << pool.size(); + std::cerr << " and poolsize == " << poolSize << std::endl; + } + + void SetAllocSize(long size) + { + allocSize = size; + } + + PoolList pool; + LockGuard bufferGuard; + size_t allocSize; + size_t poolSize; + +}; + +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/CachedData.h b/Src/Plugins/Input/in_wmvdrm/CachedData.h new file mode 100644 index 00000000..8782ead1 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/CachedData.h @@ -0,0 +1,48 @@ +#ifndef CACHEDDATAH +#define CACHEDDATAH + +template <class DataType> +class CachedData +{ +public: + CachedData() : cached(false) + { + } + CachedData(DataType _data) : cached(true), data(_data) + { + } + operator DataType() + { + return data; + } + + bool IsCached() + { + return cached; + } + void ClearCache() + { + cached=false; + } + DataType *operator &() + { + if (cached) + return &data; + else + return 0; + } + + template <class FromType> + bool operator =(FromType from) + { + data = from; + cached=true; + return cached; + } + +private: + bool cached; + DataType data; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/ClockLayer.cpp b/Src/Plugins/Input/in_wmvdrm/ClockLayer.cpp new file mode 100644 index 00000000..cce27870 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/ClockLayer.cpp @@ -0,0 +1,101 @@ +#include "Main.h" +#include "ClockLayer.h" +#include "config.h" + +ClockLayer::ClockLayer(IWMReader *reader) + : clock(0), startTime(0), + clockTick(2500000), curTime(0), + startTimeMilliseconds(0), + realTime(false), + lastOutputTime(0) +{ + if (FAILED(reader->QueryInterface(&clock))) + clock=0; +} + +void ClockLayer::Opened() +{ + realTime=!config_clock; + WMHandler::Opened(); + + if (!realTime) + { + HRESULT hr = clock->SetUserProvidedClock(TRUE); + + if (FAILED(hr)) + { + realTime=false; + } + } + else + clock->SetUserProvidedClock(FALSE); + curTime = startTime; +} + +void ClockLayer::Started() +{ + if (!realTime && config_clock) + clock->DeliverTime((QWORD) - 1); + + if (startTimeMilliseconds != 0) + out->Flush(startTimeMilliseconds); + + SetLastOutputTime(startTimeMilliseconds); + WMHandler::Started(); +} + +void ClockLayer::Clock() +{ + if (!realTime && config_clock) + clock->DeliverTime((QWORD) - 1); +} + +void ClockLayer::SetStartTimeMilliseconds(long time) +{ + startTimeMilliseconds=time; + startTime = time; + startTime *= 10000; +} + +void ClockLayer::TimeReached(QWORD &timeReached) +{ + curTime = timeReached; +} + +QWORD ClockLayer::GetStartTime() +{ + return startTime; +} + +void ClockLayer::GoRealTime() +{ + realTime = true; +} + +int ClockLayer::GetOutputTime() +{ + if (realTime) + return (int) (curTime / 10000LL); + else + return lastOutputTime + (out->GetOutputTime() - out->GetWrittenTime()); +} + +void ClockLayer::TimeToSync(QWORD timeStamp, __int64 &diff) +{ + if (realTime) + diff = 0; + else + { + QWORD outputTime = this->GetOutputTime(); + outputTime *= 10000LL; + diff = timeStamp - outputTime; + } +} + +void ClockLayer::SampleReceived(QWORD &timeStamp, QWORD &duration, unsigned long &outputNum, unsigned long &flags, INSSBuffer *&sample) +{ + if (realTime) + curTime = timeStamp; + + WMHandler::SampleReceived(timeStamp, duration, outputNum, flags, sample); +} diff --git a/Src/Plugins/Input/in_wmvdrm/ClockLayer.h b/Src/Plugins/Input/in_wmvdrm/ClockLayer.h new file mode 100644 index 00000000..1fb89230 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/ClockLayer.h @@ -0,0 +1,36 @@ +#ifndef NULLSOFT_CLOCKLAYERH +#define NULLSOFT_CLOCKLAYERH + +#include "WMHandler.h" +class ClockLayer : public WMHandler +{ +public: + ClockLayer(IWMReader *reader); + + void SetStartTimeMilliseconds(long time); + QWORD GetStartTime(); + + void GoRealTime(); + int GetOutputTime(); + void SetLastOutputTime(int _outputTime) + { + lastOutputTime = _outputTime; + } + void Clock(); +private: + // WMHandler + void Opened(); + void Started(); + void TimeReached(QWORD &timeReached); + void TimeToSync(QWORD timeStamp, __int64 &diff); + void SampleReceived(QWORD &timeStamp, QWORD &duration, unsigned long &outputNum, unsigned long &flags, INSSBuffer *&sample); + + IWMReaderAdvanced *clock; + + QWORD startTime, clockTick, curTime; + DWORD startTimeMilliseconds; + bool realTime; + int lastOutputTime; +}; + +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/ConfigDialog.cpp b/Src/Plugins/Input/in_wmvdrm/ConfigDialog.cpp new file mode 100644 index 00000000..ece52a0e --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/ConfigDialog.cpp @@ -0,0 +1,398 @@ +#include "Main.h" +#include "resource.h" +#include "../nu/ListView.h" +#include "FileTypes.h" +#include "AutoChar.h" + +W_ListView typeList; +FileTypes::TypeList types; +#define CFGSET(hwnd, code, boolval) CheckDlgButton(hwnd, code, ((boolval)?BST_CHECKED:BST_UNCHECKED)) + +struct SpeakerSetup +{ + int description; + DWORD value; +}; +SpeakerSetup speakerList[] = +{ + {IDS_STEREO,DSSPEAKER_STEREO}, + {IDS_QUADROPHONIC,DSSPEAKER_QUAD}, + {IDS_SURROUND,DSSPEAKER_SURROUND}, + {IDS_5_1,DSSPEAKER_5POINT1}, + {IDS_7_1,DSSPEAKER_7POINT1}, +}; + +void FillFileTypes() +{ + typeList.Clear(); + + long attrCount = types.size(); + int pos=0; + for (long i = 0;i < attrCount;i++) + { + typeList.InsertItem(pos, types[i].wtype, 0); + typeList.SetItemText(pos, 1, types[i].description); + pos++; + } +} + +void ResetTypes() +{ + { + Nullsoft::Utility::AutoLock lock (fileTypes.typeGuard); + types=fileTypes.types; + } + FillFileTypes(); +} + +void Preferences_Populate(HWND hwndDlg) +{ + CFGSET(hwndDlg, IDC_HTTPMETA, config_http_metadata); + +/* CFGSET(hwndDlg, IDC_SILENT, !config_no_silent); + CFGSET(hwndDlg, IDC_UNTRUSTED, config_untrusted_ok); +*/ + CFGSET(hwndDlg, IDC_EXTRA_ASX, config_extra_asx_extensions); + + SetDlgItemInt(hwndDlg,IDC_BUFFER_TIME, config_buffer_time, FALSE/*unsigned*/); + + SendMessage(GetDlgItem(hwndDlg, IDC_AUDIO_SPEAKER_COUNT), CB_RESETCONTENT, 0, 0); + for (int i = 0;i < sizeof(speakerList)/sizeof(speakerList[0]) ;i++) + { + SendMessage(GetDlgItem(hwndDlg, IDC_AUDIO_SPEAKER_COUNT), CB_ADDSTRING, 0, (LPARAM) WASABI_API_LNGSTRINGW(speakerList[i].description)); + if (speakerList[i].value == config_audio_num_channels) + SendMessage(GetDlgItem(hwndDlg, IDC_AUDIO_SPEAKER_COUNT), CB_SETCURSEL, i, 0); + } + + ResetTypes(); +} +void Preferences_Init(HWND hwndDlg) +{ + typeList.setwnd(GetDlgItem(hwndDlg, IDC_TYPELIST)); + typeList.AddCol(WASABI_API_LNGSTRINGW(IDS_EXT), 75); + typeList.AddCol(WASABI_API_LNGSTRINGW(IDS_DESCRIPTION), 250); + Preferences_Populate(hwndDlg); + + if(config_col1 == -1) + typeList.AutoSizeColumn(0); + else + typeList.SetColumnWidth(0, config_col1); + + if(config_col2 == -1) + typeList.AutoSizeColumn(1); + else + typeList.SetColumnWidth(1, config_col2); + + if (NULL != WASABI_API_APP) + WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(typeList.getwnd(), TRUE); +} + +void Preferences_TypeRemove(HWND hwndDlg) +{ + int next = -1; + + do + { + next = typeList.GetNextSelected(next); + if (next != -1) + { + free(types[next].wtype); + types[next].wtype=0; + } + } while (next != -1); + + for (FileTypes::TypeList::iterator itr = types.begin(); itr != types.end(); ) + { + if (!itr->wtype || !itr->wtype[0]) + types.erase(itr); + else + itr++; + } +} + +void OnOk(HWND hwndDlg) +{ + config_http_metadata=IsDlgButtonChecked(hwndDlg, IDC_HTTPMETA)== BST_CHECKED; +// config_no_silent=IsDlgButtonChecked(hwndDlg, IDC_SILENT) != BST_CHECKED; +// config_untrusted_ok=IsDlgButtonChecked(hwndDlg, IDC_UNTRUSTED)== BST_CHECKED; + config_extra_asx_extensions=IsDlgButtonChecked(hwndDlg, IDC_EXTRA_ASX)== BST_CHECKED; + wchar_t temp[64] = {0}; + + GetDlgItemText(hwndDlg,IDC_BUFFER_TIME, temp, 64); + int newBufferTime= _wtoi(temp); + if (newBufferTime<1000) + newBufferTime=1000; + if (newBufferTime>60000) + newBufferTime=60000; + config_buffer_time=newBufferTime; + + int numChannels=SendMessage(GetDlgItem(hwndDlg, IDC_AUDIO_SPEAKER_COUNT), CB_GETCURSEL, 0, 0); + if (numChannels!=CB_ERR) + config_audio_num_channels = speakerList[numChannels].value; + + fileTypes.SetTypes(types); +} + +static INT_PTR CALLBACK AddTypeProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + CFGSET(hwndDlg, IDC_FILEEXTENSION, true); + CFGSET(hwndDlg, IDC_TYPE_AUDIO, true); + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_PROTOCOL: + SetDlgItemText(hwndDlg, IDC_STATIC_TYPE, WASABI_API_LNGSTRINGW(IDS_PROTOCOL)); + break; + case IDC_FILEEXTENSION: + SetDlgItemText(hwndDlg, IDC_STATIC_TYPE, WASABI_API_LNGSTRINGW(IDS_EXTENSION)); + break; + case IDOK: + { + wchar_t type[MAX_PATH] = {0}, description[1024] = {0}; + GetDlgItemText(hwndDlg,IDC_TYPE,type,MAX_PATH); + GetDlgItemText(hwndDlg,IDC_DESCRIPTION,description,1024); + bool isProtocol = IsDlgButtonChecked(hwndDlg, IDC_PROTOCOL)== BST_CHECKED; + int avType = IsDlgButtonChecked(hwndDlg, IDC_TYPE_VIDEO)== BST_CHECKED; + types.push_back(FileType(type, description, isProtocol, avType)); + EndDialog(hwndDlg, 0); + } + break; + case IDCANCEL: + EndDialog(hwndDlg, 0); + break; + } + break; + + } + return FALSE; +} + +void AddType(HWND hwndDlg) +{ + WASABI_API_DIALOGBOXW(IDD_ADDTYPE, hwndDlg, AddTypeProc); +} + +static size_t editIndex=0; +static INT_PTR CALLBACK EditTypeProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + SetWindowText(hwndDlg, WASABI_API_LNGSTRINGW(IDS_EDIT_FILE_TYPE)); + CFGSET(hwndDlg, IDC_TYPE_AUDIO, (types[editIndex].avType==FileType::AUDIO)); + CFGSET(hwndDlg, IDC_TYPE_VIDEO, (types[editIndex].avType==FileType::VIDEO)); + CFGSET(hwndDlg, IDC_PROTOCOL, types[editIndex].isProtocol); + CFGSET(hwndDlg, IDC_FILEEXTENSION, !types[editIndex].isProtocol); + SetDlgItemText(hwndDlg,IDC_TYPE, types[editIndex].wtype); + SetDlgItemText(hwndDlg,IDC_DESCRIPTION, types[editIndex].description); + if (types[editIndex].isProtocol) + SetDlgItemText(hwndDlg, IDC_STATIC_TYPE, WASABI_API_LNGSTRINGW(IDS_PROTOCOL)); + else + SetDlgItemText(hwndDlg, IDC_STATIC_TYPE, WASABI_API_LNGSTRINGW(IDS_EXTENSION)); + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_PROTOCOL: + SetDlgItemText(hwndDlg, IDC_STATIC_TYPE, WASABI_API_LNGSTRINGW(IDS_PROTOCOL)); + break; + case IDC_FILEEXTENSION: + SetDlgItemText(hwndDlg, IDC_STATIC_TYPE, WASABI_API_LNGSTRINGW(IDS_EXTENSION)); + break; + case IDOK: + { + wchar_t type[MAX_PATH] = {0}, description[1024] = {0}; + GetDlgItemText(hwndDlg,IDC_TYPE,type,MAX_PATH); + GetDlgItemText(hwndDlg,IDC_DESCRIPTION,description,1024); + bool isProtocol = IsDlgButtonChecked(hwndDlg, IDC_PROTOCOL)== BST_CHECKED; + + types[editIndex].avType = (IsDlgButtonChecked(hwndDlg, IDC_TYPE_VIDEO)== BST_CHECKED); + if(types[editIndex].wtype)free(types[editIndex].wtype); + types[editIndex].wtype = _wcsdup(type); + + types[editIndex].isProtocol = isProtocol; + if(types[editIndex].description)free(types[editIndex].description); + types[editIndex].description = _wcsdup(description); + EndDialog(hwndDlg, 0); + } + break; + case IDCANCEL: + EndDialog(hwndDlg, 0); + break; + } + break; + + } + return FALSE; +} + + +void EditType(HWND hwndDlg) +{ + editIndex=-1; + do + { + editIndex=typeList.GetNextSelected(editIndex); + if (editIndex != -1) + { + WASABI_API_DIALOGBOXW(IDD_ADDTYPE, hwndDlg, EditTypeProc); + } + else + return; + + } while (true); +} + + +void Advanced_Init(HWND hwndDlg) +{ + CFGSET(hwndDlg, IDC_AUDIO_THREAD, config_audio_dedicated_thread); + CFGSET(hwndDlg, IDC_AUDIO_EARLY, config_audio_early); + CFGSET(hwndDlg, IDC_AUDIO_OUTOFORDER, config_audio_outoforder); + CFGSET(hwndDlg, IDC_AUDIO_DROP, config_video_catchup); + + CFGSET(hwndDlg, IDC_VIDEO_THREAD, config_video_dedicated_thread); + CFGSET(hwndDlg, IDC_VIDEO_EARLY, config_video_early); + CFGSET(hwndDlg, IDC_VIDEO_OUTOFORDER, config_video_outoforder); + CFGSET(hwndDlg, IDC_VIDEO_NOTIFY, config_video_notifylate); + + CFGSET(hwndDlg, IDC_REALTIME, !config_clock); + CFGSET(hwndDlg, IDC_LOWMEMORY, config_lowmemory); + + SetDlgItemInt(hwndDlg,IDC_AUDIO_CACHE_FRAMES, config_audio_cache_frames, TRUE/*signed*/); + SetDlgItemInt(hwndDlg,IDC_VIDEO_CACHE_FRAMES, config_video_cache_frames, TRUE); + SetDlgItemInt(hwndDlg,IDC_VIDEO_DROP_THRESHOLD, config_video_drop_threshold, TRUE); + SetDlgItemInt(hwndDlg,IDC_VIDEO_JITTER, config_video_jitter, TRUE); + SetDlgItemInt(hwndDlg,IDC_AUDIO_EARLYPAD, config_audio_early_pad, TRUE); + SetDlgItemInt(hwndDlg,IDC_VIDEO_EARLYPAD, config_video_early_pad,TRUE); + +} + +void Advanced_OnOK(HWND hwndDlg) +{ + config_audio_dedicated_thread=IsDlgButtonChecked(hwndDlg, IDC_AUDIO_THREAD)== BST_CHECKED; + config_audio_early=IsDlgButtonChecked(hwndDlg, IDC_AUDIO_EARLY)== BST_CHECKED; + config_audio_outoforder=IsDlgButtonChecked(hwndDlg, IDC_AUDIO_OUTOFORDER)== BST_CHECKED; + config_video_catchup=IsDlgButtonChecked(hwndDlg, IDC_AUDIO_DROP)== BST_CHECKED; + + config_video_dedicated_thread=IsDlgButtonChecked(hwndDlg, IDC_VIDEO_THREAD)== BST_CHECKED; + config_video_early=IsDlgButtonChecked(hwndDlg, IDC_VIDEO_EARLY)== BST_CHECKED; + config_video_outoforder=IsDlgButtonChecked(hwndDlg, IDC_VIDEO_OUTOFORDER)== BST_CHECKED; + config_video_notifylate=IsDlgButtonChecked(hwndDlg, IDC_VIDEO_NOTIFY)== BST_CHECKED; + + config_clock=IsDlgButtonChecked(hwndDlg, IDC_REALTIME)!= BST_CHECKED; + config_lowmemory=IsDlgButtonChecked(hwndDlg, IDC_LOWMEMORY)== BST_CHECKED; + + wchar_t temp[64] = {0}; + + GetDlgItemText(hwndDlg,IDC_AUDIO_CACHE_FRAMES, temp, 64); + config_audio_cache_frames = _wtoi(temp); + + GetDlgItemText(hwndDlg,IDC_VIDEO_CACHE_FRAMES, temp, 64); + config_video_cache_frames= _wtoi(temp); + + GetDlgItemText(hwndDlg,IDC_VIDEO_DROP_THRESHOLD, temp, 64); + config_video_drop_threshold= _wtoi(temp); + + GetDlgItemText(hwndDlg,IDC_VIDEO_JITTER, temp, 64); + config_video_jitter= _wtoi(temp); + + GetDlgItemText(hwndDlg,IDC_AUDIO_EARLYPAD, temp, 64); + config_audio_early_pad= _wtoi(temp); + + GetDlgItemText(hwndDlg,IDC_VIDEO_EARLYPAD, temp, 64); + config_video_early_pad= _wtoi(temp); +} + +static INT_PTR CALLBACK AdvancedDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + Advanced_Init(hwndDlg); + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + Advanced_OnOK(hwndDlg); + EndDialog(hwndDlg, 0); + break; + case IDCANCEL: + EndDialog(hwndDlg, 0); + break; + + } + break; + } + + return 0; +} + +void Advanced(HWND hwndDlg) +{ + WASABI_API_DIALOGBOXW(IDD_ADVANCED, hwndDlg, AdvancedDialogProc); +} + +void Preferences_Default(HWND hwndDlg) +{ + if (config_no_video) + { + // TODO: ask the user if they want to enable video support + } + DefaultConfig(); + fileTypes.LoadDefaults(); + Preferences_Populate(hwndDlg); +} + +INT_PTR CALLBACK PreferencesDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + Preferences_Init(hwndDlg); + break; + case WM_DESTROY: + config_col1 = typeList.GetColumnWidth(0); + config_col2 = typeList.GetColumnWidth(1); + + if (NULL != WASABI_API_APP) + WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(typeList.getwnd(), FALSE); + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_ADVANCED: + Advanced(hwndDlg); + break; + case IDC_DEFAULTTYPE: + Preferences_Default(hwndDlg); + break; + case IDC_ADDTYPE: + AddType(hwndDlg); + FillFileTypes(); + break; + case IDC_EDITTYPE: + EditType(hwndDlg); + if(typeList.GetNextSelected(editIndex)!=-1) + FillFileTypes(); + break; + case IDC_REMOVETYPE: + Preferences_TypeRemove(hwndDlg); + FillFileTypes(); + break; + case IDOK: + OnOk(hwndDlg); + case IDCANCEL: + EndDialog(hwndDlg, 0); + break; + } + break; + } + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/ConfigDialog.h b/Src/Plugins/Input/in_wmvdrm/ConfigDialog.h new file mode 100644 index 00000000..e2b22e4f --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/ConfigDialog.h @@ -0,0 +1,4 @@ +#ifndef NULLSOFT_CONFIGDIALOGH +#define NULLSOFT_CONFIGDIALOGH +INT_PTR CALLBACK PreferencesDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/DESIGN.txt b/Src/Plugins/Input/in_wmvdrm/DESIGN.txt new file mode 100644 index 00000000..2785c5c5 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/DESIGN.txt @@ -0,0 +1,34 @@ +Windows Media works in an asynchronous manner.
+The WM Decompressor (IWMReader) requires that you provide a class
+deriving from IWMReader (and, optionally, IWMReaderAdvanced) to receive information
+Each delivery of uncompressed media arrives in an OnSample method
+Status messages arrive in an OnStatus method.
+
+The main difficulty that arrives from this approach is that ALL messages funnel through
+a single function. Audio data and Video data arrive in the same function.
+Even worse, status messages for file opened, clock error, http buffering, and DRM requirements all arrive in the same function!
+
+In order to handle this properly, a "chain of event handlers" class was developed (WMHandler)
+Any object wishing to receive data or status messages can derive from the WMHandler. All unhandled
+messages are automatically passed to the next handler in the chain. Messages that are handled can be
+passed or not passed based on programming requirements.
+
+The main object sets up the chain. The >> operator has been convienently defined to faciliate the chaining.
+The left side of the >> line must be the source (WMCallback)
+(e.g. callback >> clock >> drm >> video >> audio >> wait >> this;)
+Handlers may create their own mini-chains (which will get added in-whole) using the WMHandler::Inject() method.
+
+Threading
+------
+7 basic threads
+
+1. Main (Windows) thread
+2. Callback/OnStatus thread
+3. Audio Decoder thread
+4. Video Decoder thread
+5. Audio buffering thread (ours)
+6. Video buffering thread (ours)
+7. Buffering-percent status thread (ours)
+
+There might be additional threads (created in another module) calling in, for functions like getextendedfileinfo
+
diff --git a/Src/Plugins/Input/in_wmvdrm/ExtendedFileInfo.cpp b/Src/Plugins/Input/in_wmvdrm/ExtendedFileInfo.cpp new file mode 100644 index 00000000..53ee3cf4 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/ExtendedFileInfo.cpp @@ -0,0 +1,597 @@ +#include "main.h" +#include "../nu/AutoWide.h" +#include "WMPlaylist.h" +#include "resource.h" +#include <math.h> +#include <strsafe.h> + +WMInformation *setFileInfo = 0; +static wchar_t *setFileInfoName=0; +static bool forcedStop = false; +static int outTime = 0; +float GetGain(WMInformation *info, bool allowDefault); + +static const wchar_t *extension(const wchar_t *fn) +{ + const wchar_t *x = PathFindExtension(fn); + + if (*x) + return CharNext(x); + else + return x; +} + +static bool KeywordMatch(const char *mainString, const char *keyword) +{ + return !_stricmp(mainString, keyword); +} + +static bool KeywordMatch(const wchar_t *mainString, const wchar_t *keyword) +{ + return !lstrcmpiW(mainString, keyword); +} + +static bool StartsWith(const wchar_t *mainString, const wchar_t *substring) +{ + return !_wcsnicmp(mainString, substring, lstrlenW(substring)); +} + +static int Width(int dec) +{ + // there's probably a better way + int width=3; + while (width && (dec % 10) == 0) + { + dec/=10; + width--; + } + return width; +} + + +int GetExtendedInformation(WMInformation *getExtendedFileInfo, const wchar_t *fn, const char *data, wchar_t *dest, int destlen) +{ + AutoWide tagNameW(data); + const wchar_t *tagName = GetAlias(tagNameW); + + if (KeywordMatch(tagName, L"streammetadata")) + { + if (config_http_metadata) + { + lstrcpyn(dest, L"1", destlen); + return 1; + } + return 0; + } + else if (KeywordMatch(tagName, L"type")) + { + if (!fn || !fn[0]) + lstrcpyn(dest, (config_no_video ? L"0" : L"1"), destlen); + else if (getExtendedFileInfo->IsAttribute(g_wszWMHasVideo)) + lstrcpyn(dest, L"1", destlen); + else if (getExtendedFileInfo->IsAttribute(g_wszWMHasAudio)) + lstrcpyn(dest, L"0", destlen); + else + { + switch (fileTypes.GetAVType(extension(fn))) + { + case FileType::AUDIO: + lstrcpyn(dest, L"0", destlen); + break; + case FileType::VIDEO: + lstrcpyn(dest, L"1", destlen); + break; + default: + return 0; + } + } + return 1; + } + else if (KeywordMatch(tagName, L"rateable")) + { + dest[0] = '1'; + dest[1] = 0; + return 1; + } + else if (StartsWith(tagName, L"WM/")) + { + getExtendedFileInfo->GetAttribute(tagName, dest, destlen); + return 1; + } + /*else if (KeywordMatch(data, "burnable")) // note: this isn't supposed to be any kind of protection - just keeps the burner from misbehaving on protected tracks + { + if (getExtendedFileInfo->IsAttribute(g_wszWMProtected)) + lstrcpyn(dest, L"0", destlen); + else + lstrcpyn(dest, L"1", destlen); + + return 1; + }*/ + else if (KeywordMatch(data, "noburnreason")) // note: this isn't supposed to be any kind of protection - just keeps the burner from misbehaving on protected tracks + { + if (getExtendedFileInfo->IsAttribute(g_wszWMProtected)) + { + lstrcpyn(dest, L"DRM (copy protected) file", destlen); + return 1; + } + } + else if ((const wchar_t *)tagNameW != tagName) // if the tag was in the tag list + { + getExtendedFileInfo->GetAttribute(tagName, dest, destlen); + return 1; + } + else if (KeywordMatch(data, "bitrate")) + { + StringCchPrintfW(dest, destlen, L"%u", getExtendedFileInfo->GetBitrate() / 1000); + return 1; + } + else if (KeywordMatch(data, "vbr")) + { + if (getExtendedFileInfo->IsAttribute(g_wszWMIsVBR)) + StringCchCopyW(dest, destlen, L"1"); + else if (getExtendedFileInfo->IsNotAttribute(g_wszWMIsVBR)) + StringCchCopyW(dest, destlen, L"0"); + + return 1; + } + //else if (KeywordMatch(data, "srate")) + else if (KeywordMatch(data, "length")) + { + long length = getExtendedFileInfo->GetLengthMilliseconds(); + if (length == -1000) + return 0; + + _itow(length, dest, 10); + return 1; + } + else if (KeywordMatch(data, "rating")) + { + wchar_t rating_string[128] = {0}; + getExtendedFileInfo->GetAttribute(L"WM/SharedUserRating", rating_string, 128); + int rating = _wtoi(rating_string); + if (rating == 0) + dest[0]=0; + else if (rating >= 1 && rating <= 12) + dest[0]=L'1'; + else if (rating >= 13 && rating <= 37) + dest[0]=L'2'; + else if (rating >= 38 && rating <= 62) + dest[0]=L'3'; + else if (rating >= 63 && rating <= 86) + dest[0]=L'4'; + else + dest[0]=L'5'; + dest[1]=0; + return 1; + } + else if (KeywordMatch(data, "replaygain_track_gain") + || KeywordMatch(data, "replaygain_track_peak") + || KeywordMatch(data, "replaygain_album_gain") + || KeywordMatch(data, "replaygain_album_peak")) + { + getExtendedFileInfo->GetAttribute(tagName, dest, destlen); + return 1; + } + else if (KeywordMatch(data, "gain")) + { + StringCchPrintfW(dest, destlen, L"%-+.2f dB", (float)log10f(GetGain(getExtendedFileInfo, false))*20.0f); + return 1; + } + else if (KeywordMatch(data, "audiocodec")) + { + if (!getExtendedFileInfo->GetCodecName(dest, destlen)) + dest[0]=0; + return 1; + } + else if (KeywordMatch(data, "lossless")) + { + wchar_t codecname[1024] = {0}; + if (!getExtendedFileInfo->GetCodecName(codecname, 1024)) + dest[0]=0; + else + { + dest[0] = wcsstr(codecname, L"Lossless")?'1':'0'; + dest[1]=0; + } + return 1; + } + else if (KeywordMatch(data, "GracenoteFileID")) + { + getExtendedFileInfo->GetAttribute_BinString(L"GN/UniqueFileIdentifier", dest, destlen); + return 1; + } + else if (KeywordMatch(data, "GracenoteExtData")) + { + getExtendedFileInfo->GetAttribute_BinString(L"GN/ExtData", dest, destlen); + return 1; + } + else if (KeywordMatch(data, "formatinformation")) + { + // this is a bit of a clusterfuck, but it's safe and (hopefully) logically laid out. + wchar_t codec[128]=L"", duration[64]=L"", bitrate[64]=L"", filesize[64]=L"", wmver[64]=L"", seekable[64]=L""; + wchar_t stridable[64]=L"", broadcast[64]=L"", protect[64]=L"", trusted[64]=L"", contents[64]=L""; + wchar_t buf[128]=L""; // temporary buffer + + // get the codec name string + if (getExtendedFileInfo->GetCodecName(buf, 128) && buf[0]) + StringCchPrintf(codec,128,L"%s: %s\n",WASABI_API_LNGSTRINGW(IDS_CODEC),buf); + + // get the length string formatted h:mm:ss.tttt + long t = getExtendedFileInfo->GetLengthMilliseconds(); + if (t) + { + long h = t/36000000; + long m = (t/60000)%60; + long s = (t/1000)%60; + long ms = t%1000; + if (h) + StringCchPrintf(duration,64,L"%s: %u:%02u:%02u.%03u\n",WASABI_API_LNGSTRINGW(IDS_DURATION),h,m,s,ms); + else if (m) + StringCchPrintf(duration,64,L"%s: %u:%02u.%03u\n",WASABI_API_LNGSTRINGW(IDS_DURATION),m,s,ms); + else + StringCchPrintf(duration,64,L"%s: %u.%03u\n",WASABI_API_LNGSTRINGW(IDS_DURATION),s,ms); + } + + // get the bitrate string formatted 128.235 kbps + long br = getExtendedFileInfo->GetBitrate(); + wchar_t kbps[16] = {0}; + StringCchPrintf(bitrate,64,L"%s: %.*f %s\n", + WASABI_API_LNGSTRINGW(IDS_BITRATE), + Width(br%1000), + br/1000.0, + WASABI_API_LNGSTRINGW_BUF(IDS_KBPS,kbps,16)); + + // get the filesize string, with commas grouping in threes + buf[0]=0; + getExtendedFileInfo->GetAttribute(L"FileSize",buf,64); + uint64_t fs = _wcstoui64(buf, 0, 10); + if (fs) + { + uint64_t fsgb = (fs/1000000000LL); + uint64_t fsmb = (fs/1000000LL)%1000LL; + uint64_t fskb = (fs/1000LL)%1000LL; + uint64_t fsb = fs%1000LL; + if (fsgb) + StringCchPrintf(filesize,64,L"%s: %I64u,%03I64u,%03I64u,%03I64u\n",WASABI_API_LNGSTRINGW(IDS_FILESIZE),fsgb,fsmb,fskb,fsb); + else if (fsmb) + StringCchPrintf(filesize,64,L"%s: %I64u,%03I64u,%03I64u\n",WASABI_API_LNGSTRINGW(IDS_FILESIZE),fsmb,fskb,fsb); + else if (fskb) + StringCchPrintf(filesize,64,L"%s: %I64u,%03I64u\n",WASABI_API_LNGSTRINGW(IDS_FILESIZE),fskb,fsb); + else + StringCchPrintf(filesize,64,L"%s: %I64u\n",WASABI_API_LNGSTRINGW(IDS_FILESIZE),fsb); + } + + // 4 boolean flags, compose their strings + wchar_t yes[64] = {0}, no[64] = {0}; + WASABI_API_LNGSTRINGW_BUF(IDS_YES,yes,64); + WASABI_API_LNGSTRINGW_BUF(IDS_NO,no,64); + + buf[0]=0; + getExtendedFileInfo->GetAttribute(L"WMFSDKVersion",buf,128); + if (buf[0]) + StringCchPrintf(wmver,64,L"%s: %s\n",WASABI_API_LNGSTRINGW(IDS_WMVER),buf); + + StringCchPrintf(seekable,64,L"%s: %s\n",WASABI_API_LNGSTRINGW(IDS_SEEKABLE),getExtendedFileInfo->IsAttribute(L"Seekable")?yes:no); + StringCchPrintf(stridable,64,L"%s: %s\n",WASABI_API_LNGSTRINGW(IDS_STRIDABLE),getExtendedFileInfo->IsAttribute(L"Stridable")?yes:no); + StringCchPrintf(broadcast,64,L"%s: %s\n",WASABI_API_LNGSTRINGW(IDS_BROADCAST),getExtendedFileInfo->IsAttribute(L"Broadcast")?yes:no); + StringCchPrintf(protect,64,L"%s: %s\n",WASABI_API_LNGSTRINGW(IDS_PROTECTED),getExtendedFileInfo->IsAttribute(L"Is_Protected")?yes:no); + StringCchPrintf(trusted,64,L"%s: %s\n",WASABI_API_LNGSTRINGW(IDS_TRUSTED),getExtendedFileInfo->IsAttribute(L"Is_Trusted")?yes:no); + + // file contents. bit gross i know. + wchar_t cont[4][16]={L"",L"",L"",L""}; + int i=0; + if (getExtendedFileInfo->IsAttribute(L"HasAudio")) + WASABI_API_LNGSTRINGW_BUF(IDS_AUDIO,cont[i++],16); + if (getExtendedFileInfo->IsAttribute(L"HasVideo")) + WASABI_API_LNGSTRINGW_BUF(IDS_VIDEO,cont[i++],16); + if (getExtendedFileInfo->IsAttribute(L"HasImage")) + WASABI_API_LNGSTRINGW_BUF(IDS_IMAGE,cont[i++],16); + if (getExtendedFileInfo->IsAttribute(L"HasScript")) + WASABI_API_LNGSTRINGW_BUF(IDS_SCRIPT,cont[i++],16); + + WASABI_API_LNGSTRINGW_BUF(IDS_NONE,buf,64); + if (i == 0) + StringCchPrintf(contents,64,L"%s: %s",WASABI_API_LNGSTRINGW(IDS_CONTAINS), buf); + else if (i == 1) + StringCchPrintf(contents,64,L"%s: %s",WASABI_API_LNGSTRINGW(IDS_CONTAINS), cont[0]); + else if (i == 2) + StringCchPrintf(contents,64,L"%s: %s, %s",WASABI_API_LNGSTRINGW(IDS_CONTAINS), cont[0], cont[1]); + else if (i == 3) + StringCchPrintf(contents,64,L"%s: %s, %s, %s",WASABI_API_LNGSTRINGW(IDS_CONTAINS), cont[0], cont[1], cont[2]); + else if (i == 4) + StringCchPrintf(contents,64,L"%s: %s, %s, %s, %s",WASABI_API_LNGSTRINGW(IDS_CONTAINS), cont[0], cont[1], cont[2], cont[3]); + + // compose our string together! + StringCchPrintf(dest,destlen,L"%s%s%s%s%s%s%s%s%s%s%s",codec, duration, bitrate, filesize, wmver, seekable, stridable, broadcast, protect, trusted, contents); + } + else + return 0; + + return 1; +} + +#if 0 // had to disable this because it was locking the file from being deleted +WMInformation *lastGetInfo = 0; +wchar_t *lastGetInfoFn; +#endif + +extern "C" __declspec(dllexport) + int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, int destlen) +{ + /* Check if there's a status message for this filename + doing this forces Winamp to hit plugin.getfileinfo, which gives us better control + over adding things like [Individualizing] to the playlist title for local files + */ + if (winamp.HasStatus(fn)) + return 0; + + if ((!fn || !*fn) && KeywordMatch(data, "type")) + { + if (config_no_video) + lstrcpyn(dest, L"0", destlen); + else + lstrcpyn(dest, L"1", destlen); + return 1; + } + + + if (KeywordMatch(data, "mime")) + { + 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"WMA")) { StringCchCopyW(dest, destlen, L"audio/x-ms-wma"); return 1; } + if (!_wcsicmp(p, L"WMV")) { StringCchCopyW(dest, destlen, L"video/x-ms-wmv"); return 1; } + if (!_wcsicmp(p, L"ASF")) { StringCchCopyW(dest, destlen, L"video/x-ms-asf"); return 1; } + + return 0; + } + if (KeywordMatch(data, "family")) + { + LPCWSTR e, p(NULL); + DWORD lcid; + size_t i; + int len2(0); + + if (!fn || !*fn) return 0; + e = PathFindExtensionW(fn); + if (L'.' != *e) return 0; + e++; + + if (!*e) return 0; + lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); + + for (i = 0; i < fileTypes.types.size() && !p; i++) + { + if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, fileTypes.types.at(i).wtype, -1)) + p = fileTypes.types.at(i).description; + } + + if (p) + { + wchar_t szTest[16]; + if (S_OK == StringCchPrintfW(szTest, sizeof(szTest)/sizeof(wchar_t), L" (*.%s)", e)) + { + int len1 = lstrlenW(szTest); + len2 = lstrlenW(p); + if (len2 > len1 && CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, szTest, -1, (p + len2 - len1), -1)) + len2 -= len1; + } + } + + return (p && S_OK == StringCchCopyNW(dest, destlen, p, len2)); + } + + if (!config_http_metadata && PathIsURL(fn)) + return 0; + + if (KeywordMatch(data, "type") && !PathFileExistsW(fn)) + { + switch (fileTypes.GetAVType(extension(fn))) + { + case FileType::AUDIO: + lstrcpyn(dest, L"0", destlen); + return 1; + case FileType::VIDEO: + lstrcpyn(dest, L"1", destlen); + return 1; + default: + return 0; + } + } + + + + if (activePlaylist.IsMe(fn)) + { + WMInformation getExtendedFileInfo(activePlaylist.GetFileName()); + + return GetExtendedInformation(&getExtendedFileInfo, fn, data, dest, destlen); + } + else + { + WMInformation getExtendedFileInfo(fn); + + return GetExtendedInformation(&getExtendedFileInfo, fn, data, dest, destlen); + } + + #if 0 // had to disable this because it was locking the file from being deleted + if (lastGetInfo && lastGetInfoFn && !_wcsicmp(fn, lastGetInfoFn)) + { + return GetExtendedInformation(lastGetInfo, fn, data, dest, destlen); + } + + delete lastGetInfo; + lastGetInfo=0; + free(lastGetInfoFn); + lastGetInfoFn=0; + + if (activePlaylist.IsMe(fn)) + lastGetInfoFn = _wcsdup(activePlaylist.GetFileName()); + else + lastGetInfoFn = _wcsdup(fn); + + lastGetInfo = new WMInformation(lastGetInfoFn); + if (lastGetInfo->ErrorOpening()) + { + if (KeywordMatch(data, "type")) + { + switch (fileTypes.GetAVType(extension(fn))) + { + case FileType::AUDIO: + lstrcpyn(dest, L"0", destlen); + return 1; + case FileType::VIDEO: + lstrcpyn(dest, L"1", destlen); + return 1; + default: + return 0; + } + } + + delete lastGetInfo; + lastGetInfo=0; + free(lastGetInfoFn); + lastGetInfoFn=0; + return 0; + } + + return GetExtendedInformation(lastGetInfo, fn, data, dest, destlen); + #endif +} + +extern "C" __declspec(dllexport) int winampClearExtendedFileInfoW(const wchar_t *fn) +{ + // TODO: press stop if it's the currently playing file + WMInformation wmInfo(fn); + wmInfo.ClearAllAttributes(); + wmInfo.Flush(); + return 1; +} + +extern "C" __declspec(dllexport) int winampSetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *val) +{ + // if (!lastSetInfoFilename.empty() && lastSetInfoFilename != fn) + // dosomething(); + + #if 0 // had to disable this because it was locking the file from being deleted + if (lastGetInfoFn && !_wcsicmp(lastGetInfoFn,fn)) + { + delete lastGetInfo; + lastGetInfo=0; + free(lastGetInfoFn); + lastGetInfoFn=0; + } +#endif + + if (!setFileInfo) + { + if (activePlaylist.IsMe(fn) && mod.playing) + { + forcedStop = true; + outTime = mod.GetOutputTime(); + winamp.PressStop(); + } + free(setFileInfoName); + setFileInfoName = _wcsdup(fn); + setFileInfo = new WMInformation(fn); + if (!setFileInfo->MakeWritable(fn)) + return 0; // can't write + } + + AutoWide tagNameW(data); + const wchar_t *tagName = GetAlias(tagNameW); + + if (StartsWith(tagName, L"WM/")) + { + setFileInfo->SetAttribute(tagName, val); + return 1; + } + + else if ((const wchar_t *)tagNameW != tagName) // if the tag was in the tag list + { + setFileInfo->SetAttribute(tagName, val); + return 1; + } + else if (KeywordMatch(data, "rating")) + { + int rating = _wtoi(val); + if (rating == 0) + setFileInfo->SetAttribute(L"WM/SharedUserRating", L"",WMT_TYPE_DWORD); + else + { + switch(rating) + { + case 1: + setFileInfo->SetAttribute(L"WM/SharedUserRating", L"1",WMT_TYPE_DWORD); + break; + case 2: + setFileInfo->SetAttribute(L"WM/SharedUserRating", L"25",WMT_TYPE_DWORD); + break; + case 3: + setFileInfo->SetAttribute(L"WM/SharedUserRating", L"50",WMT_TYPE_DWORD); + break; + case 4: + setFileInfo->SetAttribute(L"WM/SharedUserRating", L"75",WMT_TYPE_DWORD); + break; + default: + setFileInfo->SetAttribute(L"WM/SharedUserRating", L"99",WMT_TYPE_DWORD); + break; + + } + } + } + else if (KeywordMatch(data, "replaygain_track_gain") + || KeywordMatch(data, "replaygain_track_peak") + || KeywordMatch(data, "replaygain_album_gain") + || KeywordMatch(data, "replaygain_album_peak")) + { + setFileInfo->SetAttribute(tagName, val); + return 1; + } + else if (KeywordMatch(data, "GracenoteFileID")) + { + setFileInfo->SetAttribute_BinString(L"GN/UniqueFileIdentifier", val); + return 1; + } + else if (KeywordMatch(data, "GracenoteExtData")) + { + setFileInfo->SetAttribute_BinString(L"GN/ExtData", val); + return 1; + } + + // else if (KeywordMatch(data, "bitrate")) + //else if (KeywordMatch(data, "disc")) + // else if (KeywordMatch(data, "vbr")) + //else if (KeywordMatch(data, "srate")) + // else if (KeywordMatch(data, "length")) + + return 0; +} + +extern "C" __declspec(dllexport) int winampWriteExtendedFileInfo() +{ + if (setFileInfo) + { + bool flushOK = setFileInfo->Flush(); + delete setFileInfo; + setFileInfo = 0; + + if (forcedStop) + { + mod.startAtMilliseconds = outTime; + winamp.PressPlay(); + } + forcedStop=false; + + if (flushOK) + return 1; + else + return 0; + } + + return 0; +} diff --git a/Src/Plugins/Input/in_wmvdrm/ExtendedRead.cpp b/Src/Plugins/Input/in_wmvdrm/ExtendedRead.cpp new file mode 100644 index 00000000..d07fa008 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/ExtendedRead.cpp @@ -0,0 +1,240 @@ +#include "main.h" +#include "ExtendedRead.h" + +extern WM_MEDIA_TYPE *NewMediaType(IWMOutputMediaProps *props); + +ExtendedReadStruct::ExtendedReadStruct() : buffer(0), reader(0), streamNum(0), bufferUsed(0), endOfFile(false), length(0) +{} + +ExtendedReadStruct::ExtendedReadStruct(IWMSyncReader *_reader) : buffer(0), reader(0), streamNum(0), bufferUsed(0), endOfFile(false), length(0) +{ + reader = _reader; + reader->AddRef(); +} + +#define CBCLASS ExtendedReadStruct +START_DISPATCH; +CB(IFC_AUDIOSTREAM_READAUDIO, ReadAudio) +END_DISPATCH; + +ExtendedReadStruct::~ExtendedReadStruct() +{ + if (reader) + { + reader->Close(); + reader->Release(); + } + if (buffer) + buffer->Release(); +} + +size_t ExtendedReadStruct::ReadAudio(void *_buffer, size_t len) +{ + __int8 *dest = reinterpret_cast<__int8 *>(_buffer); + int bytesCopied = 0; + /* + while we still have bytes left + { + read a buffer + copy buffer to user passed buffer + if we have stuff left in the buffer, save it and return + if we hit EOF, return + } + */ + size_t frameSize = (BitSize()/8)*Channels(); + len -= (len % frameSize); // only do whole frames + while (len) + { + if (buffer) + { + BYTE *bufferBytes; + DWORD bufferTotal; + buffer->GetBufferAndLength(&bufferBytes, &bufferTotal); + + if (bufferUsed < bufferTotal) + { + size_t toCopy = min(bufferTotal - bufferUsed, len); + memcpy(dest, bufferBytes + bufferUsed, toCopy); + bufferUsed += toCopy; + len -= toCopy; + dest += toCopy; + bytesCopied += toCopy; + + if (bufferUsed == bufferTotal) + { + bufferUsed = 0; + buffer->Release(); + buffer = 0; + } + } + } + else if (!endOfFile) + { + QWORD sampleTime, duration; + DWORD flags; + DWORD outputNum; + WORD streamNum2; + HRESULT hr; + hr = reader->GetNextSample(streamNum, &buffer, &sampleTime, &duration, &flags, & outputNum, &streamNum2); + if (hr == NS_E_NO_MORE_SAMPLES) + endOfFile = true; + + } + else + return bytesCopied; + } + + return bytesCopied; +} + +bool ExtendedReadStruct::Open(const wchar_t *filename) +{ + //mod.InitWM(); + if (!reader) + { + if (!SUCCEEDED(WMCreateSyncReader(NULL, 0, &reader))) + { + reader = 0; + return false; + } + } + + if (SUCCEEDED(reader->Open(filename))) + { + return true; + } + else + { + reader->Release(); + reader = 0; + return false; + } +} + +bool ExtendedReadStruct::FindOutput(int bits, int channels) +{ + DWORD numOutputs, output, format, numFormats; + IWMOutputMediaProps *formatProperties; + GUID mediaType; + DWORD audioOutputNum; + + if (FAILED((reader->GetOutputCount(&numOutputs)))) + return false; + + for (output = 0;output < numOutputs;output++) + { + HRESULT hr; + DWORD speakerConfig; + switch (channels) + { + case 1: + speakerConfig = DSSPEAKER_MONO; + break; + case 2: + speakerConfig = DSSPEAKER_STEREO; + break; + case 0: + default: + speakerConfig = config_audio_num_channels; // TODO: force max channels? + } + + hr = reader->SetOutputSetting(output, g_wszSpeakerConfig, WMT_TYPE_DWORD, (BYTE *) & speakerConfig, sizeof(speakerConfig)); + assert(hr == S_OK); + + BOOL discreteChannels = TRUE; + hr = reader->SetOutputSetting(output, g_wszEnableDiscreteOutput, WMT_TYPE_BOOL, (BYTE *) & discreteChannels , sizeof(discreteChannels)); + assert(hr == S_OK); + + if (FAILED(reader->GetOutputFormatCount(output, &numFormats))) + continue; + + for (format = 0;format < numFormats;format++) + { + reader->GetOutputFormat(output, format, &formatProperties); + formatProperties->GetType(&mediaType); + if (mediaType != WMMEDIATYPE_Audio) + continue; + + WM_MEDIA_TYPE *mediaType = NewMediaType(formatProperties); + if (mediaType->subtype == WMMEDIASUBTYPE_PCM) + { + if (bits) + { + WAVEFORMATEXTENSIBLE *waveFormat = (WAVEFORMATEXTENSIBLE *) mediaType->pbFormat; + if (waveFormat->Format.cbSize >= 22) + waveFormat->Samples.wValidBitsPerSample = bits; + waveFormat->Format.wBitsPerSample = bits; + waveFormat->Format.nBlockAlign = (waveFormat->Format.wBitsPerSample / 8) * waveFormat->Format.nChannels; + waveFormat->Format.nAvgBytesPerSec = waveFormat->Format.nSamplesPerSec * waveFormat->Format.nBlockAlign; + if (FAILED(formatProperties->SetMediaType(mediaType))) + { + // blah, just use the default settings then + delete[] mediaType; + mediaType = NewMediaType(formatProperties); + } + } + + AudioFormat::Open(mediaType); + delete[] mediaType; + audioOutputNum = output; + reader->SetOutputProps(audioOutputNum, formatProperties); + reader->GetStreamNumberForOutput(audioOutputNum, &streamNum); + formatProperties->Release(); + reader->SetReadStreamSamples(streamNum, FALSE); + + IWMHeaderInfo *headerInfo = 0; + reader->QueryInterface(&headerInfo); + + WMT_ATTR_DATATYPE type = WMT_TYPE_QWORD; + WORD byteLength = sizeof(QWORD); + WORD blah = 0; + headerInfo->GetAttributeByName(&blah, g_wszWMDuration, &type, (BYTE *)&length, &byteLength); + + headerInfo->Release(); + + return true; + } + + delete[] mediaType; + formatProperties->Release(); + } + } + return false; +} + +extern "C" + __declspec(dllexport) intptr_t winampGetExtendedRead_openW(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) +{ + ExtendedReadStruct *read = new ExtendedReadStruct; + + if (read->Open(fn) + && read->FindOutput(*bps, *nch)) + { + *nch = read->Channels(); + *bps = read->BitSize(); + *srate = read->SampleRate(); + __int64 bytespersec = ((*nch) * (*bps / 8) * (*srate)); + __int64 s = (bytespersec * ((__int64)read->length)) / (__int64)(1000 * 10000); + *size = (int)s; + return reinterpret_cast<intptr_t>(read); + } + else + { + delete read; + return 0; + } +} + +extern "C" + __declspec(dllexport) intptr_t winampGetExtendedRead_getData(intptr_t handle, char *dest, int len, int *killswitch) +{ + ExtendedReadStruct *read = reinterpret_cast<ExtendedReadStruct *>(handle); + return read->ReadAudio(dest, len); +} + +extern "C" + __declspec(dllexport) void winampGetExtendedRead_close(intptr_t handle) +{ + ExtendedReadStruct *read = reinterpret_cast<ExtendedReadStruct *>(handle); + delete read; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/ExtendedRead.h b/Src/Plugins/Input/in_wmvdrm/ExtendedRead.h new file mode 100644 index 00000000..1e9cd063 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/ExtendedRead.h @@ -0,0 +1,30 @@ +#ifndef NULLSOFT_IN_WMVDRM_EXTENDEDREAD_H +#define NULLSOFT_IN_WMVDRM_EXTENDEDREAD_H + +#include "AudioFormat.h" +#include "../Agave/DecodeFile/ifc_audiostream.h" +#include "main.h" + +struct ExtendedReadStruct : public AudioFormat, public ifc_audiostream +{ +public: + ExtendedReadStruct(); + ExtendedReadStruct(IWMSyncReader *_reader); + ~ExtendedReadStruct(); + + bool Open(const wchar_t *filename); + bool FindOutput(int bits, int channels); + size_t ReadAudio(void *buffer, size_t sizeBytes); + + IWMSyncReader *reader; + WORD streamNum; + + INSSBuffer *buffer; + size_t bufferUsed; + bool endOfFile; + QWORD length; +protected: + RECVS_DISPATCH; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/FactoryHelper.h b/Src/Plugins/Input/in_wmvdrm/FactoryHelper.h new file mode 100644 index 00000000..aeae00b5 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/FactoryHelper.h @@ -0,0 +1,24 @@ +#include "api.h" +#include <api/service/waservicefactory.h> + +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 = (api_T *)factory->getInterface(); + } +} + +template <class api_T> +void ServiceRelease(api_T *api_t, GUID factoryGUID_t) +{ + if (plugin.service) + { + waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t); + if (factory) + factory->releaseInterface(api_t); + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/FileInfoDialog.cpp b/Src/Plugins/Input/in_wmvdrm/FileInfoDialog.cpp new file mode 100644 index 00000000..eb0965f1 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/FileInfoDialog.cpp @@ -0,0 +1,831 @@ +#include "Main.h" +#include "FileInfoDialog.h" +#include "WMInformation.h" +#include "resource.h" +#include "../nu/AutoChar.h" + +#include "WMDRMModule.h" +#include "WMPlaylist.h" + +// blah! commented out a load of shit because we can't have the advanced pane edit data cause wma sucks. + +class Info +{ +public: + Info(const wchar_t *filename); + ~Info(); + //bool Save(HWND parent); + int Error(); + int GetNumMetadataItems(); + void EnumMetadata(int n,wchar_t *key,int keylen, wchar_t *val, int vallen); + //void RemoveMetadata(wchar_t * key); + //void RemoveMetadata(int n); + //void SetMetadata(wchar_t *key, wchar_t *val); + //void SetMetadata(int n, wchar_t *key, wchar_t *val); + //void SetTag(int n,wchar_t *key); // changes the key name +private: + WMInformation wminfo; + const wchar_t *filename; +}; + +Info::Info(const wchar_t *filename) : wminfo(filename), filename(filename) +{ +} + +Info::~Info() +{ +} +/* +bool Info::Save(HWND parent) +{ + if (!wminfo.MakeWritable(filename)) + { + wchar_t title[64] = {0}; + if (activePlaylist.IsMe(filename) && mod.playing) + { + // TODO: this is a race condition. we might have stopped in between the above if () and now... + int outTime = mod.GetOutputTime(); + winamp.PressStop(); + wminfo.MakeWritable(filename); + + SendMessage(parent,WM_USER+1,0,0); + //WriteEditBoxes(); + + wminfo.Flush(); + wminfo.MakeReadOnly(filename); + mod.startAtMilliseconds=outTime; + winamp.PressPlay(); + return true; + } + else if (!wminfo.MakeReadOnly(filename)) + MessageBox(parent, WASABI_API_LNGSTRINGW(IDS_UNABLE_TO_WRITE_TO_FILE_DOES_FILE_EXIST), + WASABI_API_LNGSTRINGW_BUF(IDS_UNABLE_TO_WRITE_TO_FILE,title,64), MB_OK); + else + MessageBox(parent, WASABI_API_LNGSTRINGW(IDS_UNABLE_TO_WRITE_TO_FILE_MAY_NOT_HAVE_ACCESS), + WASABI_API_LNGSTRINGW_BUF(IDS_UNABLE_TO_WRITE_TO_FILE,title,64), MB_OK); + + return false; + } + + SendMessage(parent,WM_USER+1,0,0); + //WriteEditBoxes(); + + if (!wminfo.Flush()) + { + wchar_t* title = WASABI_API_LNGSTRINGW(IDS_SAVE_FAILED); + MessageBox(NULL, title, title, MB_OK); + } + + wminfo.MakeReadOnly(filename); + return true; +} +*/ +int Info::Error() +{ + return wminfo.ErrorOpening(); +} + +int Info::GetNumMetadataItems() +{ + return wminfo.GetNumberAttributes(); +} + +void Info::EnumMetadata(int n,wchar_t *key,int keylen, wchar_t *val, int vallen) +{ + if(keylen) key[0]=0; + if(vallen) val[0]=0; + wminfo.GetAttribute(n, key, keylen, val, vallen); +} +/* +void Info::RemoveMetadata(wchar_t * key) +{ + wminfo.DeleteAttribute(key); +} + +void Info::RemoveMetadata(int n) +{ + wchar_t key[256] = {0}; + EnumMetadata(n,key,256,NULL,0); + if(key[0]) + RemoveMetadata(key); +} + +void Info::SetMetadata(wchar_t *key, wchar_t *val) +{ + wminfo.SetAttribute(key,val); +} + +void Info::SetTag(int n, wchar_t *key) +{ // changes the key name + wchar_t val[2048]=L""; + wchar_t oldkey[256]=L""; + EnumMetadata(n,oldkey,256,val,2048); + RemoveMetadata(oldkey); + wminfo.SetAttribute(key,val); +} +*/ +bool WMTagToWinampTag(wchar_t * tag, int len) +{ + const wchar_t *f = GetAlias_rev(tag); + if(f) + { + lstrcpyn(tag,f,len); + return true; + } + return false; +} + +bool WinampTagToWMTag(wchar_t * tag, int len) +{ + const wchar_t *f = GetAlias(tag); + if(f) + { + lstrcpyn(tag,f,len); + return true; + } + return false; +} + +static INT_PTR CALLBACK ChildProc_Advanced(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { + static int ismychange=0; + switch(msg) + { + case WM_NOTIFYFORMAT: + return NFR_UNICODE; + case WM_INITDIALOG: + { + //SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam); + HWND hwndlist = GetDlgItem(hwndDlg,IDC_LIST); + ListView_SetExtendedListViewStyle(hwndlist, LVS_EX_FULLROWSELECT); + LVCOLUMN lvc = {0, }; + lvc.mask = LVCF_TEXT|LVCF_WIDTH; + lvc.pszText = WASABI_API_LNGSTRINGW(IDS_NAME); + lvc.cx = 82; + ListView_InsertColumn(hwndlist, 0, &lvc); + lvc.pszText = WASABI_API_LNGSTRINGW(IDS_VALUE); + lvc.cx = 160; + ListView_InsertColumn(hwndlist, 1, &lvc); + + Info *info = (Info *)lParam; + int n = info->GetNumMetadataItems(); + for(int i=0; i<n; i++) { + wchar_t key[512] = {0}; + wchar_t value[2048] = {0}; + info->EnumMetadata(i,key,512,value,2048); + if(key[0]) { + LVITEMW lvi={LVIF_TEXT,i,0}; + lvi.pszText = key; + SendMessage(hwndlist,LVM_INSERTITEMW,0,(LPARAM)&lvi); + lvi.iSubItem=1; + lvi.pszText = value; + SendMessage(hwndlist,LVM_SETITEMW,0,(LPARAM)&lvi); + } + } + ListView_SetColumnWidth(hwndlist,0,LVSCW_AUTOSIZE); + ListView_SetColumnWidth(hwndlist,1,LVSCW_AUTOSIZE); + + //SetDlgItemTextW(hwndDlg,IDC_NAME,L""); + //SetDlgItemTextW(hwndDlg,IDC_VALUE,L""); + //EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),FALSE); + //EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),FALSE); + //EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),FALSE); + + delete info; + } + break; + case WM_DESTROY: + { + HWND hwndlist = GetDlgItem(hwndDlg,IDC_LIST); + ListView_DeleteAllItems(hwndlist); + while(ListView_DeleteColumn(hwndlist,0)); + Info * info = (Info*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + if(info) delete info; + } + break; + /* + case WM_USER+1: + { + Info *info = (Info *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + info->wminfo.ClearAllAttributes(); + wchar_t key[100] = {0}, value[2048] = {0}; + HWND hlist = GetDlgItem(hwndDlg,IDC_LIST); + int n = ListView_GetItemCount(hlist); + for(int i=0; i<n; i++) + { + key[0]=value[0]=0; + LVITEMW lvi={LVIF_TEXT,i,0}; + lvi.pszText=key; + lvi.cchTextMax=100; + SendMessage(hlist,LVM_GETITEMW,0,(LPARAM)&lvi); + lvi.iSubItem = 1; + lvi.pszText = value; + lvi.cchTextMax=2048; + SendMessage(hlist,LVM_GETITEMW,0,(LPARAM)&lvi); + if(key[0]) + info->SetMetadata(key,value); + } + } + break; + */ + case WM_USER: + if(wParam && lParam && !ismychange) + { + wchar_t * value = (wchar_t*)lParam; + wchar_t tag[100] = {0}; + lstrcpynW(tag,(wchar_t*)wParam,100); + WinampTagToWMTag(tag,100); + /* + Info *info = (Info *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if(!*value) info->RemoveMetadata(tag); + else info->SetMetadata(tag,value); + */ + HWND hlist = GetDlgItem(hwndDlg,IDC_LIST); + int n = ListView_GetItemCount(hlist); + for(int i=0; i<n; i++) + { + wchar_t key[100]=L""; + LVITEMW lvi={LVIF_TEXT,i,0}; + lvi.pszText=key; + lvi.cchTextMax=100; + SendMessage(hlist,LVM_GETITEMW,0,(LPARAM)&lvi); + if(!_wcsicmp(key,tag)) + { + lvi.iSubItem = 1; + lvi.pszText = value; + SendMessage(hlist,LVM_SETITEMW,0,(LPARAM)&lvi); + if(!*value) + ListView_DeleteItem(hlist,i); + /* + else if(ListView_GetItemState(hlist,i,LVIS_SELECTED)) + SetDlgItemTextW(hwndDlg,IDC_VALUE,value); + */ + return 0; + } + } + // bew hew, not found + LVITEMW lvi={0,0x7FFFFFF0,0}; + n = SendMessage(hlist,LVM_INSERTITEMW,0,(LPARAM)&lvi); + lvi.mask = LVIF_TEXT; + lvi.iItem = n; + lvi.iSubItem = 0; + lvi.pszText = tag; + SendMessage(hlist,LVM_SETITEMW,0,(LPARAM)&lvi); + lvi.iSubItem = 1; + lvi.pszText = value; + SendMessage(hlist,LVM_SETITEMW,0,(LPARAM)&lvi); + } + break; + /* + case WM_NOTIFY: + { + LPNMHDR l=(LPNMHDR)lParam; + if(l->idFrom==IDC_LIST && l->code == LVN_ITEMCHANGED) { + LPNMLISTVIEW lv=(LPNMLISTVIEW)lParam; + if(lv->uNewState & LVIS_SELECTED) { + int n = lv->iItem; + LVITEMW lvi={LVIF_TEXT,lv->iItem,0}; + wchar_t key[100] = {0}; + wchar_t value[1024] = {0}; + lvi.pszText=key; + lvi.cchTextMax=100; + SendMessage(l->hwndFrom,LVM_GETITEMW,0,(LPARAM)&lvi); + lvi.pszText=value; + lvi.cchTextMax=1024; + lvi.iSubItem=1; + SendMessage(l->hwndFrom,LVM_GETITEMW,0,(LPARAM)&lvi); + SetDlgItemTextW(hwndDlg,IDC_NAME,key); + SetDlgItemTextW(hwndDlg,IDC_VALUE,value); + sel = n; + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),TRUE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),TRUE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),TRUE); + } + if(lv->uOldState & LVIS_SELECTED) { + int n = lv->iItem; + sel = -1; + SetDlgItemTextW(hwndDlg,IDC_NAME,L""); + SetDlgItemTextW(hwndDlg,IDC_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),FALSE); + } + } + } + break; + */ + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDOK: + { + //Info * info = (Info*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + //info->Save(hwndDlg); + } + break; + /* + case IDC_NAME: + case IDC_VALUE: + if(HIWORD(wParam) == EN_CHANGE && sel>=0) { + wchar_t key[100] = {0}; + wchar_t value[1024] = {0}; + LVITEMW lvi={LVIF_TEXT,sel,0}; + GetDlgItemTextW(hwndDlg,IDC_NAME,key,100); + GetDlgItemTextW(hwndDlg,IDC_VALUE,value,1024); + lvi.pszText=key; + lvi.cchTextMax=100; + SendMessage(GetDlgItem(hwndDlg,IDC_LIST),LVM_SETITEMW,0,(LPARAM)&lvi); + lvi.pszText=value; + lvi.cchTextMax=1024; + lvi.iSubItem=1; + SendMessage(GetDlgItem(hwndDlg,IDC_LIST),LVM_SETITEMW,0,(LPARAM)&lvi); + WMTagToWinampTag(key,100); + ismychange=1; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)key,(WPARAM)value); + ismychange=0; + } + else if(HIWORD(wParam) == EN_KILLFOCUS && sel>=0) { + wchar_t key[100] = {0}; + wchar_t value[1024] = {0}; + GetDlgItemTextW(hwndDlg,IDC_NAME,key,100); + GetDlgItemTextW(hwndDlg,IDC_VALUE,value,1024); + Info *info = (Info *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + wchar_t oldkey[100]=L""; + bool newitem=true; + if(sel < info->GetNumMetadataItems()) { + info->EnumMetadata(sel,oldkey,100,0,0); + newitem=false; + } + + if(!newitem && wcscmp(oldkey,key)) { // key changed + info->SetTag(sel,key); + } else { + info->SetMetadata(key,value); + } + WMTagToWinampTag(key,100); + ismychange=1; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)key,(WPARAM)value); + ismychange=0; + } + break; + case IDC_BUTTON_DEL: + if(sel >= 0) { + wchar_t tag[100] = {0}; + GetDlgItemTextW(hwndDlg,IDC_NAME,tag,100); + SetDlgItemTextW(hwndDlg,IDC_NAME,L""); + SetDlgItemTextW(hwndDlg,IDC_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),FALSE); + Info *info = (Info *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if(sel < info->GetNumMetadataItems()) + info->RemoveMetadata(sel); + ListView_DeleteItem(GetDlgItem(hwndDlg,IDC_LIST),sel); + sel=-1; + WMTagToWinampTag(tag,100); + ismychange=1; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)tag,(WPARAM)L""); + ismychange=0; + } + break; + case IDC_BUTTON_DELALL: + ListView_DeleteAllItems(GetDlgItem(hwndDlg,IDC_LIST)); + SetDlgItemTextW(hwndDlg,IDC_NAME,L""); + SetDlgItemTextW(hwndDlg,IDC_VALUE,L""); + EnableWindow(GetDlgItem(hwndDlg,IDC_NAME),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_VALUE),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON_DEL),FALSE); + sel=-1; + { + Info *info = (Info *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + int n = info->GetNumMetadataItems(); + while(n>0) { + --n; + wchar_t tag[100] = {0}; + info->EnumMetadata(n,tag,100,0,0); + WMTagToWinampTag(tag,100); + ismychange=0; + SendMessage(GetParent(hwndDlg),WM_USER,(WPARAM)tag,(WPARAM)L""); + ismychange=1; + info->RemoveMetadata(n); + } + } + break; + case IDC_BUTTON_ADD: + { + HWND hwndlist = GetDlgItem(hwndDlg,IDC_LIST); + LVITEMW lvi={0,0x7FFFFFF0,0}; + int n = SendMessage(hwndlist,LVM_INSERTITEMW,0,(LPARAM)&lvi); + ListView_SetItemState(hwndlist,n,LVIS_SELECTED,LVIS_SELECTED); + } + break; + */ + } + break; + } + return 0; +} + +extern "C" +{ + // return 1 if you want winamp to show it's own file info dialogue, 0 if you want to show your own (via In_Module.InfoBox) + // if returning 1, remember to implement winampGetExtendedFileInfo("formatinformation")! + __declspec(dllexport) int winampUseUnifiedFileInfoDlg(const wchar_t * fn) + { + return 1; + } + + // should return a child window of 513x271 pixels (341x164 in msvc dlg units), or return NULL for no tab. + // Fill in name (a buffer of namelen characters), this is the title of the tab (defaults to "Advanced"). + // filename will be valid for the life of your window. n is the tab number. This function will first be + // called with n == 0, then n == 1 and so on until you return NULL (so you can add as many tabs as you like). + // The window you return will recieve WM_COMMAND, IDOK/IDCANCEL messages when the user clicks OK or Cancel. + // when the user edits a field which is duplicated in another pane, do a SendMessage(GetParent(hwnd),WM_USER,(WPARAM)L"fieldname",(LPARAM)L"newvalue"); + // this will be broadcast to all panes (including yours) as a WM_USER. + __declspec(dllexport) HWND winampAddUnifiedFileInfoPane(int n, const wchar_t * filename, HWND parent, wchar_t *name, size_t namelen) + { + if(n == 0) { // add first pane + //SetPropW(parent,L"INBUILT_NOWRITEINFO", (HANDLE)1); + Info *info = new Info(filename); + if(info->Error()) + { + delete info; + return NULL; + } + return WASABI_API_CREATEDIALOGPARAMW(IDD_INFO,parent,ChildProc_Advanced,(LPARAM)info); + } + return NULL; + } + +}; + +/* CUT> we're now using the unified file info dlg. I'll leave this commented out incase we want to do an advanced tab later on. + +#define CREATEDIALOGBOX DialogBoxParam +#define CLOSEDIALOGBOX(x) EndDialog(x, 0) +extern WMDRM mod; +enum +{ + AttributeColumn = 0, + ValueColumn = 1, +}; + +void FileInfoDialog::FillAttributeList() +{ + attributeList.Clear(); + WORD attrCount = wmInfo->GetNumberAttributes(); + wchar_t attrName[32768] = {0}, value[32768] = {0}; + int pos=0; + for (WORD i = 0;i != attrCount;i++) + { + wmInfo->GetAttribute(i, attrName, 32768, value, 32768); + // if (!AttributeInStandardEditor(attrName.c_str())) + attributeList.InsertItem(pos, (wchar_t *)attrName, 0); + attributeList.SetItemText(pos, 1, (wchar_t *)value); + pos++; + } + + attributeList.AutoColumnWidth(1); +} + +void FileInfoDialog::FillEditBoxes() +{ + wchar_t temp[32768] = {0}; + + wmInfo->GetAttribute(g_wszWMAuthor, temp, 32768); + SetWindowText(GetDlgItem(fileInfoHWND, IDC_EDIT_ARTIST), temp); + + wmInfo->GetAttribute(g_wszWMTitle, temp, 32768); + SetWindowText(GetDlgItem(fileInfoHWND, IDC_EDIT_TITLE), temp); + + wmInfo->GetAttribute(g_wszWMAlbumTitle, temp, 32768); + SetWindowText(GetDlgItem(fileInfoHWND, IDC_EDIT_ALBUM), temp); + + wmInfo->GetAttribute(g_wszWMDescription, temp, 32768); + SetWindowText(GetDlgItem(fileInfoHWND, IDC_EDIT_COMMENTS), temp); + + wmInfo->GetAttribute(g_wszWMGenre, temp, 32768); + SetWindowText(GetDlgItem(fileInfoHWND, IDC_EDIT_GENRE), temp); + + wmInfo->GetAttribute(g_wszWMYear, temp, 32768); + SetWindowText(GetDlgItem(fileInfoHWND, IDC_EDIT_YEAR), temp); + + wmInfo->GetAttribute(g_wszWMTrackNumber, temp, 32768); + SetWindowText(GetDlgItem(fileInfoHWND, IDC_EDIT_TRACK), temp); + + wmInfo->GetAttribute(g_wszWMPublisher, temp, 32768); + SetWindowText(GetDlgItem(fileInfoHWND, IDC_EDIT_PUBLISHER), temp); + + wmInfo->GetAttribute(g_wszWMAlbumArtist, temp, 32768); + SetWindowText(GetDlgItem(fileInfoHWND, IDC_EDIT_ALBUMARTIST), temp); +} + +void FileInfoDialog::Init(HWND _hwnd) +{ + fileInfoHWND = _hwnd; + + attributeList.setwnd(GetDlgItem(fileInfoHWND, IDC_METADATALIST)); + + attributeList.AddCol(WASABI_API_LNGSTRINGW(IDS_ATTRIBUTE), 150); + attributeList.AddCol(WASABI_API_LNGSTRINGW(IDS_VALUE), 1); + attributeList.AutoColumnWidth(1); + + if (fileNameToShow) + SetWindowText(GetDlgItem(fileInfoHWND, IDC_FILENAME), fileNameToShow); + else + SetWindowText(GetDlgItem(fileInfoHWND, IDC_FILENAME), fileName); + FillEditBoxes(); + FillAttributeList(); + + if (wmInfo->NonWritable()) + { + EnableWindow(GetDlgItem(fileInfoHWND, IDC_APPLY), FALSE); + EnableWindow(GetDlgItem(fileInfoHWND, IDC_REVERT), FALSE); + EnableWindow(GetDlgItem(fileInfoHWND, IDOK), FALSE); + SetDlgItemText(fileInfoHWND, IDCANCEL, WASABI_API_LNGSTRINGW(IDS_CLOSE)); + SendMessage(GetDlgItem(fileInfoHWND, IDC_EDIT_TITLE), EM_SETREADONLY, TRUE, 0); + SendMessage(GetDlgItem(fileInfoHWND, IDC_EDIT_ARTIST), EM_SETREADONLY, TRUE, 0); + SendMessage(GetDlgItem(fileInfoHWND, IDC_EDIT_ALBUM), EM_SETREADONLY, TRUE, 0); + SendMessage(GetDlgItem(fileInfoHWND, IDC_EDIT_COMMENTS), EM_SETREADONLY, TRUE, 0); + SendMessage(GetDlgItem(fileInfoHWND, IDC_EDIT_GENRE), EM_SETREADONLY, TRUE, 0); + SendMessage(GetDlgItem(fileInfoHWND, IDC_EDIT_YEAR), EM_SETREADONLY, TRUE, 0); + SendMessage(GetDlgItem(fileInfoHWND, IDC_EDIT_TRACK), EM_SETREADONLY, TRUE, 0); + SendMessage(GetDlgItem(fileInfoHWND, IDC_EDIT_PUBLISHER), EM_SETREADONLY, TRUE, 0); + SendMessage(GetDlgItem(fileInfoHWND, IDC_EDIT_ALBUMARTIST), EM_SETREADONLY, TRUE, 0); + } + SetFocus(GetDlgItem(fileInfoHWND, IDCANCEL)); + SendMessage(fileInfoHWND, DM_SETDEFID, GetDlgCtrlID(GetDlgItem(fileInfoHWND, IDCANCEL)),0); +} + +void FileInfoDialog::Revert() +{ + FillEditBoxes(); + FillAttributeList(); +} + +BOOL FileInfoDialog::MetadataList_Notify(NMHDR *header) +{ + switch (header->code) + { + + case LVN_ITEMCHANGED: + { + LPNMLISTVIEW lvNotif = (LPNMLISTVIEW)header; + if ((lvNotif->uOldState & LVIS_SELECTED) + && !(lvNotif->uNewState & LVIS_SELECTED)) + { + EnableWindow(GetDlgItem(fileInfoHWND, IDC_EDIT),0); + EnableWindow(GetDlgItem(fileInfoHWND, IDC_DELETE), 0); + } + if (lvNotif->uNewState & LVIS_SELECTED) + { + if (lvNotif->iItem != -1) + { + EnableWindow(GetDlgItem(fileInfoHWND, IDC_EDIT),1); + EnableWindow(GetDlgItem(fileInfoHWND, IDC_DELETE), 1); + } + + } + } + break; + } + return 0; +} + +bool FileInfoDialog::AttributeInStandardEditor(const wchar_t *attrName) +{ + return (!wcscmp(attrName, g_wszWMTitle) + ||!wcscmp(attrName, g_wszWMAuthor) + ||!wcscmp(attrName, g_wszWMAlbumTitle) + ||!wcscmp(attrName, g_wszWMDescription) + ||!wcscmp(attrName, g_wszWMGenre) + ||!wcscmp(attrName, g_wszWMYear) + ||!wcscmp(attrName, g_wszWMTrackNumber) + ||!wcscmp(attrName, g_wszWMPublisher) + || !wcscmp(attrName, g_wszWMAlbumArtist)); +} + +void FileInfoDialog::WriteEditBoxHelper(const wchar_t attrName[], DWORD IDC, wchar_t *&temp, int &size) +{ + int thisSize = GetWindowTextLength(GetDlgItem(fileInfoHWND, IDC))+1; + if (thisSize && thisSize>size) + { + if (temp) + delete[] temp; + temp = new wchar_t[thisSize]; + size=thisSize; + + } + + GetWindowText(GetDlgItem(fileInfoHWND, IDC), temp, size); + wmInfo->SetAttribute(attrName, temp); +} + +void FileInfoDialog::WriteEditBoxes() +{ + wchar_t *temp=0; + int thisSize=0; + int size=0; + + WriteEditBoxHelper(g_wszWMTitle, IDC_EDIT_TITLE, temp, size); + WriteEditBoxHelper(g_wszWMAuthor, IDC_EDIT_ARTIST, temp, size); + WriteEditBoxHelper(g_wszWMAlbumTitle, IDC_EDIT_ALBUM, temp, size); + WriteEditBoxHelper(g_wszWMDescription, IDC_EDIT_COMMENTS, temp, size); + WriteEditBoxHelper(g_wszWMGenre, IDC_EDIT_GENRE, temp, size); + WriteEditBoxHelper(g_wszWMYear, IDC_EDIT_YEAR, temp, size); + WriteEditBoxHelper(g_wszWMTrackNumber, IDC_EDIT_TRACK, temp, size); + WriteEditBoxHelper(g_wszWMPublisher, IDC_EDIT_PUBLISHER, temp, size); + WriteEditBoxHelper(g_wszWMAlbumArtist, IDC_EDIT_ALBUMARTIST, temp, size); +} + +void FileInfoDialog::WriteAttributeListA() +{ + int attributeTextLength=0, valueTextLength=0; + char *attribute=0, *value=0; + size_t numAttrs = attributeList.GetCount(); + for (size_t i=0;i!=numAttrs;i++) + { + int textLength; + textLength = attributeList.GetTextLength(i, 0); + if (textLength>attributeTextLength) + { + if (attribute) + delete[] attribute; + attribute = new char[textLength]; + attributeTextLength=textLength; + } + attributeList.GetText(i, 0, attribute, attributeTextLength); + + textLength = attributeList.GetTextLength(i, 0); + if (textLength>valueTextLength) + { + if (value) + delete[] value; + value = new char[textLength]; + valueTextLength=textLength; + } + attributeList.GetText(i, 0, value, valueTextLength); + } +} + +void FileInfoDialog::WriteAttributeList() +{ + int attributeTextLength=0, valueTextLength=0; + wchar_t *attribute=0, *value=0; + size_t numAttrs = attributeList.GetCount(); + for (size_t i=0;i!=numAttrs;i++) + { + int textLength; + textLength = attributeList.GetTextLength(i, 0); + if (textLength>attributeTextLength) + { + if (attribute) + delete[] attribute; + attribute = new wchar_t[textLength]; + attributeTextLength=textLength; + } + attributeList.GetText(i, 0, attribute, attributeTextLength); + + textLength = attributeList.GetTextLength(i, 0); + if (textLength>valueTextLength) + { + if (value) + delete[] value; + value = new wchar_t[textLength]; + valueTextLength=textLength; + } + attributeList.GetText(i, 0, value, valueTextLength); + } +} + +bool FileInfoDialog::Apply() +{ + edited=true; + if (!wmInfo->MakeWritable(fileName)) + { + wchar_t title[64] = {0}; + if (activePlaylist.IsMe(fileName) && mod.playing) + { + // TODO: this is a race condition. we might have stopped in between the above if () and now... + int outTime = mod.GetOutputTime(); + winamp.PressStop(); + wmInfo->MakeWritable(fileName); + WriteEditBoxes(); + wmInfo->Flush(); + wmInfo->MakeReadOnly(fileName); + mod.startAtMilliseconds=outTime; + winamp.PressPlay(); + return true; + } + else if (!wmInfo->MakeReadOnly(fileName)) + MessageBox(fileInfoHWND, WASABI_API_LNGSTRINGW(IDS_UNABLE_TO_WRITE_TO_FILE_DOES_FILE_EXIST), + WASABI_API_LNGSTRINGW_BUF(IDS_UNABLE_TO_WRITE_TO_FILE,title,64), MB_OK); + else + MessageBox(fileInfoHWND, WASABI_API_LNGSTRINGW(IDS_UNABLE_TO_WRITE_TO_FILE_MAY_NOT_HAVE_ACCESS), + WASABI_API_LNGSTRINGW_BUF(IDS_UNABLE_TO_WRITE_TO_FILE,title,64), MB_OK); + + return false; + } + + WriteEditBoxes(); + if (!wmInfo->Flush()) + { + wchar_t* title = WASABI_API_LNGSTRINGW(IDS_SAVE_FAILED); + MessageBox(NULL, title, title, MB_OK); + } + + wmInfo->MakeReadOnly(fileName); + return true; +} + +BOOL FileInfoDialog::OnOk() +{ + if (Apply()) + CLOSEDIALOGBOX(fileInfoHWND); + return 0; +} + +BOOL FileInfoDialog::OnCancel() +{ + CLOSEDIALOGBOX(fileInfoHWND); + return 0; +} + +INT_PTR WINAPI FileInfoDialog::FileInfoProc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp) +{ + FileInfoDialog *fileInfoDialog = (FileInfoDialog *)GetWindowLongPtr(wnd, GWLP_USERDATA); + switch (msg) + { + case WM_NOTIFYFORMAT: + return NFR_UNICODE; + + case WM_NOTIFY: + { + LPNMHDR l = (LPNMHDR)lp; + + if (l->hwndFrom == GetDlgItem(wnd, IDC_METADATALIST)) + return fileInfoDialog->MetadataList_Notify(l); + } + break; + + case WM_INITDIALOG: + SetWindowLongPtr(wnd, GWLP_USERDATA, (LONG_PTR)lp); + fileInfoDialog = (FileInfoDialog *)lp; + fileInfoDialog->Init(wnd); + return FALSE; + case WM_DESTROY: + if (fileInfoDialog) + { + //delete fileInfoDialog; + //fileInfoDialog=0; + SetWindowLongPtr(wnd, GWLP_USERDATA, 0); + } + break; + case WM_COMMAND: + switch (LOWORD(wp)) + { + case IDC_REVERT: + fileInfoDialog->Revert(); + break; + case IDCANCEL: + return fileInfoDialog->OnCancel(); + break; + case IDOK: + return fileInfoDialog->OnOk(); + break; + case IDC_APPLY: + fileInfoDialog->Apply(); + break; + } + break; + } + + return 0; +} + +FileInfoDialog::FileInfoDialog(HINSTANCE _hInstance, HWND parent,const wchar_t *_fileName) +: wmInfo(0),hInstance(_hInstance),fileName(0),fileNameToShow(0), edited(false) +{ + if (activePlaylist.IsMe(_fileName)) + { + fileName = _wcsdup(activePlaylist.GetFileName()); + fileNameToShow = _wcsdup(_fileName); + } + else + fileName = _wcsdup(_fileName); + + wmInfo = new WMInformation(fileName); + CREATEDIALOGBOX(hInstance, MAKEINTRESOURCE(IDD_FILEINFO), parent, FileInfoProc, (LPARAM)this); +} + +FileInfoDialog::~FileInfoDialog() +{ + delete wmInfo; + wmInfo=0; + free(fileName); + free(fileNameToShow); +} + +bool FileInfoDialog::WasEdited() +{ + return edited; +} +*/
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/FileInfoDialog.h b/Src/Plugins/Input/in_wmvdrm/FileInfoDialog.h new file mode 100644 index 00000000..76d9b7d9 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/FileInfoDialog.h @@ -0,0 +1,41 @@ +#ifndef NULLSOFT_FILEINFODIALOGH +#define NULLSOFT_FILEINFODIALOGH + +#include "../nu/listview.h" +#include "WMInformation.h" +/* CUT> we're now using the unified file info dlg. I'll leave this commented out incase we want to do an advanced tab later on. +class FileInfoDialog +{ +public: + FileInfoDialog(HINSTANCE _hInstance, HWND parent, const wchar_t *fileName); + ~FileInfoDialog(); + void Init(HWND _hwnd); + static INT_PTR WINAPI FileInfoProc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp); + BOOL MetadataList_Notify(NMHDR *header); + BOOL Edit_Notify(NMHDR *header); + BOOL OnOk(); + BOOL OnCancel(); + bool WasEdited(); +private: + void FillAttributeList(); + void WriteAttributeList(); + void WriteAttributeListA(); + void FillEditBoxes(); + void WriteEditBoxes(); + bool Apply(); + void Revert(); + void FileInfoDialog::WriteEditBoxHelper(const wchar_t attrName[], DWORD IDC, wchar_t *&temp, int &size); + bool AttributeInStandardEditor(const wchar_t *attrName); + HWND fileInfoHWND; + WMInformation *wmInfo; + W_ListView attributeList; + HINSTANCE hInstance; + + wchar_t *fileName; + wchar_t *fileNameToShow; + + bool edited; + +}; +*/ +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/FileTypes.cpp b/Src/Plugins/Input/in_wmvdrm/FileTypes.cpp new file mode 100644 index 00000000..839ae21b --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/FileTypes.cpp @@ -0,0 +1,179 @@ +#include "main.h" +#include "FileTypes.h" +#include "Config.h" +#include "../nu/Config.h" +#include "resource.h" +#include <strsafe.h> + +FileTypes fileTypes; +extern Nullsoft::Utility::Config wmConfig; + +bool FileTypes::IsSupportedURL(const wchar_t *fn) +{ + Nullsoft::Utility::AutoLock lock(typeGuard); + TypeList::iterator itr; + for (itr=types.begin();itr!=types.end();itr++) + { + if (itr->isProtocol && !_wcsnicmp(fn, itr->type, wcslen(itr->type))) + return true; + } + return false; +} + +bool FileTypes::IsDefault() +{ + return true; +} + +void FileTypes::LoadDefaults() +{ + Nullsoft::Utility::AutoLock lock(typeGuard); + types.clear(); + types.push_back(FileType(L"WMA", WASABI_API_LNGSTRINGW(IDS_WMA_AUDIO_FILE), false, FileType::AUDIO)); + if (!config_no_video) + { + types.push_back(FileType(L"WMV", WASABI_API_LNGSTRINGW(IDS_WMA_VIDEO_FILE), false, FileType::VIDEO)); + types.push_back(FileType(L"ASF", WASABI_API_LNGSTRINGW(IDS_ASF_STREAM), false, FileType::VIDEO)); + } + types.push_back(FileType(L"MMS://", WASABI_API_LNGSTRINGW(IDS_WINDOWS_MEDIA_STREAM), true, FileType::VIDEO)); + types.push_back(FileType(L"MMSU://", WASABI_API_LNGSTRINGW(IDS_WINDOWS_MEDIA_STREAM), true, FileType::VIDEO)); + types.push_back(FileType(L"MMST://", WASABI_API_LNGSTRINGW(IDS_WINDOWS_MEDIA_STREAM), true, FileType::VIDEO)); +} + +void FileTypes::ReadConfig() +{ + Nullsoft::Utility::AutoLock lock(typeGuard); + int numTypes = wmConfig.cfg_int(L"numtypes", -1); + if (numTypes!=-1) + { + for (size_t i=0;i!=numTypes;i++) + { + wchar_t type[1024] = {0}, description[1024] = {0}, temp[64] = {0}; + + StringCchPrintf(temp, 64, L"type%u", i); + wmConfig.cfg_str(temp).GetString(type, 1024); + StringCchPrintf(temp, 64, L"description%u", i); + wmConfig.cfg_str(temp).GetString(description, 1024); + StringCchPrintf(temp, 64, L"protocol%u", i); + bool protocol = !!wmConfig.cfg_int(temp, 0); + StringCchPrintf(temp, 64, L"avtype%u", i); + int avtype = !!wmConfig.cfg_int(temp, 0); + if (!(config_no_video && !protocol && avtype==FileType::VIDEO)) // if we havn't explicity disabled video support + types.push_back(FileType(type, description, protocol, avtype)); + } + } + else + fileTypes.LoadDefaults(); + + ResetTypes(); + numTypes=types.size(); + for (size_t i=0;i!=numTypes;i++) + { + if (!types[i].isProtocol) + AddType(AutoChar(types[i].type), AutoChar(types[i].description)); + } + + CheckVideo(); +} + +void FileTypes::SetTypes(TypeList &newTypes) +{ + Nullsoft::Utility::AutoLock lock(typeGuard); + types=newTypes; + ResetTypes(); + int numTypes=types.size(); + for (size_t i=0;i!=numTypes;i++) + { + if (!types[i].isProtocol) + AddType(AutoChar(types[i].type), AutoChar(types[i].description)); + } + + CheckVideo(); +} +void FileTypes::SaveConfig() +{ + Nullsoft::Utility::AutoLock lock(typeGuard); + wmConfig.cfg_int(L"numtypes", -1) = types.size(); + for (size_t i=0;i!=types.size();i++) + { + wchar_t temp[64] = {0}; + + StringCchPrintf(temp, 64, L"type%u", i); + wmConfig.cfg_str(temp) = types[i].wtype; + StringCchPrintf(temp, 64, L"description%u", i); + wmConfig.cfg_str(temp) = types[i].description; + StringCchPrintf(temp, 64, L"protocol%u", i); + wmConfig.cfg_int(temp, 0) = types[i].isProtocol; + StringCchPrintf(temp, 64, L"avtype%u", i); + wmConfig.cfg_int(temp, 0) = types[i].avType; + } +} + +void FileTypes::CheckVideo() +{ + bool videoPresent=false; + Nullsoft::Utility::AutoLock lock(typeGuard); + //wmConfig.cfg_int(L"numtypes", -1) = types.size(); + for (size_t i=0;i!=types.size();i++) + if (types[i].avType == FileType::VIDEO) + videoPresent=true; + + config_no_video = !videoPresent; +} + +void FileTypes::ResetTypes() +{ + free(typesString); + typesString=0; +} + +void FileTypes::AddType(const char *extension, const char *description) +{ + size_t oldSize=0, size=0; + + if (typesString) + { + char *temp=typesString; + while (temp && *temp++) + { + size++; + if (*temp == 0) + { + size++; + temp++; + } + } ; + oldSize=size; + } + size += lstrlenA(extension)+1; + size += lstrlenA(description)+1; + + char *newTypes = (char *)calloc(size+1, sizeof(char)); + if (newTypes) + { + memcpy(newTypes, typesString, oldSize); + free(typesString); + typesString=newTypes; + newTypes += oldSize; + size-=oldSize; + StringCchCopyA(newTypes, size, extension); + int extSize = lstrlenA(extension)+1; + newTypes+=extSize; + size-=extSize; + StringCchCopyA(newTypes, size, description); + newTypes+=lstrlenA(description)+1; + *newTypes=0; + plugin.FileExtensions = typesString; + } +} + +int FileTypes::GetAVType(const wchar_t *ext) +{ + Nullsoft::Utility::AutoLock lock(typeGuard); + for (size_t i=0;i!=types.size();i++) + { + if (!types[i].isProtocol && !lstrcmpi(types[i].type, ext)) + return types[i].avType; + } + return -1; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/FileTypes.h b/Src/Plugins/Input/in_wmvdrm/FileTypes.h new file mode 100644 index 00000000..335ddb70 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/FileTypes.h @@ -0,0 +1,88 @@ +#ifndef NULLSOFT_FILETYPESH +#define NULLSOFT_FILETYPESH + +#include <vector> +#include "../nu/AutoLock.h" +#include "AutoChar.h" + +class FileType +{ +public: + enum + { + AUDIO = 0, + VIDEO = 1, + }; + FileType() : wtype(0), type(0), description(0), isProtocol(false), avType(AUDIO) + { + } + + FileType(wchar_t *_type, wchar_t *_description, bool _protocol, int _avType) + { + wtype=_wcsdup(_type); + type = _wcsdup(_type); + description = _wcsdup(_description); + isProtocol = _protocol; + avType = _avType; + } + ~FileType() + { + free(type); + free(wtype); + free(description); + } + FileType(const FileType ©) :wtype(0), type(0), description(0) + { + operator =(copy); + } + void operator =(const FileType ©) + { + + if (copy.wtype) + wtype=_wcsdup(copy.wtype); + if (copy.type) + type = _wcsdup(copy.type); + if (copy.description) + description = _wcsdup(copy.description); + isProtocol = copy.isProtocol; + avType = copy.avType; + } + wchar_t *type; + wchar_t *wtype; + wchar_t *description; + bool isProtocol; + int avType; // audio or video +}; + +class FileTypes +{ +public: + FileTypes() + : typesString(0), + typeGuard(GUARDNAME("FileTypes::typeGuard")) + {} + ~FileTypes() + { + free(typesString); + typesString=0; + } + int GetAVType(const wchar_t *ext); + bool IsSupportedURL(const wchar_t *fn); + bool IsDefault(); + void LoadDefaults(); + void ReadConfig(); + void SaveConfig(); + void CheckVideo(); + + typedef std::vector<FileType> TypeList; + void SetTypes(TypeList &newTypes); + TypeList types; + Nullsoft::Utility::LockGuard typeGuard; +private: + char *typesString; + void ResetTypes(); + void AddType(const char *type, const char *description); +}; + +extern FileTypes fileTypes; +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/GainLayer.cpp b/Src/Plugins/Input/in_wmvdrm/GainLayer.cpp new file mode 100644 index 00000000..84811f79 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/GainLayer.cpp @@ -0,0 +1,232 @@ +#include "main.h" +#include "GainLayer.h" +#include <malloc.h> +#include <math.h> +#include <locale.h> +#include "api.h" + +static void FillFloat(float *floatBuf, void *samples, size_t bps, size_t numSamples, size_t numChannels, float preamp) +{ + switch (bps) + { + case 8: + { + unsigned __int8 *samples8 = (unsigned __int8 *)samples; + size_t totalSamples = numSamples * numChannels; + for (size_t x = 0; x != totalSamples; x ++) + { + floatBuf[x] = (float)(samples8[x] - 128) * preamp; + } + } + break; + case 16: + { + short *samples16 = (short *)samples; + size_t totalSamples = numSamples * numChannels; + for (size_t x = 0; x != totalSamples; x ++) + { + floatBuf[x] = (float)samples16[x] * preamp; + } + } + break; + case 24: + { + unsigned __int8 *samples8 = (unsigned __int8 *)samples; + size_t totalSamples = numSamples * numChannels; + for (size_t x = 0; x != totalSamples; x ++) + { + long temp = (((long)samples8[0]) << 8); + temp = temp | (((long)samples8[1]) << 16); + temp = temp | (((long)samples8[2]) << 24); + floatBuf[x] = (float)temp * preamp; + samples8 += 3; + } + } + break; + } +} + +inline static float fastclip(float x, const float a, const float b) +{ + float x1 = (float)fabs (x - a); + float x2 = (float)fabs (x - b); + x = x1 + (a + b); + x -= x2; + x *= 0.5f; + return (x); +} + +#define PA_CLIP_( val, min, max )\ + { val = ((val) < (min)) ? (min) : (((val) > (max)) ? (max) : (val)); } + +void Float32_To_Int16_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count) +{ + float *src = (float*)sourceBuffer; + signed short *dest = (signed short*)destinationBuffer; + + while ( count-- ) + { + long samp = lrint(*src); + + PA_CLIP_( samp, -0x8000, 0x7FFF ); + *dest = (signed short) samp; + + src += sourceStride; + dest += destinationStride; + } +} +inline static void clip(double &x, double a, double b) +{ + double x1 = fabs (x - a); + double x2 = fabs (x - b); + x = x1 + (a + b); + x -= x2; + x *= 0.5; +} + +void Float32_To_Int24_Clip( + void *destinationBuffer, signed int destinationStride, + void *sourceBuffer, signed int sourceStride, + unsigned int count) +{ + float *src = (float*)sourceBuffer; + unsigned char *dest = (unsigned char*)destinationBuffer; + + while ( count-- ) + { + /* convert to 32 bit and drop the low 8 bits */ + double scaled = *src; + clip( scaled, -2147483648., 2147483647. ); + signed long temp = (signed long) scaled; + + dest[0] = (unsigned char)(temp >> 8); + dest[1] = (unsigned char)(temp >> 16); + dest[2] = (unsigned char)(temp >> 24); + src += sourceStride; + dest += destinationStride * 3; + } +} + +static void FillSamples(void *samples, float *floatBuf, size_t bps, size_t numSamples, size_t numChannels) +{ + switch (bps) + { + case 16: + Float32_To_Int16_Clip(samples, 1, floatBuf, 1, numSamples*numChannels); + break; + case 24: + Float32_To_Int24_Clip(samples, 1, floatBuf, 1, numSamples*numChannels); + break; + + } +} + +float GetGain(WMInformation *info, bool allowDefault) +{ + if (AGAVE_API_CONFIG && AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false)) + { + float dB = 0, peak = 1.0f; + wchar_t gain[64]=L"", peakVal[64]=L""; + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_source", 0)) + { + case 0: // track + info->GetAttribute(L"replaygain_track_gain", gain, 64); + if (!gain[0] && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + info->GetAttribute(L"replaygain_album_gain", gain, 64); + + info->GetAttribute(L"replaygain_track_peak", peakVal, 64); + if (!peakVal[0] && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + info->GetAttribute(L"replaygain_album_peak", peakVal, 64); + + break; + case 1: + info->GetAttribute(L"replaygain_album_gain", gain, 64); + if (!gain[0] && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + info->GetAttribute(L"replaygain_track_gain", gain, 64); + + info->GetAttribute(L"replaygain_album_peak", peakVal, 64); + if (!peakVal[0] && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + info->GetAttribute(L"replaygain_track_peak", peakVal, 64); + + break; + } + + _locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale(); + + if (gain[0]) + { + if (gain[0] == L'+') + dB = static_cast<float>(_wtof_l(&gain[1],C_locale)); + else + dB = static_cast<float>(_wtof_l(gain,C_locale)); + } + else if (allowDefault) + { + dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0); + return powf(10.0f, dB / 20.0f); + } + + if (peakVal[0]) + { + peak = static_cast<float>(_wtof_l(peakVal,C_locale)); + } + + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_mode", 1)) + { + case 0: // apply gain + return powf(10.0f, dB / 20.0f); + case 1: // apply gain, but don't clip + return min(powf(10.0f, dB / 20.0f), 1.0f / peak); + case 2: // normalize + return 1.0f / peak; + case 3: // prevent clipping + if (peak > 1.0f) + return 1.0f / peak; + else + return 1.0f; + } + + } + + return 1.0f; // no gain +} + + +void GainLayer::AudioDataReceived(void *_data, unsigned long sizeBytes, DWORD timestamp) +{ + if (enabled) + { + size_t samples = audio->AudioBytesToSamples(sizeBytes); + int channels = audio->Channels(); + if (floatSize < (samples * channels)) + { + delete [] floatData; + floatSize = samples * channels; + floatData = new float[floatSize]; + } + if (outSize < sizeBytes) + { + delete [] outData; + outSize=sizeBytes; + outData = (void *)new __int8[sizeBytes]; + } + + FillFloat(floatData, _data, audio->BitSize(), samples , channels, replayGain); + FillSamples(outData, floatData, audio->BitSize(), samples , channels); + + WMHandler::AudioDataReceived(outData, sizeBytes, timestamp); + } + else + WMHandler::AudioDataReceived(_data, sizeBytes, timestamp); +} + +void GainLayer::Opened() +{ + enabled= (AGAVE_API_CONFIG && AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false)); + if (enabled) + replayGain = GetGain(info, true); + WMHandler::Opened(); +} diff --git a/Src/Plugins/Input/in_wmvdrm/GainLayer.h b/Src/Plugins/Input/in_wmvdrm/GainLayer.h new file mode 100644 index 00000000..6eb6c67d --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/GainLayer.h @@ -0,0 +1,32 @@ +#ifndef NULLSOFT_GAIN_LAYER_H +#define NULLSOFT_GAIN_LAYER_H + +#include "WMHandler.h" +#include "AudioFormat.h" +#include "WMInformation.h" +class GainLayer : public WMHandler +{ +public: + GainLayer(AudioFormat *_audio, WMInformation *_info) + : audio(_audio), info(_info), enabled(false), replayGain(1.0f), + floatData(0),floatSize(0), outData(0), outSize(0) + {} + ~GainLayer() + { + delete[]floatData; + delete[]outData; + } + void AudioDataReceived(void *_data, unsigned long sizeBytes, DWORD timestamp); + void Opened(); + AudioFormat *audio; + WMInformation *info; + bool enabled; + float replayGain; + + float *floatData; + size_t floatSize; + + void *outData; + size_t outSize; +}; +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/Main.h b/Src/Plugins/Input/in_wmvdrm/Main.h new file mode 100644 index 00000000..85b3e4b5 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/Main.h @@ -0,0 +1,52 @@ +#ifndef NULLSOFT_MAINH +#define NULLSOFT_MAINH + +#define WMDRM_VERSION L"3.95" + +#include "WinampInterface.h" +#include "../Winamp/in2.h" +#include <windows.h> +#include "WMDRMModule.h" +#include <shlwapi.h> +#include <wmsdk.h> +#include "config.h" +#include "WMHandler.h" +#include "util.h" +#include "FileTypes.h" +#include "TagAlias.h" +#include "WMInformation.h" +#include "../nu/AutoWide.h" +#include "../nu/AutoChar.h" +#include "vidutils.h" +#include "api.h" + +extern WMInformation *setFileInfo; + +struct IDispatch; +extern IDispatch *winampExternal; + +extern WinampInterface winamp; +extern In_Module plugin; + +extern WMDRM mod; + +#ifdef _DEBUG +#define SHOW_CALLBACKS +#endif + +//#define SHOW_CALLBACKS + +#ifdef SHOW_CALLBACKS +#include <iostream> +#define WMTCASE(sw) case sw: std::cerr << #sw << std::endl; +#define WMT_SHOW_HR_CODE(hr) std::cerr << HRErrorCode(hr) << std::endl; +#else +#define WMTCASE(sw) case sw: +#define WMT_SHOW_HR_CODE(hr) +#endif + +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = +{ 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } }; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/MediaThread.cpp b/Src/Plugins/Input/in_wmvdrm/MediaThread.cpp new file mode 100644 index 00000000..1120c952 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/MediaThread.cpp @@ -0,0 +1,110 @@ +#include "main.h" +#include "MediaThread.h" +#include "config.h" + +MediaThread::MediaThread() : wait(INFINITE), thread(0) +{ + killEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + stopped = CreateEvent(NULL, TRUE, TRUE, NULL); + bufferFreed = CreateEvent(NULL, TRUE, TRUE, NULL); +} + +MediaThread::~MediaThread() +{ + Kill(); + if (thread) + CloseHandle(thread); +} + +VOID CALLBACK MediaThread_StartAPC(ULONG_PTR param) +{ + reinterpret_cast<MediaThread *>(param)->StartAPC(); +} + +void MediaThread::StartAPC() +{ + wait=config_video_jitter; +} + +void MediaThread::StopAPC() +{ + BufferList::iterator itr; + for (itr = buffers.begin();itr != buffers.end();itr++) + { + (*itr)->buffer->Release(); + delete (*itr); + } + + buffers.clear(); + SetEvent(stopped); + SetEvent(bufferFreed); + wait=INFINITE; +} + +static VOID CALLBACK MediaThread_StopAPC(ULONG_PTR param) +{ + reinterpret_cast<MediaThread *>(param)->StopAPC(); +} + +void MediaThread::Stop() +{ + ResetEvent(stopped); + QueueUserAPC(MediaThread_StopAPC, thread, reinterpret_cast<ULONG_PTR>(this)); + WaitForSingleObject(stopped, INFINITE); +} + +void MediaThread::WaitForStop() +{ + WaitForSingleObject(stopped, INFINITE); +} + +void MediaThread::SignalStop() +{ + ResetEvent(stopped); + QueueUserAPC(MediaThread_StopAPC, thread, reinterpret_cast<ULONG_PTR>(this)); +} + +void MediaThread::Kill() +{ + SetEvent(killEvent); + WaitForSingleObject(stopped, INFINITE); +} + +void MediaThread::OrderedInsert(MediaBuffer *buffer) +{ + BufferList::iterator itr; + for (itr = buffers.begin();itr != buffers.end(); itr++) + { + if ((*itr)->timestamp > buffer->timestamp) + { + buffers.insert(itr, buffer); + break; + } + } + if (itr == buffers.end()) + buffers.push_back(buffer); + +} + + +VOID CALLBACK MediaThread_AddAPC(ULONG_PTR param) +{ + MediaBufferAPC *apc = reinterpret_cast<MediaBufferAPC *>(param); + apc->thread->AddAPC(apc->buffer); + delete apc; +} + +bool MediaThread::AddBuffer(INSSBuffer *buff, QWORD ts, unsigned long flags, bool drmProtected) +{ + if (WaitForSingleObject(bufferFreed, 0) == WAIT_TIMEOUT) + return false; + + buff->AddRef(); + MediaBuffer *buffer = new MediaBuffer(buff, ts, flags, drmProtected); + MediaBufferAPC *apc = new MediaBufferAPC; + apc->buffer = buffer; + apc->thread = this; + QueueUserAPC(MediaThread_AddAPC, thread, reinterpret_cast<ULONG_PTR>(apc)); + Sleep(config_video_jitter); // sleep for a bit to keep the thread from going nuts + return true; // added +} diff --git a/Src/Plugins/Input/in_wmvdrm/MediaThread.h b/Src/Plugins/Input/in_wmvdrm/MediaThread.h new file mode 100644 index 00000000..3dd3fdcb --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/MediaThread.h @@ -0,0 +1,56 @@ +#ifndef NULLSOFT_MEDIATHREADH +#define NULLSOFT_MEDIATHREADH + +#include <deque> +#include <wmsdk.h> +#include <vector> + +VOID CALLBACK MediaThread_StartAPC(ULONG_PTR param); +VOID CALLBACK MediaThread_AddAPC(ULONG_PTR param); +struct MediaBuffer +{ + MediaBuffer(INSSBuffer *b, QWORD t, unsigned long f, bool d) : buffer(b), timestamp(t), flags(f), drmProtected(d) {} + INSSBuffer *buffer; + QWORD timestamp; + unsigned long flags; + bool drmProtected; +}; +struct MediaBufferAPC; + +class MediaThread +{ +public: + MediaThread(); + ~MediaThread(); + + bool AddBuffer(INSSBuffer *buff, QWORD ts, unsigned long flags, bool drmProtected); + + void Stop(); + void SignalStop(); + void WaitForStop(); + void Kill(); + +public: + void StopAPC(); + void StartAPC(); + + virtual void AddAPC(MediaBuffer *buffer)=0; + +protected: + void OrderedInsert(MediaBuffer *buffer); + +protected: + int wait; + HANDLE thread; + HANDLE killEvent, stopped, bufferFreed; + + typedef std::vector<MediaBuffer*> BufferList; + BufferList buffers; +}; + +struct MediaBufferAPC +{ + MediaBuffer *buffer; + MediaThread *thread; +}; +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/MetaTag.cpp b/Src/Plugins/Input/in_wmvdrm/MetaTag.cpp new file mode 100644 index 00000000..9eecb772 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/MetaTag.cpp @@ -0,0 +1,96 @@ +#include "main.h" +#include "MetaTag.h" +#include "FileTypes.h" +#include "TagAlias.h" + +ASFMetaTag::~ASFMetaTag() +{ + delete info; +} + +const wchar_t *ASFMetaTag::getName() +{ + return L"ASF Metadata"; +} + +GUID ASFMetaTag::getGUID() +{ + return getServiceGuid(); +} + +int ASFMetaTag::getFlags() +{ + return METATAG_FILE_INFO; +} + +int ASFMetaTag::isOurFile(const wchar_t *filename) +{ + const wchar_t *ext = PathFindExtension(filename); + return !lstrcmpiW(ext, L".WMA") + || !lstrcmpiW(ext, L".WMV") + || !lstrcmpiW(ext, L".ASF"); + +} + +int ASFMetaTag::metaTag_open(const wchar_t *filename) +{ + info = new WMInformation(filename); + return METATAG_SUCCESS; // TODO: can we verify this? +} + +void ASFMetaTag::metaTag_close() +{ + delete this; +} + +const wchar_t *ASFMetaTag::enumSupportedTag(int n, int *datatype) +{ + return 0; +} + +int ASFMetaTag::getTagSize(const wchar_t *tag, size_t *sizeBytes) +{ + size_t size; + const wchar_t *tagName = GetAlias(tag); + if (info && info->GetAttributeSize(tagName, size)) + { + *sizeBytes = size; + return METATAG_SUCCESS; + } + else + { + return METATAG_UNKNOWN_TAG; + } +} + +int ASFMetaTag::getMetaData(const wchar_t *tag, __int8 *buf, int buflenBytes, int datatype) +{ + const wchar_t *tagName = GetAlias(tag); + info->GetAttribute(tagName, reinterpret_cast<wchar_t *>(buf), buflenBytes / sizeof(wchar_t)); + return METATAG_SUCCESS; + //return METATAG_FAILED; +} + +int ASFMetaTag::setMetaData(const wchar_t *tag, const __int8 *buf, int buflenBytes, int datatype ) +{ + return METATAG_FAILED; +} + + +#ifdef CBCLASS +#undef CBCLASS +#endif + +#define CBCLASS ASFMetaTag +START_DISPATCH; +CB(SVC_METATAG_GETNAME,getName) + CB(SVC_METATAG_GETGUID,getGUID) + CB(SVC_METATAG_GETFLAGS,getFlags) + CB(SVC_METATAG_ISOURFILE,isOurFile) + CB(SVC_METATAG_OPEN,metaTag_open) + VCB(SVC_METATAG_CLOSE,metaTag_close) + CB(SVC_METATAG_ENUMTAGS,enumSupportedTag) + CB(SVC_METATAG_GETTAGSIZE,getTagSize) + CB(SVC_METATAG_GETMETADATA,getMetaData) + CB(SVC_METATAG_SETMETADATA,setMetaData) +END_DISPATCH; diff --git a/Src/Plugins/Input/in_wmvdrm/MetaTag.h b/Src/Plugins/Input/in_wmvdrm/MetaTag.h new file mode 100644 index 00000000..8c674663 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/MetaTag.h @@ -0,0 +1,42 @@ +#ifndef NULLSOFT_IN_WMVDRM_METATAG_H +#define NULLSOFT_IN_WMVDRM_METATAG_H + +#include "../Agave/Metadata/svc_metatag.h" +#include "WMInformation.h" + +// {8FF721E1-2FF4-4721-A7D5-60FDB32FEB1F} +static const GUID asfMetaTagGUID = +{ 0x8ff721e1, 0x2ff4, 0x4721, { 0xa7, 0xd5, 0x60, 0xfd, 0xb3, 0x2f, 0xeb, 0x1f } }; + +class ASFMetaTag : public svc_metaTag +{ +public: + static const GUID getServiceGuid() { return asfMetaTagGUID; } + static const char *getServiceName() { return "ASF Metadata"; } +public: + ASFMetaTag() : info(0) + { + } + ~ASFMetaTag(); + + /* These methods are to be used by api_metadata */ + const wchar_t *getName(); // i.e. "ID3v2" or something + GUID getGUID(); // this needs to be the same GUID that you use when registering your service factory + int getFlags(); // how this service gets its info + int isOurFile(const wchar_t *filename); + int metaTag_open(const wchar_t *filename); + void metaTag_close(); // self-destructs when this is called (you don't need to call serviceFactory->releaseInterface) + + /* user API starts here */ + const wchar_t *enumSupportedTag(int n, int *datatype); // returns a list of understood tags. might not be complete (see note [1]) + int getTagSize(const wchar_t *tag, size_t *sizeBytes); // always gives you BYTES, not characters (be careful with your strings) + int getMetaData(const wchar_t *tag, __int8 *buf, int buflenBytes, int datatype); // buflen is BYTES, not characters (be careful with your strings) + int setMetaData(const wchar_t *tag, const __int8 *buf, int buflenBytes, int datatype); + +private: + WMInformation *info; + RECVS_DISPATCH; +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/MetaTagFactory.cpp b/Src/Plugins/Input/in_wmvdrm/MetaTagFactory.cpp new file mode 100644 index 00000000..727727b9 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/MetaTagFactory.cpp @@ -0,0 +1,67 @@ +#include "main.h" +#include "MetaTag.h" +#include "api.h" +#include "MetaTagFactory.h" + +FOURCC MetaTagFactory::GetServiceType() +{ + return WaSvc::METATAG; +} + +const char *MetaTagFactory::GetServiceName() +{ + return ASFMetaTag::getServiceName(); +} + +GUID MetaTagFactory::GetGUID() +{ + return ASFMetaTag::getServiceGuid(); +} + +void *MetaTagFactory::GetInterface(int global_lock) +{ + svc_metaTag *ifc= new ASFMetaTag; +// if (global_lock) +// plugin.service->service_lock(this, (void *)ifc); + return ifc; +} + +int MetaTagFactory::SupportNonLockingInterface() +{ + return 1; +} + +int MetaTagFactory::ReleaseInterface(void *ifc) +{ + //plugin.service->service_unlock(ifc); + svc_metaTag *metaTag = static_cast<svc_metaTag *>(ifc); + ASFMetaTag *asfMetaTag = static_cast<ASFMetaTag *>(metaTag); + delete asfMetaTag; + return 1; +} + +const char *MetaTagFactory::GetTestString() +{ + return NULL; +} + +int MetaTagFactory::ServiceNotify(int msg, int param1, int param2) +{ + return 1; +} + +#ifdef CBCLASS +#undef CBCLASS +#endif + +#define CBCLASS MetaTagFactory +START_DISPATCH; +CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType) +CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName) +CB(WASERVICEFACTORY_GETGUID, GetGUID) +CB(WASERVICEFACTORY_GETINTERFACE, GetInterface) +CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface) +CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface) +CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString) +CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify) +END_DISPATCH; diff --git a/Src/Plugins/Input/in_wmvdrm/MetaTagFactory.h b/Src/Plugins/Input/in_wmvdrm/MetaTagFactory.h new file mode 100644 index 00000000..0387401d --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/MetaTagFactory.h @@ -0,0 +1,24 @@ +#ifndef NULLSOFT_IN_WMVDRM_METATAGFACTORY_H +#define NULLSOFT_IN_WMVDRM_METATAGFACTORY_H + +#include <api/service/waservicefactory.h> +#include <api/service/services.h> + +class MetaTagFactory : public waServiceFactory +{ +public: + FOURCC GetServiceType(); + const char *GetServiceName(); + GUID GetGUID(); + void *GetInterface(int global_lock); + int SupportNonLockingInterface(); + int ReleaseInterface(void *ifc); + const char *GetTestString(); + int ServiceNotify(int msg, int param1, int param2); + +protected: + RECVS_DISPATCH; +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/OutputStream.h b/Src/Plugins/Input/in_wmvdrm/OutputStream.h new file mode 100644 index 00000000..bf0fe3db --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/OutputStream.h @@ -0,0 +1,169 @@ +#ifndef NULLSOFT_OUTPUTSTREAMH +#define NULLSOFT_OUTPUTSTREAMH + +#include <wmsdk.h> +#define NULLSOFT_INTERFACE_BEGIN(RIID, OBJ) void **&NULLSOFT_interfaceHolder = OBJ; REFIID NULLSOFT_IID = RIID; +#define NULLSOFT_VALID_INTERFACE(a) if (NULLSOFT_IID == IID_ ## a) { *NULLSOFT_interfaceHolder = static_cast<a *>(this); return S_OK; } +#define NULLSOFT_INTERFACE_END() *NULLSOFT_interfaceHolder = 0; return E_NOINTERFACE; + +class OutputStream : public IWMOutputMediaProps +{ +public: + OutputStream(IWMMediaProps *props) : mediaType(0) + { + DWORD mediaTypeSize; + props->GetMediaType(0, &mediaTypeSize); + if (mediaTypeSize) + { + mediaType = (WM_MEDIA_TYPE *)new unsigned char[mediaTypeSize]; + props->GetMediaType(mediaType, &mediaTypeSize); + } + } + + ~OutputStream() + { + if (mediaType) + { + delete mediaType; + mediaType = 0; + } + } + + GUID &GetSubType() const + { + return mediaType->subtype; + } + + WM_MEDIA_TYPE *mediaType; + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) + { + NULLSOFT_INTERFACE_BEGIN(riid, ppvObject) + NULLSOFT_VALID_INTERFACE(IWMOutputMediaProps); + NULLSOFT_VALID_INTERFACE(IWMMediaProps); + NULLSOFT_INTERFACE_END() + } + + ULONG STDMETHODCALLTYPE AddRef() + { + return 0; + } + ULONG STDMETHODCALLTYPE Release() + { + return 0; + } + HRESULT STDMETHODCALLTYPE GetType(GUID *pguidType) + { + if (!mediaType) return E_FAIL; + *pguidType = mediaType->majortype; + return S_OK; + } + HRESULT STDMETHODCALLTYPE GetMediaType(WM_MEDIA_TYPE *pType, DWORD *pcbType) + { + if (!mediaType) return E_FAIL; + if (!pType) + { + if (!pcbType) return E_INVALIDARG; + *pcbType = sizeof(WM_MEDIA_TYPE); + } + else + { + if (*pcbType < sizeof(WM_MEDIA_TYPE)) ASF_E_BUFFERTOOSMALL; + memcpy(pType, mediaType, sizeof(WM_MEDIA_TYPE)); + } + return S_OK; + } + + HRESULT STDMETHODCALLTYPE SetMediaType(WM_MEDIA_TYPE *pType) + { + return E_NOTIMPL; + } + HRESULT STDMETHODCALLTYPE GetStreamGroupName(WCHAR *pwszName, WORD *pcchName) + { + return E_NOTIMPL; + } + HRESULT STDMETHODCALLTYPE GetConnectionName(WCHAR *pwszName, WORD *pcchName) + { + return E_NOTIMPL; + } +}; +#include <uuids.h> +class VideoOutputStream : public OutputStream +{ +public: + + VideoOutputStream(IWMMediaProps *props) : OutputStream(props) + {} + + WMVIDEOINFOHEADER *VideoInfo() const + { + return (WMVIDEOINFOHEADER *)mediaType->pbFormat; + } +int SourceWidth() const +{ + return VideoInfo()->rcSource.right - VideoInfo()->rcSource.left; + } + int DestinationWidth() const + { + return VideoInfo()->rcTarget.right - VideoInfo()->rcTarget.left; + } + + int DestinationHeight() const + { + return VideoInfo()->rcTarget.bottom - VideoInfo()->rcTarget.top; + } + + bool Flipped() const + { + BITMAPINFOHEADER &info = VideoInfo()->bmiHeader; + if (info.biHeight < 0 || info.biCompression == 0) + return true; + else + return false; + + } + int bmiHeight() + { + return VideoInfo()->bmiHeader.biYPelsPerMeter; + } + int bmiWidth() + { + return VideoInfo()->bmiHeader.biXPelsPerMeter; + } + RGBQUAD *CreatePalette() + { + RGBQUAD *palette = (RGBQUAD *)calloc(1, 1024); + BITMAPINFOHEADER &info = VideoInfo()->bmiHeader; + memcpy(palette, (char *)(&info) + 40, info.biClrUsed * 4); + return palette; + } + int FourCC() const + { + BITMAPINFOHEADER &info = VideoInfo()->bmiHeader; + int fourcc = info.biCompression; + if (fourcc == BI_RGB) + { + switch(info.biBitCount) + { + case 32: + fourcc='23GR'; // RG32 + break; + case 24: + fourcc='42GR'; // RG24 + break; + case 8: + fourcc='8BGR'; // RGB8 + break; + } + } else if (fourcc == BI_BITFIELDS) + fourcc = 0; // TODO: calc a CC that winamp likes + return fourcc; + } + + bool IsVideo() const + { + return !!(mediaType->formattype == WMFORMAT_VideoInfo); + } +}; + +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/PlaylistHandler.cpp b/Src/Plugins/Input/in_wmvdrm/PlaylistHandler.cpp new file mode 100644 index 00000000..7fee0a5e --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/PlaylistHandler.cpp @@ -0,0 +1,167 @@ +#include "main.h" +#include "config.h" +#include "PlaylistHandler.h" +#include "WPLLoader.h" +#include "ASXLoader.h" +#include <shlwapi.h> +#include "resource.h" + +void GetExtension(const wchar_t *filename, wchar_t *ext, size_t extCch) +{ + const wchar_t *s = PathFindExtensionW(filename); + if (!PathIsURLW(filename) + || (!wcsstr(s, L"?") && !wcsstr(s, L"&") && !wcsstr(s, L"=") && *s)) + { + lstrcpynW(ext, s, extCch); + return ; + } + // s is not a terribly good extension, let's try again + { + wchar_t *copy = _wcsdup(filename); + s = L""; + again: + { + wchar_t *p = StrRChrW(copy,NULL, L'?'); + if (p) + { + *p = 0; + s = PathFindExtensionW(copy); + if (!*s) goto again; + } + lstrcpynW(ext, s, extCch); + } + free(copy); + } +} + +/* ---------------------------------- WPL --------------------------------------- */ +const wchar_t *WPLHandler::enumExtensions(size_t n) +{ + switch(n) + { + case 0: + return L"WPL"; + /*case 1: + return L"ZPL";*/ + default: + return 0; + } +} + +int WPLHandler::SupportedFilename(const wchar_t *filename) +{ + wchar_t ext[16] = {0}; + GetExtension(filename, ext, 16); + + if (!lstrcmpiW(ext, L".WPL") || !lstrcmpiW(ext, L".ZPL")) + return SVC_PLAYLISTHANDLER_SUCCESS; + + // TODO: open file and sniff it for file signature + return SVC_PLAYLISTHANDLER_FAILED; +} + +ifc_playlistloader *WPLHandler::CreateLoader(const wchar_t *filename) +{ + return new WPLLoader; +} + +void WPLHandler::ReleaseLoader(ifc_playlistloader *loader) +{ + WPLLoader *pls; + + pls = static_cast<WPLLoader *>(loader); + delete pls; +} + +const wchar_t *WPLHandler::GetName() +{ + static wchar_t wplpl[64]; + // no point re-loading this all of the time since it won't change once we've been loaded + return (!wplpl[0]?WASABI_API_LNGSTRINGW_BUF(IDS_WINDOWS_MEDIA_PLAYLIST,wplpl,64):wplpl); +} + +/* --- ASX --- */ +const wchar_t *ASXHandler::enumExtensions(size_t n) +{ + + switch(n) + { + case 0: + return L"ASX"; + case 1: + if (config_extra_asx_extensions) + return L"WAX"; + else + return 0; + case 2: + if (config_extra_asx_extensions) + return L"WMX"; + else + return 0; + case 3: + if (config_extra_asx_extensions) + return L"WVX"; + else + return 0; + default: + return 0; + } +} + +int ASXHandler::SupportedFilename(const wchar_t *filename) +{ + wchar_t ext[16] = {0}; + GetExtension(filename, ext, 16); + + if (!lstrcmpiW(ext, L".ASX")) + return SVC_PLAYLISTHANDLER_SUCCESS; + if (!lstrcmpiW(ext, L".WAX")) + return SVC_PLAYLISTHANDLER_SUCCESS; + if (!lstrcmpiW(ext, L".WMX")) + return SVC_PLAYLISTHANDLER_SUCCESS; + if (!lstrcmpiW(ext, L".WVX")) + return SVC_PLAYLISTHANDLER_SUCCESS; + + // TODO: open file and sniff it for file signature + return SVC_PLAYLISTHANDLER_FAILED; +} + +ifc_playlistloader *ASXHandler::CreateLoader(const wchar_t *filename) +{ + return new ASXLoader; +} + +void ASXHandler::ReleaseLoader(ifc_playlistloader *loader) +{ + ASXLoader *pls; + + pls = static_cast<ASXLoader *>(loader); + delete pls; +} + +const wchar_t *ASXHandler::GetName() +{ + static wchar_t asxpl[64]; + // no point re-loading this all of the time since it won't change once we've been loaded + return (!asxpl[0]?WASABI_API_LNGSTRINGW_BUF(IDS_ASX_PLAYLIST,asxpl,64):asxpl); +} + +#define CBCLASS WPLHandler +START_DISPATCH; +CB(SVC_PLAYLISTHANDLER_ENUMEXTENSIONS, enumExtensions) +CB(SVC_PLAYLISTHANDLER_SUPPORTFILENAME, SupportedFilename) +CB(SVC_PLAYLISTHANDLER_CREATELOADER, CreateLoader) +VCB(SVC_PLAYLISTHANDLER_RELEASELOADER, ReleaseLoader) +CB(SVC_PLAYLISTHANDLER_GETNAME, GetName) +END_DISPATCH; +#undef CBCLASS + +#define CBCLASS ASXHandler +START_DISPATCH; +CB(SVC_PLAYLISTHANDLER_ENUMEXTENSIONS, enumExtensions) +CB(SVC_PLAYLISTHANDLER_SUPPORTFILENAME, SupportedFilename) +CB(SVC_PLAYLISTHANDLER_CREATELOADER, CreateLoader) +VCB(SVC_PLAYLISTHANDLER_RELEASELOADER, ReleaseLoader) +CB(SVC_PLAYLISTHANDLER_GETNAME, GetName) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/PlaylistHandler.h b/Src/Plugins/Input/in_wmvdrm/PlaylistHandler.h new file mode 100644 index 00000000..98eae155 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/PlaylistHandler.h @@ -0,0 +1,28 @@ +#ifndef NULLSOFT_PLAYLISTS_HANDLER_H +#define NULLSOFT_PLAYLISTS_HANDLER_H + +#include "../playlist/svc_playlisthandler.h" +#include <bfc/platform/types.h> + +#define DECLARE_HANDLER(className) class className ## Handler : public svc_playlisthandler {\ +public:\ + const wchar_t *enumExtensions(size_t n); \ + int SupportedFilename(const wchar_t *filename); \ + ifc_playlistloader *CreateLoader(const wchar_t *filename);\ + void ReleaseLoader(ifc_playlistloader *loader);\ + const wchar_t *GetName(); \ +protected: RECVS_DISPATCH;} + +DECLARE_HANDLER(WPL); +DECLARE_HANDLER(ASX); + +// {DC13A85D-7C61-4462-8CB4-9EBBBD86FA7C} +static const GUID wplHandlerGUID = +{ 0xdc13a85d, 0x7c61, 0x4462, { 0x8c, 0xb4, 0x9e, 0xbb, 0xbd, 0x86, 0xfa, 0x7c } }; +// {8909A743-6F00-43ef-B3F6-365000347DE3} + +static const GUID asxHandlerGUID = +{ 0x8909a743, 0x6f00, 0x43ef, { 0xb3, 0xf6, 0x36, 0x50, 0x0, 0x34, 0x7d, 0xe3 } }; +// {6F62CBB8-7E1F-43eb-B3F6-01C2601029A3} + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/RawReader.cpp b/Src/Plugins/Input/in_wmvdrm/RawReader.cpp new file mode 100644 index 00000000..1189b0ab --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/RawReader.cpp @@ -0,0 +1,173 @@ +#include "main.h" +#include "RawReader.h" +#include "FileTypes.h" +#include <shlwapi.h> + +#define NS_E_FILE_IS_CORRUPTED _HRESULT_TYPEDEF_(0xC00D080DL) + +bool IsMyExtension(const wchar_t *filename) +{ + const wchar_t *ext = PathFindExtension(filename); + if (ext && *ext) + { + ext++; + return fileTypes.GetAVType(ext) != -1; + } + return false; +} + +int RawMediaReaderService::CreateRawMediaReader(const wchar_t *filename, ifc_raw_media_reader **out_reader) +{ + if (IsMyExtension(filename)) + { + IWMSyncReader *reader = 0; + if (!SUCCEEDED(WMCreateSyncReader(NULL, 0, &reader))) + { + return NErr_FailedCreate; + } + + if (FAILED(reader->Open(filename))) + { + reader->Release(); + reader = 0; + return NErr_FileNotFound; // TODO: check HRESULT + } + + RawMediaReader *raw_reader = new RawMediaReader(); + if (!raw_reader) + { + reader->Close(); + reader->Release(); + return NErr_OutOfMemory; + } + + int ret = raw_reader->Initialize(reader); + if (ret != NErr_Success) + { + delete raw_reader; + return ret; + } + + *out_reader = raw_reader; + return NErr_Success; + } + else + { + return NErr_False; + } +} + +#define CBCLASS RawMediaReaderService +START_DISPATCH; +CB(CREATERAWMEDIAREADER, CreateRawMediaReader); +END_DISPATCH; +#undef CBCLASS + +RawMediaReader::RawMediaReader() +{ + reader=0; + stream_num=0; + buffer_used=0; + end_of_file=false; + length=0; + buffer=0; + next_output=0; +} + +RawMediaReader::~RawMediaReader() +{ + if (reader) + { + reader->Close(); + reader->Release(); + } + + if (buffer) + { + buffer->Release(); + } +} + +int RawMediaReader::Initialize(IWMSyncReader *reader) +{ + this->reader=reader; + return NErr_Success; +} + +int RawMediaReader::Read(void *out_buffer, size_t buffer_size, size_t *bytes_read) +{ + /* we don't care about these, but the API does not allows NULL */ + QWORD sample_time = 0, duration = 0; + size_t bytesCopied = 0; + uint8_t *dest = (uint8_t *)out_buffer; + for (;;) + { + if (buffer) + { + BYTE *bufferBytes = 0; + DWORD bufferTotal = 0; + buffer->GetBufferAndLength(&bufferBytes, &bufferTotal); + + if (buffer_used < bufferTotal) + { + size_t toCopy = min(bufferTotal - buffer_used, buffer_size); + memcpy(dest, bufferBytes + buffer_used, toCopy); + buffer_used += toCopy; + buffer_size -= toCopy; + dest += toCopy; + bytesCopied += toCopy; + + if (buffer_used == bufferTotal) + { + buffer_used = 0; + buffer->Release(); + buffer = 0; + } + } + if (buffer_size == 0) + { + *bytes_read = bytesCopied; + return NErr_Success; + } + } + + if (stream_num == 0) + { + DWORD outputs = 0; + HRESULT hr=reader->GetOutputCount(&outputs); + if (FAILED(hr)) + return NErr_Error; + if (next_output >= outputs) + return NErr_EndOfFile; + hr=reader->GetStreamNumberForOutput(next_output, &stream_num); + if (FAILED(hr)) + return NErr_Error; + hr=reader->SetReadStreamSamples(stream_num, TRUE); + if (FAILED(hr)) + return NErr_Error; + next_output++; + } + + DWORD flags = 0; + HRESULT r = reader->GetNextSample(stream_num, &buffer, &sample_time, &duration, &flags, 0, 0); + if (r == NS_E_NO_MORE_SAMPLES || r == NS_E_FILE_IS_CORRUPTED) + { + stream_num=0; + } + } + + return NErr_Error; +} + +size_t RawMediaReader::Release() +{ + delete this; + return 0; +} + +#define CBCLASS RawMediaReader +START_DISPATCH; +CB(RELEASE, Release); +CB(RAW_READ, Read); +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/RawReader.h b/Src/Plugins/Input/in_wmvdrm/RawReader.h new file mode 100644 index 00000000..f7d2e80b --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/RawReader.h @@ -0,0 +1,40 @@ +#pragma once +#include "../Agave/DecodeFile/svc_raw_media_reader.h" +#include "../Agave/DecodeFile/ifc_raw_media_reader.h" +#include <mmreg.h> +#include <wmsdk.h> + +// {9AF5FD89-DC41-4F2A-A156-8D1399FDE57B} +static const GUID wm_raw_reader_guid = +{ 0x9af5fd89, 0xdc41, 0x4f2a, { 0xa1, 0x56, 0x8d, 0x13, 0x99, 0xfd, 0xe5, 0x7b } }; + +class RawMediaReaderService : public svc_raw_media_reader +{ +public: + static const char *getServiceName() { return "Windows Media Audio Raw Reader"; } + static GUID getServiceGuid() { return wm_raw_reader_guid; } + int CreateRawMediaReader(const wchar_t *filename, ifc_raw_media_reader **reader); +protected: + RECVS_DISPATCH; +}; + +class RawMediaReader : public ifc_raw_media_reader +{ +public: + RawMediaReader(); + ~RawMediaReader(); + int Initialize(IWMSyncReader *reader); + int Read(void *buffer, size_t buffer_size, size_t *bytes_read); + size_t Release(); +protected: + RECVS_DISPATCH; +private: + IWMSyncReader *reader; + WORD stream_num; + + INSSBuffer *buffer; + size_t buffer_used; + bool end_of_file; + QWORD length; + DWORD next_output; +}; diff --git a/Src/Plugins/Input/in_wmvdrm/Remaining.h b/Src/Plugins/Input/in_wmvdrm/Remaining.h new file mode 100644 index 00000000..acfbbffe --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/Remaining.h @@ -0,0 +1,68 @@ +#ifndef NULLSOFT_REMAININGH +#define NULLSOFT_REMAININGH +#include <assert.h> +#include <memory.h> +/* this class is used to store leftover samples */ + +class Remaining +{ +public: + Remaining() + : store(0), size(0), used(0) + {} + + void Allocate(unsigned long _size) + { + assert(_size); + used=0; + size=_size; + if (store) + delete [] store; + store = new unsigned char [size]; + } + + /* Saves the incoming data and updates the pointer positions */ + template <class storage_t> + void UpdatingWrite(storage_t *&data, unsigned long &bytes) + { + unsigned long bytesToWrite = min(bytes, SizeRemaining()); + Write(data, bytesToWrite); + assert(bytesToWrite); + data = (storage_t *)((char *)data + bytesToWrite); + bytes -= bytesToWrite; + } + + void Write(void *data, unsigned long bytes) + { + unsigned char *copy = (unsigned char *)store; + copy+=used; + memcpy(copy, data, bytes); + used+=bytes; + } + + unsigned long SizeRemaining() + { + return size-used; + } + + bool Empty() + { + return !used; + } + bool Full() + { + return size == used; + } + void *GetData() + { + return (void *)store; + } + + void Flush() + { + used=0; + } + unsigned char *store; + long size, used; +}; +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/SeekLayer.cpp b/Src/Plugins/Input/in_wmvdrm/SeekLayer.cpp new file mode 100644 index 00000000..2b7eabdd --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/SeekLayer.cpp @@ -0,0 +1,376 @@ +#include "SeekLayer.h" +#include "Main.h" +#include "output/AudioOut.h" +#include "util.h" +#include <assert.h> +using namespace Nullsoft::Utility; + +struct OpenThreadData +{ + IWMReaderCallback *callback; + wchar_t *url; + IWMReader *reader; + + void open() + { + reader->Open(url, callback, 0); + } + + OpenThreadData(IWMReader *_reader, const wchar_t *_url, IWMReaderCallback *_callback) + { + reader = _reader; + reader->AddRef(); + callback = _callback; + callback->AddRef(); + + url = _wcsdup(_url); + } + + ~OpenThreadData() + { + free(url); + reader->Release(); + callback->Release(); + } +}; +DWORD WINAPI OpenThread(void *param) +{ + OpenThreadData *data = (OpenThreadData *)param; + data->open(); + delete data; + return 0; +} + +#define NEW_SEEK +SeekLayer::SeekLayer(IWMReader *_reader, ClockLayer *_clock) + : seekPos(0), reader(_reader), playState(PLAYSTATE_CLOSED), metadata(NULL), clock(_clock), + needPause(false), paused(false), needStop(false), + seekGuard(GUARDNAME("Seek Guard")), + oldState_buffer(PLAYSTATE_NONE) +{ + reader->AddRef(); + reader->QueryInterface(&reader2); +} + +void SeekLayer::DoStop() +{ + if (paused) + reader->Resume(); + + reader->Stop(); + First().Stopping(); + First().Kill(); + if (paused) + { + paused = false; + out->Pause(0); + } + needStop = false; +} + +void SeekLayer::SeekTo(long position) +{ + AutoLock lock (seekGuard LOCKNAME("SeekTo")); + if (paused) + { + reader->Resume(); + } + First().Stopping(); + First().Kill(); + seekPos = position; + clock->SetLastOutputTime(position); + clock->SetStartTimeMilliseconds(position); + out->Flush(position); + QWORD qSeekPos = position; + qSeekPos *= 10000; + reader->Start(qSeekPos, 0, 1.0f, NULL); + if (paused) + { + reader->Pause(); + } +} + +void SeekLayer::Pause() +{ + AutoLock lock (seekGuard LOCKNAME("Pause")); + if (playState == PLAYSTATE_STARTED) + { + paused = true; + reader->Pause(); + out->Pause(1); + } + else + { + needPause = true; + } +} + +int SeekLayer::Open(const wchar_t *filename, IWMReaderCallback *callback) +{ + AutoLock lock (seekGuard LOCKNAME("Open")); + assert(playState == PLAYSTATE_CLOSED); + needStop = false; + playState = PLAYSTATE_OPENING; + DWORD dummyId; + CreateThread(NULL, 0, OpenThread, new OpenThreadData(reader, filename, callback), 0, &dummyId); + return 0; +} + +void SeekLayer::Stop() +{ + AutoLock lock (seekGuard LOCKNAME("Stop")); + needStop = true; + + switch (playState) + { + case PLAYSTATE_BUFFERING: +// needStop=false; + reader2->StopBuffering(); +// reader->Stop(); + break; + + case PLAYSTATE_OPENING: + // wait for it to open (or connect) and then we'll kill it there + break; + + case PLAYSTATE_NONE: + case PLAYSTATE_STOPPED: + if (FAILED(reader->Close())) // reader->Close() is sometimes synchronous, and sometimes not valid here + { + playState = PLAYSTATE_CLOSED; + return ; + } + break; + + case PLAYSTATE_OPENED: + case PLAYSTATE_STARTED: + reader->Stop(); + First().Stopping(); + First().Kill(); + break; + + case PLAYSTATE_CLOSED: + needStop = false; + break; + /* + case PLAYSTATE_BUFFERING: + reader2->StopBuffering(); + reader->Stop(); + break;*/ + + case PLAYSTATE_SEEK: + break; + } + + while (playState != PLAYSTATE_CLOSED) + { + lock.ManualUnlock(); + Sleep(55); + lock.ManualLock(MANUALLOCKNAME("[Manual Lock]Stop")); + } + needStop = false; +} + +void SeekLayer::Unpause() +{ + AutoLock lock (seekGuard LOCKNAME("Unpause")); + if (playState == PLAYSTATE_STARTED) + { + paused = false; + out->Pause(0); + reader->Resume(); + clock->Clock(); + } + else + { + needPause = false; + } +} + +void SeekLayer::Opened() +{ + { + AutoLock lock (seekGuard LOCKNAME("SeekLayer::Opened")); + if (needStop) + { + playState = PLAYSTATE_OPENED; + lock.ManualUnlock(); + reader->Close(); + lock.ManualLock(MANUALLOCKNAME("[Manual Lock]SeekLayer::Opened")); + return ; + } + + playState = PLAYSTATE_OPENED; + } + WMHandler::Opened(); +} + +void SeekLayer::Stopped() +{ + { + AutoLock lock (seekGuard LOCKNAME("Stopped")); + if (needStop) + { + playState = PLAYSTATE_STOPPED; + lock.ManualUnlock(); + reader->Close(); + lock.ManualLock(MANUALLOCKNAME("Stopped")); + } + else + { + playState = PLAYSTATE_STOPPED; + } + + WMHandler::Stopped(); + } +} + +void SeekLayer::Started() +{ + { + AutoLock lock (seekGuard LOCKNAME("Started")); + playState = PLAYSTATE_STARTED; + + if (needStop) + { + reader->Stop(); + First().Stopping(); + First().Kill(); + return ; + } + else if (needPause) + { + Pause(); + needPause = false; + } + } + WMHandler::Started(); +} + +void SeekLayer::Closed() +{ + playState = PLAYSTATE_CLOSED; + paused = false; + needPause = false; + needStop = false; + seekPos = 0; + + WMHandler::Closed(); +} + +void SeekLayer::BufferingStarted() +{ + { + AutoLock lock (seekGuard LOCKNAME("BufferingStarted")); + if (playState == PLAYSTATE_OPENED) + oldState_buffer = PLAYSTATE_NONE; + else + oldState_buffer = playState; + if (playState != PLAYSTATE_STARTED) + playState = PLAYSTATE_BUFFERING; + if (needStop) + reader2->StopBuffering(); + + } + WMHandler::BufferingStarted(); +} + + +void SeekLayer::BufferingStopped() +{ + { + AutoLock lock (seekGuard LOCKNAME("BufferingStopped")); + if (needStop) + reader->Stop(); + playState = oldState_buffer; + } + WMHandler::BufferingStopped(); +} + + +void SeekLayer::EndOfFile() +{ + { + AutoLock lock (seekGuard LOCKNAME("EndOfFile")); + if (needStop) + return ; + } + WMHandler::EndOfFile(); + +} + +void SeekLayer::Connecting() +{ + { + AutoLock lock (seekGuard LOCKNAME("SeekLayer::Connecting")); + if (needStop) + { + playState = PLAYSTATE_NONE; + lock.ManualUnlock(); + reader->Close(); + lock.ManualLock(MANUALLOCKNAME("[Manual Lock]SeekLayer::Connecting")); + return ; + } + playState = PLAYSTATE_NONE; + } + WMHandler::Connecting(); +} + + +void SeekLayer::Locating() +{ + { + AutoLock lock (seekGuard LOCKNAME("SeekLayer::Locating")); + if (needStop) + { + playState = PLAYSTATE_NONE; + lock.ManualUnlock(); + reader->Close(); + lock.ManualLock(MANUALLOCKNAME("[Manual Lock]SeekLayer::Locating")); + return ; + } + playState = PLAYSTATE_NONE; + } + WMHandler::Locating(); +} + + +void SeekLayer::OpenCalled() +{ + { + AutoLock lock (seekGuard LOCKNAME("SeekLayer::OpenCalled")); + if (needStop) + { + playState = PLAYSTATE_NONE; + lock.ManualUnlock(); + reader->Close(); + lock.ManualLock(MANUALLOCKNAME("[Manual Lock]SeekLayer::OpenCalled")); + return ; + } + + playState = PLAYSTATE_NONE; + } + WMHandler::OpenCalled(); +} + +void SeekLayer::OpenFailed() +{ + { + AutoLock lock (seekGuard LOCKNAME("SeekLayer::OpenFailed")); + if (playState == PLAYSTATE_OPENING) + playState = PLAYSTATE_NONE; + } + WMHandler::OpenFailed(); +} + +void SeekLayer::Error() +{ + { + AutoLock lock (seekGuard LOCKNAME("SeekLayer::Error")); + /*if (playState == PLAYSTATE_OPENING) + playState = PLAYSTATE_CLOSED; + else */if (playState != PLAYSTATE_CLOSED) + playState = PLAYSTATE_NONE; + } + WMHandler::Error(); +} diff --git a/Src/Plugins/Input/in_wmvdrm/SeekLayer.h b/Src/Plugins/Input/in_wmvdrm/SeekLayer.h new file mode 100644 index 00000000..7b896845 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/SeekLayer.h @@ -0,0 +1,55 @@ +#ifndef NULLSOFT_SEEKLAYERH +#define NULLSOFT_SEEKLAYERH + +#include "WMHandler.h" +#include "../nu/AutoLock.h" +#include "ClockLayer.h" +class SeekLayer : public WMHandler +{ + enum PlayState + { + PLAYSTATE_NONE, + PLAYSTATE_OPENING, + PLAYSTATE_OPENED, + PLAYSTATE_BUFFERING, + PLAYSTATE_STARTED, + PLAYSTATE_STOPPED, + PLAYSTATE_CLOSED, + PLAYSTATE_SEEK, + + }; +public: + SeekLayer(IWMReader *_reader, ClockLayer *_clock); + void SeekTo(long position); + void Pause(); + void Unpause(); + void Stop(); + int Open(const wchar_t *filename, IWMReaderCallback *callback); + +private: + void BufferingStarted(); + void BufferingStopped(); + void Started(); + void Stopped(); + void Closed(); + void Opened(); + void OpenCalled(); + void Connecting(); + void Locating(); + void EndOfFile(); + void OpenFailed(); + void Error(); + +private: + void DoStop(); + bool needPause, paused, needStop; + long seekPos; + Nullsoft::Utility::LockGuard seekGuard; + IWMReader *reader; + IWMReaderAdvanced2 *reader2; + IWMMetadataEditor *metadata; + ClockLayer *clock; + PlayState playState, oldState_buffer; +}; + +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/StatusHook.cpp b/Src/Plugins/Input/in_wmvdrm/StatusHook.cpp new file mode 100644 index 00000000..45cf618a --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/StatusHook.cpp @@ -0,0 +1,54 @@ +#if 0 +#include <windows.h> +#include "../Winamp/wa_ipc.h" +#include "Main.h" +#include <shlwapi.h> + +static WNDPROC waProc=0; +static bool winampisUnicode=false; + +static LRESULT WINAPI StatusHookProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + + if (msg == WM_WA_IPC && lParam == IPC_HOOK_TITLESW) + { + LRESULT downTheLine = winampisUnicode?CallWindowProcW(waProc, hwnd, msg, wParam, lParam):CallWindowProcA(waProc, hwnd, msg, wParam, lParam); + waHookTitleStructW *hook = (waHookTitleStructW *)wParam; + if (!PathIsURLW(hook->filename) && winamp.GetStatusHook(hook->title, 2048, hook->filename)) + { + return TRUE; + } + else + return downTheLine; + } + + if (waProc) + { + if (winampisUnicode) + return CallWindowProcW(waProc, hwnd, msg, wParam, lParam); + else + return CallWindowProcA(waProc, hwnd, msg, wParam, lParam); + } + else + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +void Hook(HWND winamp) +{ + if (winamp) + { + winampisUnicode = !!IsWindowUnicode(winamp); + if (winampisUnicode) + waProc = (WNDPROC)SetWindowLongPtrW(winamp, GWLP_WNDPROC, (LONG_PTR)StatusHookProc); + else + waProc = (WNDPROC)SetWindowLongPtrA(winamp, GWLP_WNDPROC, (LONG_PTR)StatusHookProc); + } +} + +void Unhook(HWND winamp) +{ +// if (winamp && GetWindowLongA(winamp,GWL_WNDPROC) == (LONG)StatusHookProc) + //SetWindowLong(winamp, GWL_WNDPROC, (LONG)waProc); + //waProc=0; +} +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/StatusHook.h b/Src/Plugins/Input/in_wmvdrm/StatusHook.h new file mode 100644 index 00000000..400c134a --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/StatusHook.h @@ -0,0 +1,7 @@ +#ifndef NULLSOFT_STATUSHOOKH +#define NULLSOFT_STATUSHOOKH + +#include <windows.h> +void Hook(HWND winamp); +void Unhook(HWND winamp); +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/TODO.txt b/Src/Plugins/Input/in_wmvdrm/TODO.txt new file mode 100644 index 00000000..3c5fa698 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/TODO.txt @@ -0,0 +1,8 @@ +advanced preferences:
+defaults button
+
+why do some videos not end properly?
+
+potential race condition over WMDRM::fn
+
+make the messagebox for 'do you want to acquire this shit' have its own message loop so we can killswitch it
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/TagAlias.cpp b/Src/Plugins/Input/in_wmvdrm/TagAlias.cpp new file mode 100644 index 00000000..5f5e8e59 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/TagAlias.cpp @@ -0,0 +1,64 @@ +#include "main.h" +#include "TagAlias.h" + +struct TagAliases +{ + const wchar_t *winampTag; + const wchar_t *wmaTag; +}; + +const TagAliases aliases[] = + { + {L"comment", g_wszWMDescription}, + {L"album", g_wszWMAlbumTitle}, + {L"genre", g_wszWMGenre}, + {L"year", g_wszWMYear}, + {L"track", g_wszWMTrackNumber}, + {L"artist", g_wszWMAuthor}, + {L"title", g_wszWMTitle}, + {L"copyright", g_wszWMCopyright}, + {L"composer", g_wszWMComposer}, + {L"albumartist", g_wszWMAlbumArtist}, + {L"bpm", g_wszWMBeatsPerMinute}, + {L"publisher", g_wszWMPublisher}, + {L"ISRC", g_wszWMISRC}, + {L"lyricist", g_wszWMWriter}, + {L"conductor", g_wszWMConductor}, + {L"tool", g_wszWMToolName}, + {L"encoder", g_wszWMEncodingSettings}, + {L"key", g_wszWMInitialKey}, + {L"mood", g_wszWMMood}, + {L"disc", g_wszWMPartOfSet}, + {L"height", g_wszWMVideoHeight}, + {L"width", g_wszWMVideoWidth}, + {L"category", g_wszWMCategory}, + {L"producer", g_wszWMProducer}, + {L"director", g_wszWMDirector}, + {L"fps", g_wszWMVideoFrameRate}, + {0, 0}, + }; + + +const wchar_t *GetAlias(const wchar_t *tag) +{ + int i = 0; + while (aliases[i].winampTag) + { + if (!lstrcmpiW(tag, aliases[i].winampTag)) + return aliases[i].wmaTag; + i++; + } + return tag; +} + +const wchar_t *GetAlias_rev(const wchar_t *tag) +{ + int i = 0; + while (aliases[i].wmaTag) + { + if (!lstrcmpiW(tag, aliases[i].wmaTag)) + return aliases[i].winampTag; + i++; + } + return tag; +} diff --git a/Src/Plugins/Input/in_wmvdrm/TagAlias.h b/Src/Plugins/Input/in_wmvdrm/TagAlias.h new file mode 100644 index 00000000..56a25be1 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/TagAlias.h @@ -0,0 +1,7 @@ +#ifndef NULLSOFT_IN_WMVDRM_TAGALIAS_H +#define NULLSOFT_IN_WMVDRM_TAGALIAS_H + +#include <wchar.h> +const wchar_t *GetAlias(const wchar_t *tag); +const wchar_t *GetAlias_rev(const wchar_t *tag); +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/VideoDataConverter.cpp b/Src/Plugins/Input/in_wmvdrm/VideoDataConverter.cpp new file mode 100644 index 00000000..35130024 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/VideoDataConverter.cpp @@ -0,0 +1,41 @@ +#include "main.h" +#include "VideoDataConverter.h" +#include <cassert> + +class YV12Converter : public VideoDataConverter +{ +public: + YV12Converter(int w, int h) + : width(w), height(h) + { + yv12.y.rowBytes = width; + yv12.v.rowBytes = width / 2; + yv12.u.rowBytes = width / 2; + vOffset = width*height; + uOffset = width*height/4; + } + + void *Convert(void *videoData) + { + yv12.y.baseAddr = (unsigned char*)videoData; + yv12.v.baseAddr = yv12.y.baseAddr + vOffset; + yv12.u.baseAddr = yv12.v.baseAddr + uOffset; + + return (void *)&yv12; + } + + int width, height; + int vOffset, uOffset; + YV12_PLANES yv12; +}; + +VideoDataConverter *MakeConverter(VideoOutputStream *stream) +{ + switch (stream->FourCC()) + { + case '21VY': + return new YV12Converter(stream->DestinationWidth(), stream->DestinationHeight()); + default: + return new VideoDataConverter; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/VideoDataConverter.h b/Src/Plugins/Input/in_wmvdrm/VideoDataConverter.h new file mode 100644 index 00000000..d1530cb0 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/VideoDataConverter.h @@ -0,0 +1,18 @@ +#ifndef NULLSOFT_VIDEODATACONVERTERH +#define NULLSOFT_VIDEODATACONVERTERH + +#include "OutputStream.h" +class VideoDataConverter +{ +public: + virtual void *Convert(void *videoData) + { + return videoData; + } +}; + + + +VideoDataConverter *MakeConverter(VideoOutputStream *stream); + +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/VideoLayer.cpp b/Src/Plugins/Input/in_wmvdrm/VideoLayer.cpp new file mode 100644 index 00000000..cee4297b --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/VideoLayer.cpp @@ -0,0 +1,300 @@ +#include "Main.h" +#include "VideoLayer.h" +#include <initguid.h> +#include <wmsdkidl.h> +#include <cassert> +#include "util.h" +#include "resource.h" +#include <strsafe.h> + +#include "config.h" +#define VIDEO_ACCEPTABLE_DROP (config_video_drop_threshold*10000) + +VideoLayer::VideoLayer(IWMReader *_reader) + : reader(_reader), videoOutputNum(-1), + reader2(0), offset(0), nextRest(0), + converter(NULL), videoOpened(false), + video_output_opened(false), + killSwitch(0), aspect(0), + earlyDelivery(0), fourcc(0), + drmProtected(false), + videoStream(0), flip(false), + videoWidth(0), videoHeight(0) +{ + reader->AddRef(); + if (FAILED(reader->QueryInterface(&reader2))) + reader2 = 0; + + if (FAILED(reader->QueryInterface(&header))) + header = 0; + + killSwitch = CreateEvent(NULL, TRUE, FALSE, NULL); +} + +VideoLayer::~VideoLayer() +{ + videoThread.Kill(); + if (reader2) + reader2->Release(); + if (header) + header->Release(); + reader->Release(); + CloseHandle(killSwitch); +} + +bool AcceptableFormat(GUID &subtype) +{ + if (subtype == WMMEDIASUBTYPE_YV12 + || subtype == WMMEDIASUBTYPE_YUY2 + || subtype == WMMEDIASUBTYPE_UYVY + //|| subtype == WMMEDIASUBTYPE_YVYU + || subtype == WMMEDIASUBTYPE_RGB24 + || subtype == WMMEDIASUBTYPE_RGB32 + || subtype == WMMEDIASUBTYPE_I420 + || subtype == WMMEDIASUBTYPE_IYUV + || subtype == WMMEDIASUBTYPE_RGB1 + || subtype == WMMEDIASUBTYPE_RGB4 + || subtype == WMMEDIASUBTYPE_RGB8 + || subtype == WMMEDIASUBTYPE_RGB565 + || subtype == WMMEDIASUBTYPE_RGB555 + ) + return true; + else + return false; +} + +bool VideoLayer::AttemptOpenVideo(VideoOutputStream *attempt) +{ + videoWidth = attempt->DestinationWidth(); + videoHeight = attempt->DestinationHeight(); + flip = attempt->Flipped(); + fourcc = attempt->FourCC(); + if (!fourcc) + return false; + + aspect = 1.0; + return true; + +} + +bool VideoLayer::OpenVideo() +{ + videoOutputNum = -1; + DWORD numOutputs, numFormats; + IWMOutputMediaProps *formatProperties; + VideoOutputStream *stream; + GUID mediaType; + + reader->GetOutputCount(&numOutputs); + + for (DWORD output = 0;output < numOutputs;output++) + { + // test the default format first, and if that fails, iterate through the rest + const int defaultFormat = -1; + HRESULT hr; + if (FAILED(hr = reader->GetOutputFormatCount(output, &numFormats))) + continue; + + for (int format = 0/*defaultFormat*/;format != numFormats;format++) + { + if (format == defaultFormat) + reader->GetOutputProps(output, &formatProperties); + else + reader->GetOutputFormat(output, format, &formatProperties); + + formatProperties->GetType(&mediaType); + + if (mediaType == WMMEDIATYPE_Video) + { + stream = new VideoOutputStream(formatProperties); + + if (stream->IsVideo() // if it's video + && AcceptableFormat(stream->GetSubType()) // and a video format we like + && AttemptOpenVideo(stream)) // and winamp was able to open it + { + videoOpened = true; + int fourcc = stream->FourCC(); + if (fourcc == '8BGR') + { + RGBQUAD *palette = stream->CreatePalette(); + winamp.SetVideoPalette(palette); + + // TODO: don't leak the palette + } + char *cc = (char *) & fourcc; + char status[512] = {0}; + StringCchPrintfA(status, 512, WASABI_API_LNGSTRING(IDS_WINDOWS_MEDIA_XXX), + stream->DestinationWidth(), stream->DestinationHeight(), cc[0], cc[1], cc[2], cc[3]); + winamp.SetVideoStatusText(status); + converter = MakeConverter(stream); + videoOutputNum = output; + videoStream = stream; + reader->SetOutputProps(output, formatProperties); + formatProperties->Release(); + return true; + } + + delete stream; + stream = 0; + + } + formatProperties->Release(); + } + } + return false; +} + +void VideoLayer::SampleReceived(QWORD &timeStamp, QWORD &duration, unsigned long &outputNum, unsigned long &flags, INSSBuffer *&sample) +{ + if (outputNum == videoOutputNum) + { + if (WaitForSingleObject(killSwitch, 0) == WAIT_OBJECT_0) + return ; + + INSSBuffer3 *buff3; + if (SUCCEEDED(sample->QueryInterface(&buff3))) + { + short aspectHex = 0; + DWORD size = 2; + buff3->GetProperty(WM_SampleExtensionGUID_PixelAspectRatio, &aspectHex, &size); + if (aspectHex) + { + double newAspect = (double)((aspectHex & 0xFF00) >> 8) / (double)(aspectHex & 0xFF) ; + + if (newAspect != aspect) + { + aspect = newAspect; + videoThread.OpenVideo(drmProtected, videoWidth, videoHeight, flip, aspect, fourcc); + video_output_opened=true; + } + } + buff3->Release(); + } + + if (!video_output_opened) + { + videoThread.OpenVideo(drmProtected, videoWidth, videoHeight, flip, aspect, fourcc); + video_output_opened=true; + } + + __int64 timeDiff; + First().TimeToSync(timeStamp, timeDiff); + + if (timeDiff < -VIDEO_ACCEPTABLE_DROP) // late + { + timeDiff = -timeDiff; + if (config_video_catchup) First().VideoCatchup(timeDiff); + if (config_video_framedropoffset) this->VideoFrameDrop((DWORD)(timeDiff / 10000)); + if (config_video_notifylate) reader2->NotifyLateDelivery(timeDiff); + + // drop the frame + } + else // early + { + while (!videoThread.AddBuffer(sample, timeStamp, flags, drmProtected)) + { + if (WaitForSingleObject(killSwitch, VIDEO_ACCEPTABLE_JITTER_MS) == WAIT_OBJECT_0) + return ; + } + } + } + else + WMHandler::SampleReceived(timeStamp, duration, outputNum, flags, sample); +} + +void VideoLayer::Opened() +{ + WORD stream = 0; + WMT_ATTR_DATATYPE type = WMT_TYPE_BOOL; + BOOL value; + WORD valueLen = sizeof(value); + header->GetAttributeByName(&stream, g_wszWMProtected, &type, (BYTE *)&value, &valueLen); + drmProtected = !!value; + + ResetEvent(killSwitch); + if (OpenVideo()) + { + ResetEvent(killSwitch); + HRESULT hr; + + BOOL dedicatedThread = config_video_dedicated_thread ? TRUE : FALSE; + hr = reader2->SetOutputSetting(videoOutputNum, g_wszDedicatedDeliveryThread, WMT_TYPE_BOOL, (BYTE *) & dedicatedThread, sizeof(dedicatedThread)); + assert(hr == S_OK); + + earlyDelivery = config_video_early ? config_video_early_pad : 0; + hr = reader2->SetOutputSetting(videoOutputNum, g_wszEarlyDataDelivery, WMT_TYPE_DWORD, (BYTE *) & earlyDelivery , sizeof(earlyDelivery)); + assert(hr == S_OK); + + BOOL outOfOrder = config_video_outoforder ? TRUE : FALSE; + hr = reader2->SetOutputSetting(videoOutputNum, g_wszDeliverOnReceive, WMT_TYPE_BOOL, (BYTE *) & outOfOrder, sizeof(outOfOrder)); + assert(hr == S_OK); + + BOOL justInTime = config_lowmemory ? TRUE : FALSE; + hr = reader2->SetOutputSetting(videoOutputNum, g_wszJustInTimeDecode, WMT_TYPE_BOOL, (BYTE *) & justInTime, sizeof(justInTime)); + assert(hr == S_OK); + } + else + { + videoOpened = false; + } + + WMHandler::Opened(); +} + +void VideoLayer::VideoFrameDrop(DWORD lateness) +{ + //earlyDelivery+=lateness; + lateness += earlyDelivery; + HRESULT hr = reader2->SetOutputSetting(videoOutputNum, g_wszEarlyDataDelivery, WMT_TYPE_DWORD, (BYTE *) & lateness, sizeof(lateness)); + assert(hr == S_OK); +} + +void VideoLayer::Closed() +{ + if (video_output_opened) + { + videoThread.CloseVideo(drmProtected); + video_output_opened = false; + } + videoOpened = false; + delete videoStream; + videoStream=0; + WMHandler::Closed(); +} + + +bool VideoLayer::IsOpen() +{ + return videoOpened; +} + +void VideoLayer::HasVideo(bool &video) +{ + video=videoOpened; +} + +void VideoLayer::Kill() +{ + SetEvent(killSwitch); + if (videoOpened) + videoThread.SignalStop();//SignalStop(); + + WMHandler::Kill(); + if (videoOpened) + videoThread.WaitForStop(); +} + +void VideoLayer::Started() +{ + ResetEvent(killSwitch); + if (videoOpened) + videoThread.Start(converter, &First()); + WMHandler::Started(); +} + +void VideoLayer::Stopped() +{ + if (videoOpened) + videoThread.Stop(); + WMHandler::Stopped(); +} diff --git a/Src/Plugins/Input/in_wmvdrm/VideoLayer.h b/Src/Plugins/Input/in_wmvdrm/VideoLayer.h new file mode 100644 index 00000000..1f95ccdc --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/VideoLayer.h @@ -0,0 +1,62 @@ +#ifndef NULLSOFT_VIDEOLAYERH +#define NULLSOFT_VIDEOLAYERH + +#include "WMHandler.h" +#include "OutputStream.h" +#include <wmsdk.h> +#include "VideoDataConverter.h" +#include "Config.h" +#include "VideoThread.h" + +#define VIDEO_ACCEPTABLE_JITTER (config_video_jitter*10000) +#define VIDEO_ACCEPTABLE_JITTER_MS (config_video_jitter) + +class VideoLayer : public WMHandler +{ +public: + VideoLayer(IWMReader *_reader); + ~VideoLayer(); + bool IsOpen(); + void Kill(); +private: + // WMHandler + void VideoFrameDrop(DWORD lateness); + void SampleReceived(QWORD &timeStamp, QWORD &duration, unsigned long &outputNum, unsigned long &flags, INSSBuffer *&sample); + void Opened(); + void AudioBufferMilliseconds(long ms); + void Closed(); + + void Started(); + void Stopped(); + void HasVideo(bool &video); + // utility methods + bool AttemptOpenVideo(VideoOutputStream *attempt); + bool OpenVideo(); + + // other people's data + IWMReader *reader; + + // our data + IWMReaderAdvanced2 *reader2; + IWMHeaderInfo *header; + int videoOutputNum; + DWORD offset; + long nextRest; + VideoDataConverter *converter; + VideoOutputStream *videoStream; + + bool videoOpened; + QWORD catchupTime; + double aspect; + int fourcc; + bool flip; + int videoWidth, videoHeight; + HANDLE killSwitch; + DWORD earlyDelivery; + bool drmProtected; + bool video_output_opened; + VideoThread videoThread; + +}; + +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/VideoOutputChildDDraw.cpp b/Src/Plugins/Input/in_wmvdrm/VideoOutputChildDDraw.cpp new file mode 100644 index 00000000..511fb80a --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/VideoOutputChildDDraw.cpp @@ -0,0 +1,80 @@ +#include "main.h" +#include "VideoOutputChildDDraw.h" +#include <multimon.h> +#include <ddraw.h> + +class MonitorFinder +{ +public: + MonitorFinder(HMONITOR hm) : m_monitor_to_find(hm), m_found_devguid(0) + {} + + HMONITOR m_monitor_to_find; + int m_found_devguid; + GUID m_devguid; +}; + +static BOOL WINAPI DDEnumCallbackEx(GUID FAR *lpGUID, LPSTR lpDriverDescription, LPSTR lpDriverName, LPVOID lpContext, HMONITOR hm) +{ + MonitorFinder *ovo = (MonitorFinder *)lpContext; + if (ovo->m_found_devguid) return 1; + if (hm == ovo->m_monitor_to_find) + { + ovo->m_devguid = *lpGUID; + ovo->m_found_devguid = 1; + } + return 1; +} + +void VideoOutputChildDDraw::update_monitor_coords() +{ + //find the correct monitor if multiple monitor support is present + m_mon_x = 0; + m_mon_y = 0; + + HINSTANCE h = LoadLibrary(L"user32.dll"); + if (h) + { + HMONITOR (WINAPI *Mfp)(POINT pt, DWORD dwFlags) = (HMONITOR (WINAPI *)(POINT, DWORD)) GetProcAddress(h, "MonitorFromPoint"); + HMONITOR (WINAPI *Mfr)(LPCRECT lpcr, DWORD dwFlags) = (HMONITOR (WINAPI *)(LPCRECT, DWORD)) GetProcAddress(h, "MonitorFromRect"); + HMONITOR (WINAPI *Mfw)(HWND wnd, DWORD dwFlags) = (HMONITOR (WINAPI *)(HWND, DWORD)) GetProcAddress(h, "MonitorFromWindow"); + BOOL (WINAPI *Gmi)(HMONITOR mon, LPMONITORINFO lpmi) = (BOOL (WINAPI *)(HMONITOR, LPMONITORINFO)) GetProcAddress(h, "GetMonitorInfoA"); + if (Mfp && Mfr && Mfw && Gmi) + { + HMONITOR hm = Mfw(parent, 0); + if (hm) + { + HINSTANCE hdd = LoadLibrary(L"ddraw.dll"); + if (hdd) + { + typedef BOOL (FAR PASCAL * LPDDENUMCALLBACKEXA)(GUID FAR *, LPSTR, LPSTR, LPVOID, HMONITOR); + typedef HRESULT (WINAPI * LPDIRECTDRAWENUMERATEEX)( LPDDENUMCALLBACKEXA lpCallback, LPVOID lpContext, DWORD dwFlags); + LPDIRECTDRAWENUMERATEEX lpDDEnumEx; + lpDDEnumEx = (LPDIRECTDRAWENUMERATEEX) GetProcAddress(hdd, "DirectDrawEnumerateExW"); + if (lpDDEnumEx) + { + MonitorFinder finder(hm); + + lpDDEnumEx(&DDEnumCallbackEx, &finder, DDENUM_ATTACHEDSECONDARYDEVICES | DDENUM_NONDISPLAYDEVICES); + foundGUID=!!finder.m_found_devguid; + if (foundGUID) + { + m_devguid=finder.m_devguid; + MONITORINFOEXW mi; + memset(&mi, 0, sizeof(mi)); + mi.cbSize = sizeof(mi); + if (Gmi(hm, &mi)) + { + m_mon_x = mi.rcMonitor.left; + m_mon_y = mi.rcMonitor.top; + } + } + } + FreeLibrary(hdd); + } + } + } + FreeLibrary(h); + } +} + diff --git a/Src/Plugins/Input/in_wmvdrm/VideoOutputChildDDraw.h b/Src/Plugins/Input/in_wmvdrm/VideoOutputChildDDraw.h new file mode 100644 index 00000000..e5f72d50 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/VideoOutputChildDDraw.h @@ -0,0 +1,17 @@ +#ifndef NULLSOFT_VIDEOOUTPUTCHILDDDRAWH +#define NULLSOFT_VIDEOOUTPUTCHILDDDRAWH +#include "../Winamp/VideoOutputChild.h" + +class VideoOutputChildDDraw : public VideoRenderer +{ +public: + VideoOutputChildDDraw() : m_mon_x(0), m_mon_y(0), foundGUID(false), parent(0), adjuster(0) {} + VideoAspectAdjuster *adjuster; + void update_monitor_coords(); + int m_mon_x, m_mon_y; + bool foundGUID; + GUID m_devguid; + HWND parent; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/VideoThread.cpp b/Src/Plugins/Input/in_wmvdrm/VideoThread.cpp new file mode 100644 index 00000000..fd6f1f09 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/VideoThread.cpp @@ -0,0 +1,166 @@ +#include "Main.h" +#include "VideoThread.h" +#include "VideoLayer.h" +#include "config.h" +#include <windows.h> + +DWORD WINAPI VidThread_stub(void *ptr) +{ + ((VideoThread *)ptr)->VidThread(); + return 0; +} + +VideoThread::VideoThread() : converter(0), clock(0) +{ + drm = false; + + DWORD id; + thread = CreateThread(NULL, 256*1024, VidThread_stub, (void *)this, NULL, &id); + SetThreadPriority(thread, AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); +} + +void VideoThread::Start(VideoDataConverter *_converter, WMHandler *_clock) +{ + clock = _clock; + if (converter != _converter) + { + if (converter) + delete converter; + converter = _converter; + } + ResetEvent(stopped); + QueueUserAPC(MediaThread_StartAPC, thread, reinterpret_cast<ULONG_PTR>(this)); +} + +void VideoThread::VidThread() +{ + while (true) + { + switch (WaitForSingleObjectEx(killEvent, wait, TRUE)) + { + case WAIT_OBJECT_0: + //StopAPC(); + return; + + case WAIT_TIMEOUT: + { + if (buffers.empty()) + { + SetEvent(bufferFreed); + continue; + } + + MediaBuffer *buffer = buffers.front(); + + __int64 diff; + clock->TimeToSync(buffer->timestamp, diff); + if (diff < VIDEO_ACCEPTABLE_JITTER) + { + void *data; + DWORD size; + + buffer->buffer->GetBufferAndLength((BYTE **)&data, &size); + if (buffer->drmProtected) + winamp.EncryptedDrawFrame(converter->Convert(data)); + else + winamp.DrawFrame(converter->Convert(data)); + + try { + buffer->buffer->Release(); + delete buffer; + } catch (...) {} + + //buffers.pop_front(); + if (buffers.size()) + { + buffers.erase(buffers.begin()); + } + } + if (buffers.size() < config_video_cache_frames) + SetEvent(bufferFreed); + } + continue; + + default: + continue; + } + } +} + +void VideoThread::AddAPC(MediaBuffer *buffer) +{ + if (buffers.empty()) + { + __int64 diff; + clock->TimeToSync(buffer->timestamp, diff); + if (diff < VIDEO_ACCEPTABLE_JITTER) + { + void *data; + DWORD size; + buffer->buffer->GetBufferAndLength((BYTE **)&data, &size); + if (buffer->drmProtected) + winamp.EncryptedDrawFrame(converter->Convert(data)); + else + winamp.DrawFrame(converter->Convert(data)); + + buffer->buffer->Release(); + if (buffers.size() >= config_video_cache_frames) + ResetEvent(bufferFreed); + return; + } + } + + OrderedInsert(buffer); + + if (buffers.size() >= config_video_cache_frames) + ResetEvent(bufferFreed); +} + +struct VideoOpenParameters +{ + int width; + int height; + int color_format; + double aspect; + int flip; + bool drm; +}; + +VOID CALLBACK VideoThread::VideoThread_VideoOpenAPC(ULONG_PTR params) +{ + VideoOpenParameters *p = (VideoOpenParameters *)params; + if (p->drm) + { + winamp.OpenEncryptedVideo(p->width, p->height, !!p->flip, p->aspect, p->color_format); + } + else + { + winamp.OpenVideo(p->width, p->height, !!p->flip, p->aspect, p->color_format); + } +} + +void VideoThread::OpenVideo(bool drm, int width, int height, bool flip, double aspect, int fourcc) +{ + VideoOpenParameters *p = new VideoOpenParameters; + p->width = width; + p->height = height; + p->color_format = fourcc; + p->aspect = aspect; + p->flip = flip; + p->drm = drm; + this->drm = drm; + QueueUserAPC(VideoThread_VideoOpenAPC, thread, reinterpret_cast<ULONG_PTR>(p)); +} + +VOID CALLBACK VideoThread::VideoThread_VideoCloseAPC(ULONG_PTR params) +{ + if (params) + winamp.CloseEncryptedVideo(); + else + winamp.CloseVideo(); +} + +void VideoThread::CloseVideo(bool drm) +{ + QueueUserAPC(VideoThread_VideoCloseAPC, thread, static_cast<ULONG_PTR>(drm)); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/VideoThread.h b/Src/Plugins/Input/in_wmvdrm/VideoThread.h new file mode 100644 index 00000000..2127710f --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/VideoThread.h @@ -0,0 +1,37 @@ +#ifndef NULLSOFTVIDEOTHREADH +#define NULLSOFTVIDEOTHREADH + +#include "VideoDataConverter.h" +#include "WMHandler.h" +#include <deque> +#include <wmsdk.h> +#include "MediaThread.h" + +class VideoThread : public MediaThread +{ +public: + VideoThread(); + void Start(VideoDataConverter *_converter, WMHandler *_clock); + + /* AddBuffers put a video buffer in the queue + it returns true if it was added + it returns false if it was NOT added. it is up to YOU (the caller) to sleep for a while and call again + */ + void VidThread(); + + void OpenVideo(bool drm, int width, int height, bool flip, double aspect, int fourcc); + void CloseVideo(bool drm); +private: + static VOID CALLBACK VideoThread_VideoOpenAPC(ULONG_PTR params); + static VOID CALLBACK VideoThread_VideoCloseAPC(ULONG_PTR params); + + + void AddAPC(MediaBuffer *); + VideoDataConverter *converter; + WMHandler *clock; + bool drm; + +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/WMCallback.cpp b/Src/Plugins/Input/in_wmvdrm/WMCallback.cpp new file mode 100644 index 00000000..cc92ce70 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/WMCallback.cpp @@ -0,0 +1,294 @@ +#include "Main.h" +#include "WMCallback.h" +#include <algorithm> +#include "WMHandler.h" +#include "util.h" +#include <cassert> + +#define CAST_TO(x) if (riid== IID_##x) { *ppvObject=static_cast<x *>(this); AddRef(); return S_OK; } +#define CAST_TO_VIA(x,y) if (riid== IID_##x) { *ppvObject=static_cast<y *>(this); AddRef(); return S_OK; } +HRESULT WMCallback::QueryInterface(REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject ) +{ + CAST_TO(IWMReaderCallback); + CAST_TO_VIA(IUnknown, IWMReaderCallback); + CAST_TO(IWMReaderCallbackAdvanced); +#ifdef _DEBUG + CAST_TO(IWMCredentialCallback); +#endif + + *ppvObject = NULL; + return E_NOINTERFACE; +} + +ULONG WMCallback::AddRef() +{ + return InterlockedIncrement(&refCount); +} + +ULONG WMCallback::Release() +{ + if (InterlockedDecrement(&refCount) == 0) + { + delete this; + return 0; + } + + return refCount; +} + + +HRESULT WMCallback::OnStatus(WMT_STATUS Status, HRESULT hr, WMT_ATTR_DATATYPE dwType, + BYTE __RPC_FAR *pValue, void __RPC_FAR *pvContext) +{ + if (!handler) + return S_OK; + + switch (Status) + { + WMTCASE(WMT_INIT_PLAYLIST_BURN) + handler->InitPlaylistBurn(); + break; + + WMTCASE(WMT_NO_RIGHTS) + handler->NoRights((wchar_t *)pValue); + break; + + WMTCASE(WMT_NO_RIGHTS_EX) + handler->NoRightsEx((WM_GET_LICENSE_DATA *&)pValue); + break; + + WMTCASE(WMT_NEEDS_INDIVIDUALIZATION) + handler->Individualize(); + break; + + WMTCASE(WMT_END_OF_STREAMING) + break; + + WMTCASE(WMT_LICENSEURL_SIGNATURE_STATE) + handler->SignatureState((WMT_DRMLA_TRUST *&)pValue); + break; + + WMTCASE(WMT_ACQUIRE_LICENSE) + WMT_SHOW_HR_CODE(hr) + switch (hr) + { + case NS_S_DRM_LICENSE_ACQUIRED: + handler->LicenseAcquired(); + break; + case NS_S_DRM_MONITOR_CANCELLED: + handler->MonitorCancelled(); + break; + case NS_S_DRM_ACQUIRE_CANCELLED: + handler->SilentCancelled(); + break; + default: + handler->AcquireLicense((WM_GET_LICENSE_DATA *&)pValue); + } + break; + + WMTCASE(WMT_INDIVIDUALIZE) + handler->IndividualizeStatus((WM_INDIVIDUALIZE_STATUS *)pValue); + break; + + //the file has been opened + WMTCASE(WMT_OPENED) + if (SUCCEEDED(hr)) + handler->Opened(); + else + { + switch (hr) + { + WMTCASE(NS_E_DRM_APPCERT_REVOKED) + WMTCASE(NS_E_DRM_LICENSE_APP_NOTALLOWED) + handler->DRMExpired(); + WMTCASE(NS_E_LICENSE_REQUIRED) + handler->LicenseRequired(); + break; + WMTCASE(NS_E_DRM_NEEDS_INDIVIDUALIZATION) + handler->NeedsIndividualization(); + break; + WMTCASE(E_ACCESSDENIED) + handler->AccessDenied(); + break; + default: + WMT_SHOW_HR_CODE(hr); + handler->Error(); + return S_OK; + } + handler->OpenCalled(); + } + + break; + + // Playback of the opened file has begun. + WMTCASE( WMT_STARTED) + if (SUCCEEDED(hr)) + handler->Started(); + else + { + switch (hr) + { + WMTCASE(E_ABORT) + //handler->OpenFailed(); + break; + WMTCASE(NS_E_DRM_REOPEN_CONTENT) + handler->Error(); + break; + default: + WMT_SHOW_HR_CODE(hr); + handler->Error(); + break; + } + } + break; + + WMTCASE( WMT_NEW_METADATA) + if (SUCCEEDED(hr)) + handler->NewMetadata(); + break; + + // The previously playing reader has stopped. + WMTCASE( WMT_STOPPED) + if (SUCCEEDED(hr)) + handler->Stopped(); + else + { + WMT_SHOW_HR_CODE(hr); + handler->Error(); + } + break; + + // The previously playing reader has stopped. + WMTCASE( WMT_CLOSED) + if (SUCCEEDED(hr)) + handler->Closed(); + else + { + WMT_SHOW_HR_CODE(hr); + handler->Error(); + } + break; + + WMTCASE(WMT_ERROR) + WMT_SHOW_HR_CODE(hr); + //handler->Error(); + + break; + + WMTCASE( WMT_BUFFERING_START) + if (SUCCEEDED(hr)) + handler->BufferingStarted(); + break; + + WMTCASE( WMT_BUFFERING_STOP) + if (SUCCEEDED(hr)) + handler->BufferingStopped(); + break; + + WMTCASE( WMT_EOF) + WMT_SHOW_HR_CODE(hr); + handler->EndOfFile(); + break; + + WMTCASE( WMT_LOCATING) + handler->Locating(); + break; + + WMTCASE( WMT_CONNECTING) + handler->Connecting(); + break; + + WMTCASE( WMT_PREROLL_READY) + break; + + WMTCASE( WMT_PREROLL_COMPLETE) + break; + WMTCASE(WMT_NEW_SOURCEFLAGS) + break; + WMTCASE(WMT_MISSING_CODEC) + + WMT_SHOW_HR_CODE(hr); +#ifdef DEBUG + std::cerr << dwType << std::endl; + std::wcerr << GuidString(*(GUID *)pValue) << std::endl; +#endif + break; + default: + #ifdef DEBUG + std::cerr << "unknown message = " << Status << std::endl; + #endif + break; + }; + return S_OK; +} + +HRESULT WMCallback::OnStreamSelection(WORD wStreamCount, WORD *pStreamNumbers, WMT_STREAM_SELECTION *pSelections, void *pvContext) +{ + #ifdef DEBUG + std::cerr << "OnStreamSelection" << std::endl; + #endif + return E_NOTIMPL; +} + +HRESULT WMCallback::OnOutputPropsChanged(DWORD dwOutputNum, WM_MEDIA_TYPE *pMediaType, void *pvContext) +{ + #ifdef DEBUG + std::cerr << "OnOutputPropsChanged" << std::endl; + #endif + + return E_NOTIMPL; +} + +HRESULT WMCallback::AllocateForStream(WORD wStreamNum, DWORD cbBuffer, INSSBuffer **ppBuffer, void *pvContext) +{ + return E_NOTIMPL; +} + +HRESULT WMCallback::AllocateForOutput(DWORD dwOutputNum, DWORD cbBuffer, INSSBuffer **ppBuffer, void *pvContext) +{ + return E_NOTIMPL; +} + +HRESULT WMCallback::OnStreamSample(WORD wStreamNum, QWORD cnsSampleTime, QWORD cnsSampleDuration, DWORD dwFlags, INSSBuffer *pSample, void *pvContext) +{ + return E_NOTIMPL; +} + +// 0x5A, 0xA5, 0x00, 0x03, 0x74, 0x00, 0x01, 0x01, 0x77, +//------------------------------------------------------------------------------ +// Name: CWAPlugin::OnSample() +// Desc: IWMReaderCallback method to process samples. +//------------------------------------------------------------------------------ +HRESULT WMCallback::OnSample(DWORD dwOutputNum, QWORD cnsSampleTime, + QWORD cnsSampleDuration, DWORD dwFlags, + INSSBuffer __RPC_FAR *pSample, void __RPC_FAR *pvContext) +{ + if (!handler) + return S_OK; + handler->SampleReceived(cnsSampleTime, cnsSampleDuration, dwOutputNum, dwFlags, pSample); + return S_OK; +} + +HRESULT WMCallback::OnTime(QWORD cnsCurrentTime, void *pvContext) +{ + if (!handler) + return S_OK; + handler->TimeReached(cnsCurrentTime); + return S_OK; +} + + +HRESULT WMCallback::AcquireCredentials(WCHAR* pwszRealm, WCHAR* pwszSite, + WCHAR* pwszUser, DWORD cchUser, + WCHAR* pwszPassword, DWORD cchPassword, + HRESULT hrStatus, DWORD* pdwFlags) +{ +#ifdef _DEBUG + std::cout << "WMCallback::AcquireCredentials" << std::endl; + std::wcout << pwszRealm << std::endl; + std::wcout << pwszSite << std::endl; + std::wcout << HRErrorCode(hrStatus) << std::endl; + return S_OK; +#endif + return E_NOTIMPL; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/WMCallback.h b/Src/Plugins/Input/in_wmvdrm/WMCallback.h new file mode 100644 index 00000000..4ae68c42 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/WMCallback.h @@ -0,0 +1,52 @@ +#ifndef NULLSOFT_WMCALLBACK +#define NULLSOFT_WMCALLBACK + +#include <wmsdk.h> +#include <deque> +#include "WMHandler.h" + + +class WMCallback : public IWMReaderCallback, public IWMReaderCallbackAdvanced, public IWMCredentialCallback +{ + +public: + WMCallback() : refCount(0), handler(0) + { + AddRef(); + } + + ~WMCallback() + { + } + + WMHandler &operator >> (WMHandler *_handler) + { + handler = _handler; + return *handler; + } + + +private: + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject); + ULONG STDMETHODCALLTYPE AddRef(); + ULONG STDMETHODCALLTYPE Release(); + HRESULT STDMETHODCALLTYPE OnStatus(WMT_STATUS Status, HRESULT hr, WMT_ATTR_DATATYPE dwType, BYTE __RPC_FAR *pValue, void __RPC_FAR *pvContext); + virtual HRESULT STDMETHODCALLTYPE OnSample(DWORD dwOutputNum, QWORD cnsSampleTime, QWORD cnsSampleDuration, DWORD dwFlags, INSSBuffer __RPC_FAR *pSample, void __RPC_FAR *pvContext); + + /* IWMReaderCallbackAdvanced */ + HRESULT STDMETHODCALLTYPE OnStreamSample(WORD wStreamNum, QWORD cnsSampleTime, QWORD cnsSampleDuration, DWORD dwFlags, INSSBuffer *pSample, void *pvContext); + HRESULT STDMETHODCALLTYPE OnTime(QWORD cnsCurrentTime, void *pvContext); + HRESULT STDMETHODCALLTYPE OnStreamSelection(WORD wStreamCount, WORD *pStreamNumbers, WMT_STREAM_SELECTION *pSelections, void *pvContext); + HRESULT STDMETHODCALLTYPE OnOutputPropsChanged(DWORD dwOutputNum, WM_MEDIA_TYPE *pMediaType, void *pvContext); + HRESULT STDMETHODCALLTYPE AllocateForStream(WORD wStreamNum, DWORD cbBuffer, INSSBuffer **ppBuffer, void *pvContext); + HRESULT STDMETHODCALLTYPE AllocateForOutput(DWORD dwOutputNum, DWORD cbBuffer, INSSBuffer **ppBuffer, void *pvContext); + + /* IWMCredentialCallback */ + HRESULT STDMETHODCALLTYPE AcquireCredentials(WCHAR* pwszRealm, WCHAR* pwszSite, WCHAR* pwszUser, DWORD cchUser, WCHAR* pwszPassword, DWORD cchPassword, HRESULT hrStatus, DWORD* pdwFlags); + + long refCount; + WMHandler *handler; + +}; + +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/WMDRMModule.cpp b/Src/Plugins/Input/in_wmvdrm/WMDRMModule.cpp new file mode 100644 index 00000000..92a6db3c --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/WMDRMModule.cpp @@ -0,0 +1,649 @@ +#include "Main.h" +#include "WMDRMModule.h" +#include "AutoWide.h" +#include "WMInformation.h" +#include "AutoChar.h" +#include "FileInfoDialog.h" +#include "ConfigDialog.h" +#include "resource.h" +#include "StatusHook.h" +#include "../nu/Config.h" +#include "util.h" +#include "WMPlaylist.h" +#include "api.h" +#include "output/OutPlugin.h" +#include "output/AudioOut.h" +#include <strsafe.h> + +extern Nullsoft::Utility::Config wmConfig; + +#define SAMPLES_PER_BLOCK 576 +AudioOut *out = 0; + +unsigned long endTime = 0; +unsigned long startTime = 0; + +void InitOutputs(HWND hMainWindow, HMODULE hDllInstance) +{} + +void WMDRM::AssignOutput() +{ + out = &pluginOut; +} + +WMDRM::WMDRM() + : paused(false), + clock(0), audio(0), video(0), wait(0), info(0), buffer(0), seek(0), reader(NULL), gain(0), + killswitch(0), opened(false), + drmProtected(false), + volume(-666), pan(0), + reader2(0), network(0), playing(false), + startAtMilliseconds(0), + dspBuffer(0), + vizBuffer(0), + reader1(0), + flushed(false) +{ + killswitch = CreateEvent(NULL, TRUE, TRUE, NULL); +} + +WMDRM::~WMDRM() +{ + DeleteObject(killswitch); + delete [] dspBuffer; + delete [] vizBuffer; +} + +static int winampVersion=0; + +#define WINAMP_VERSION_MINOR1(winampVersion) ((winampVersion & 0x000000F0)>>4) // returns, i.e. 0x01 for 5.12 and 0x02 for 5.2... +#define WINAMP_VERSION_MINOR2(winampVersion) ((winampVersion & 0x0000000F)) // returns, i.e. 0x02 for 5.12 and 0x00 for 5.2... +static void MakeVersionString(QWORD *ver) +{ + if (!winampVersion) + winampVersion = (int)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GETVERSION); + + LARGE_INTEGER temp; + temp.HighPart = MAKELONG(WINAMP_VERSION_MINOR1(winampVersion), WINAMP_VERSION_MAJOR(winampVersion)); + temp.LowPart = MAKELONG(WASABI_API_APP->main_getBuildNumber(), WINAMP_VERSION_MINOR2(winampVersion)); + + *ver = temp.QuadPart; +} + +static void MakeUserAgentString(wchar_t str[256]) +{ + if (!winampVersion) + winampVersion = (int)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GETVERSION); + + StringCchPrintfW(str, 256, L"WinampASF/%01x.%02x", + WINAMP_VERSION_MAJOR(winampVersion), + WINAMP_VERSION_MINOR(winampVersion)); +} + + +void WMDRM::InitWM() +{ + static int triedInit = 0; + if (!triedInit) + { + if (FAILED(WMCreateReader(0, WMT_RIGHT_PLAYBACK, &reader)) || !reader) + { + reader = 0; + plugin.FileExtensions = "\0"; + return ; + } + if (FAILED(reader->QueryInterface(&reader1))) + reader1 = 0; + + if (FAILED(reader->QueryInterface(&reader2))) + reader2 = 0; + + if (FAILED(reader->QueryInterface(&network))) + network = 0; + + if (reader1) + { + QWORD verStr; + wchar_t userAgent[256] = {0}; + MakeVersionString(&verStr); + MakeUserAgentString(userAgent); + WM_READER_CLIENTINFO info; + ZeroMemory(&info, sizeof(WM_READER_CLIENTINFO)); + info.cbSize = sizeof(WM_READER_CLIENTINFO); + info.wszHostExe = L"winamp.exe"; + info.qwHostVersion = verStr; + info.wszPlayerUserAgent = userAgent; + info.wszBrowserWebPage = L"http://www.winamp.com"; + + reader1->SetClientInfo(&info); + } + + clock = new ClockLayer(reader); + audio = new AudioLayer(reader); + video = new VideoLayer(reader); + wait = new WaitLayer(reader); + info = new WMInformation(reader); + buffer = new BufferLayer(reader); + seek = new SeekLayer(reader, clock); + gain = new GainLayer(audio, info); + + callback >> seek >> buffer >> clock >> video >> audio >> wait >> gain >> this; + + triedInit = 1; + } +} + +void WMDRM::Init() +{ + winampVersion = (int)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GETVERSION); + InitOutputs(plugin.hMainWindow, plugin.hDllInstance); + + //Hook(plugin.hMainWindow); +} + +void WMDRM::Config(HWND hwndParent) +{ + WASABI_API_DIALOGBOXW(IDD_CONFIG, hwndParent, PreferencesDialogProc); +} + +void WMDRM::Quit() +{ + activePlaylist.Clear(); + delete setFileInfo; setFileInfo = 0; + delete clock; clock = 0; + delete audio; audio = 0; + delete video; video = 0; + delete wait; wait = 0; + delete info; info = 0; + delete buffer; buffer = 0; + delete seek; seek = 0; + + if (network) network->Release(); network = 0; + if (reader2) reader2->Release(); reader2 = 0; + if (reader1) reader1->Release(); reader1 = 0; + if (reader) reader->Release(); reader = 0; + +// Unhook(plugin.hMainWindow); +} + +static void BuildTitle(WMInformation *info, const wchar_t *file, wchar_t *str, size_t len) +{ + if (info) + { + wchar_t artist[256] = L"", title[256] = L""; + info->GetAttribute(g_wszWMAuthor, artist, 256); + info->GetAttribute(g_wszWMTitle, title, 256); + + if (!artist[0] && !title[0]) + { + if (file && *file) + { + StringCchCopy(str, len, file); + } + } + else if (artist[0] && title[0]) + StringCchPrintf(str, len, L"%s - %s", artist, title); + else if (artist[0]) + StringCchCopy(str, len, artist); + else if (title[0]) + StringCchCopy(str, len, title); + } + else if (file) + StringCchCopy(str, len, file); + +} + +void WMDRM::GetFileInfo(const wchar_t *file, wchar_t *title, int *length_in_ms) +{ + InitWM(); + + if (length_in_ms) *length_in_ms = -1000; + if (file && file[0]) + { + bool isURL = !!PathIsURL(file); + if (config_http_metadata || !isURL) + { + WMInformation getFileInfo(file, true); + + if (title) + { + BuildTitle(&getFileInfo, file, title, GETFILEINFO_TITLE_LENGTH); + } + if (length_in_ms) *length_in_ms = getFileInfo.GetLengthMilliseconds(); + } + else + { + if (title) + StringCchCopy(title, GETFILEINFO_TITLE_LENGTH, file); + } + winamp.GetStatus(title, 256, file); + } + else if (activePlaylist.GetFileName()) + { + //isURL = !!wcsstr(activePlaylist.GetFileName(), L"://"); + if (wait && !wait->IsOpen()) // if it's not open, fill in with some default data... WMDRM::Opened() will refresh the title ... + { + StringCchCopy(title, GETFILEINFO_TITLE_LENGTH, activePlaylist.GetOriginalFileName()); + if (length_in_ms) *length_in_ms = -1000; + //if (isURL) + winamp.GetStatus(title, 256, activePlaylist.GetOriginalFileName()); + return ; + } + + if (title) + { + BuildTitle(info, activePlaylist.GetOriginalFileName(), title, GETFILEINFO_TITLE_LENGTH); + } + if (info) + if (length_in_ms) *length_in_ms = info->GetLengthMilliseconds(); + else + if (length_in_ms) *length_in_ms = -1000; + + //if (isURL) + winamp.GetStatus(title, 256, activePlaylist.GetOriginalFileName()); + } +} + +int WMDRM::InfoBox(const wchar_t *fn, HWND hwndParent) +{ + /* CUT> we're now using the unified file info dialogue + FileInfoDialog dialog(WASABI_API_LNG_HINST, hwndParent, fn); + if (dialog.WasEdited()) + return 0; + else + return 1; + */ + return 0; +} + +int WMDRM::IsOurFile(const in_char *fn) +{ + // if (!reader) + // return 0; + if (wcsstr(fn, L".asx")) // TODO: need something WAY better than this + return 1; + return fileTypes.IsSupportedURL(fn); +} + +int WMDRM::Play(const wchar_t * fn) +{ + InitWM(); + + if (!reader) + return -1; + if (network) + network->SetBufferingTime((QWORD)config_buffer_time*10000LL); + + ResetEvent(killswitch); + wait->ResetForOpen(); + + activePlaylist.Clear(); + activePlaylist.playlistFilename = _wcsdup(fn); + if (playlistManager->Load(fn, &activePlaylist) != PLAYLISTMANAGER_SUCCESS) + activePlaylist.OnFile(fn, 0, -1, 0); // add it manually (TODO: need a better way to do this) + + winamp.GetVideoOutput(); + playing = true; + startTime = winamp.GetStart() * 1000; + if (!startAtMilliseconds) + startAtMilliseconds = startTime; + + endTime = winamp.GetEnd() * 1000; + clock->SetLastOutputTime(startAtMilliseconds); // normally 0, but set when metadata editor needs to stop / restart a file + AssignOutput(); + clock->SetStartTimeMilliseconds(startAtMilliseconds); // normally 0, but set when metadata editor needs to stop / restart a file + startAtMilliseconds = 0; + return seek->Open(activePlaylist.GetFileName(), &callback); +} + +void WMDRM::ReOpen() +{ + if (opened) + seek->Stop(); + seek->Open(activePlaylist.GetFileName(), &callback); +} + +void WMDRM::Pause() +{ + paused = true; + if (seek) + seek->Pause(); +} + +void WMDRM::UnPause() +{ + paused = false; + if (seek) + seek->Unpause(); +} + +int WMDRM::IsPaused() +{ + return (int)paused; +} + +void WMDRM::Stop() +{ + if (!playing) + return ; + + playing = false; + SetEvent(killswitch); + if (paused) + UnPause(); + if (seek) + seek->Stop(); +} + + +void WMDRM::Closed() +{ + opened = false; + WMHandler::Closed(); +} + +int WMDRM::GetLength() +{ + if (info) + return info->GetLengthMilliseconds(); + else + return 0; +} + +int WMDRM::GetOutputTime() +{ + if (!opened) + { + //if (winamp.bufferCount) + return winamp.bufferCount; + //return 0; + } + return clock->GetOutputTime(); +} + +void WMDRM::SetOutputTime(int time_in_ms) +{ + if (startTime || endTime) + { + unsigned int seektime = time_in_ms; + if (endTime && seektime > endTime) + seektime = endTime; + if (startTime && seektime < startTime) + seektime = startTime; + seek->SeekTo(seektime); + return ; + } + seek->SeekTo(time_in_ms); +} + +void WMDRM::SetVolume(int volume) +{ + this->volume = volume; + if (out) + out->SetVolume(volume); +} + +void WMDRM::SetPan(int pan) +{ + this->pan = pan; + if (out) + out->SetPan(pan); +} + +void WMDRM::EQSet(int on, char data[10], int preamp) +{} + +void WMDRM::BuildBuffers() +{ + remaining.Allocate(audio->AudioSamplesToBytes(SAMPLES_PER_BLOCK)); + + // TODO: check against old size + delete [] dspBuffer; + delete [] vizBuffer; + dspBuffer = new unsigned char[audio->AudioSamplesToBytes(SAMPLES_PER_BLOCK) * 2]; + vizBuffer = new unsigned char[audio->AudioSamplesToBytes(SAMPLES_PER_BLOCK) * 2]; +} + +void WMDRM::AudioDataReceived(void *_data, unsigned long sizeBytes, DWORD timestamp) +{ + // TODO: apply replaygain first + // but if we change bitdepth, we'll have to be careful about calling audio->AudioSamplesToBytes() and similiar functions + + unsigned char *data = (unsigned char *)_data; + if (!remaining.Empty()) + { + if (WaitForSingleObject(killswitch, 0) == WAIT_OBJECT_0) + { + remaining.Flush(); + return ; + } + remaining.UpdatingWrite(data, sizeBytes); + if (remaining.Full()) + { + OutputAudioSamples(remaining.GetData(), SAMPLES_PER_BLOCK, timestamp); + remaining.Flush(); + } + } + + long samplesLeft = audio->AudioBytesToSamples(sizeBytes); + + while (samplesLeft) + { + if (WaitForSingleObject(killswitch, 0) == WAIT_OBJECT_0) + { + remaining.Flush(); + return ; + } + + if (samplesLeft >= SAMPLES_PER_BLOCK) + { + OutputAudioSamples(data, SAMPLES_PER_BLOCK, timestamp); + data += audio->AudioSamplesToBytes(SAMPLES_PER_BLOCK); + samplesLeft -= SAMPLES_PER_BLOCK; + } + else + { + unsigned long bytesLeft = audio->AudioSamplesToBytes(samplesLeft); + remaining.UpdatingWrite(data, bytesLeft); + samplesLeft = audio->AudioBytesToSamples(bytesLeft); // should always be 0 + assert(samplesLeft == 0); + } + } +} + + +void WMDRM::QuantizedViz(void *data, long sizeBytes, DWORD timestamp) +{ + if (drmProtected) + { + assert(sizeBytes == audio->Channels() *(audio->BitSize() / 8) * SAMPLES_PER_BLOCK); + memset(vizBuffer, 0, sizeBytes); + ptrdiff_t stride = audio->BitSize() / 8; + size_t position = stride - 1; + unsigned char *origData = (unsigned char *)data; + for (int i = 0;i < SAMPLES_PER_BLOCK*audio->Channels();i++) // winamp hardcodes this ... + { + vizBuffer[position] = (origData[position] & 0xFC); // 6 bits of precision, enough for viz. + position += stride; + } + + plugin.SAAddPCMData((char *) vizBuffer, audio->Channels(), audio->ValidBits(), timestamp); + plugin.VSAAddPCMData((char *) vizBuffer, audio->Channels(), audio->ValidBits(), timestamp); + } + else + { + plugin.SAAddPCMData((char *) data, audio->Channels(), audio->ValidBits(), timestamp); + plugin.VSAAddPCMData((char *) data, audio->Channels(), audio->ValidBits(), timestamp); + } +} + +long WMDRM::GetPosition() +{ + if (!opened) + return 0; + return out->GetWrittenTime(); +} + +void WMDRM::OutputAudioSamples(void *data, long samples) +{ + DWORD timestamp = out->GetWrittenTime(); + OutputAudioSamples(data, samples, timestamp); +} + +void WMDRM::OutputAudioSamples(void *data, long samples, DWORD ×tamp) +{ + clock->SetLastOutputTime(timestamp); + timestamp += audio->AudioSamplesToMilliseconds(samples); + + + //clock->SetLastOutputTime(winamp.GetWrittenTime()); + + //in theory, we could check mod->dsp_isactive(), but that opens up a potential race condition ... + memcpy(dspBuffer, data, audio->AudioSamplesToBytes(samples)); + int dspSize = samples; + if (!drmProtected) + dspSize = plugin.dsp_dosamples((short *)dspBuffer, samples, audio->BitSize(), audio->Channels(), audio->SampleRate()); + dspSize = audio->AudioSamplesToBytes(dspSize); + if (samples == SAMPLES_PER_BLOCK) + QuantizedViz(dspBuffer, dspSize, timestamp); + while (out->CanWrite() <= dspSize) + { + if (WaitForSingleObject(killswitch, 10) == WAIT_OBJECT_0) + { + remaining.Flush(); + return ; + } + } + out->Write((char *)dspBuffer, dspSize); + /*long bytesAvail = */out->CanWrite(); +} + +void WMDRM::Opened() +{ + //winamp.ResetBuffering(); + drmProtected = info->IsAttribute(g_wszWMProtected); + ResetEvent(killswitch); + if (!audio->IsOpen()) + { + if (video->IsOpen()) + { + winamp.SetStatus(WASABI_API_LNGSTRINGW(IDS_REALTIME)); + clock->GoRealTime(); + plugin.is_seekable = info->IsSeekable() ? 1 : 0; + winamp.SetAudioInfo(info->GetBitrate() / 1000, 0, 0); + //out->SetVolume( -666); // set default volume + } + else + { + // no audio or video!! + seek->Stop(); + First().OpenFailed(); + return ; + } + } + else + { + BuildBuffers(); + plugin.is_seekable = info->IsSeekable() ? 1 : 0; + winamp.SetAudioInfo(info->GetBitrate() / 1000, audio->SampleRate() / 1000, audio->Channels()); + out->SetVolume(volume); // set default volume + out->SetPan(pan); + winamp.SetVizInfo(audio->SampleRate(), audio->Channels()); + } + opened = true; + winamp.ClearStatus(); + reader->Start(clock->GetStartTime(), 0, 1.0f, NULL); + WMHandler::Opened(); + +} + +void WMDRM::Started() +{ + ResetEvent(killswitch); + winamp.ResetBuffering(); + winamp.ClearStatus(); + WMHandler::Started(); +} +void WMDRM::EndOfFile() +{ + if (audio->IsOpen()) + { + if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0) + { + if (remaining.used) + { + OutputAudioSamples(remaining.GetData(), audio->AudioBytesToSamples(remaining.used)); + remaining.Flush(); + } + out->Write(0, 0); + while (out->IsPlaying()) + { + if (WaitForSingleObject(killswitch, 10) == WAIT_OBJECT_0) + { + break; + } + } + } + } + + // TODO: if we have a playlist, start the next track instead of telling winamp to go to the next track + if (playing) + winamp.EndOfFile(); + WMHandler::EndOfFile(); +} + +void WMDRM::NewMetadata() +{ + winamp.RefreshTitle(); + WMHandler::NewMetadata(); +} + +void WMDRM::Error() +{ + // wait 200 ms for the killswitch (aka hitting stop) + // this allows the user to hit "stop" and not have to continue cycling through songs if there are a whole bunch of bad/missing WMAs in the playlist + if (playing && WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0) + winamp.EndOfFile(); +} + +void WMDRM::OpenFailed() +{ + if (playing && WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0) // wait 200 ms for the killswitch (see above notes) + winamp.EndOfFile(); +} + +void WMDRM::Stopped() +{ + remaining.Flush(); + WMHandler::Stopped(); +} + +void WMDRM::Kill() +{ + SetEvent(killswitch); + WMHandler::Kill(); +} + +void WMDRM::NewSourceFlags() +{ + plugin.is_seekable = info->IsSeekable() ? 1 : 0; +} + +void WMDRM::Connecting() +{ + winamp.SetStatus(WASABI_API_LNGSTRINGW(IDS_CONNECTING)); + WMHandler::Connecting(); +} + +void WMDRM::Locating() +{ + winamp.SetStatus(WASABI_API_LNGSTRINGW(IDS_LOCATING)); + WMHandler::Locating(); +} + +void WMDRM::AccessDenied() +{ + winamp.SetStatus(WASABI_API_LNGSTRINGW(IDS_ACCESS_DENIED)); + if (playing && WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0) // wait 200 ms for the killswitch (see above notes) + winamp.PressStop(); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/WMDRMModule.h b/Src/Plugins/Input/in_wmvdrm/WMDRMModule.h new file mode 100644 index 00000000..52812930 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/WMDRMModule.h @@ -0,0 +1,100 @@ +#ifndef NULLSOFT_WMDRMMODULEH +#define NULLSOFT_WMDRMMODULEH + +#include "Remaining.h" +#include <wmsdk.h> +// layers +#include "AudioLayer.h" +#include "VideoLayer.h" +#include "ClockLayer.h" +#include "WaitLayer.h" +#include "BufferLayer.h" +#include "SeekLayer.h" +#include "GainLayer.h" + +#include "WMHandler.h" +#include "WMCallback.h" +#include "WMInformation.h" + +class WMDRM : public WMHandler +{ +public: + WMDRM(); + ~WMDRM(); + void Config(HWND hwndParent); + void Init(); + void Quit(); + void GetFileInfo(const wchar_t *file, wchar_t *title, int *length_in_ms); + int InfoBox(const wchar_t *file, HWND hwndParent); + int IsOurFile(const wchar_t *fn); + int Play(const wchar_t *fn); + void Pause(); + void UnPause(); + int IsPaused(); + void Stop(); + int GetLength(); + int GetOutputTime(); + void SetOutputTime(int time_in_ms); + void SetVolume(int volume); + void SetPan(int pan); + int GetVolume() { return volume; } + int GetPan() { return pan; } + void EQSet(int on, char data[10], int preamp); + void BuildBuffers(); + void OutputAudioSamples(void *data, long samples, DWORD&); + void OutputAudioSamples(void *data, long samples); + void QuantizedViz(void *data, long sizeBytes, DWORD); + long GetPosition(); + void EndOfFile(); + bool OpenVideo(int fourcc, int width, int height, bool flipped); + void ReOpen(); + void NewSourceFlags(); +// const char *GetFile() { return fn.c_str();} + bool playing; + int startAtMilliseconds; + void InitWM(); +protected: + //WMHandler + void AudioDataReceived(void *_data, unsigned long sizeBytes, DWORD timestamp); + void Opened(); + void NewMetadata(); + void Closed(); + void Started(); + void Error(); + void OpenFailed(); + void Stopped(); + void Kill(); + + BufferLayer *buffer; + ClockLayer *clock; +#ifndef NO_DRM + DRMLayer *drm; +#endif + AudioLayer *audio; + VideoLayer *video; + WaitLayer *wait; + SeekLayer *seek; + GainLayer *gain; + WMCallback callback; + IWMReader *reader; + IWMReaderAdvanced *reader1; + IWMReaderAdvanced2 *reader2; + IWMReaderNetworkConfig *network; + WMInformation *info; + Remaining remaining; + unsigned char *dspBuffer, *vizBuffer; + int volume, pan; + bool flushed, paused; + + HANDLE killswitch; + + bool opened; + bool drmProtected; + void Connecting(); + void Locating(); + void AssignOutput(); + void AccessDenied(); + + +}; +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/WMHandler.cpp b/Src/Plugins/Input/in_wmvdrm/WMHandler.cpp new file mode 100644 index 00000000..090e1cd7 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/WMHandler.cpp @@ -0,0 +1,181 @@ +#include "main.h" +#include <assert.h> + +void WMHandler::OpenFailed() +{ + if (next) + next->OpenFailed(); +} + +void WMHandler::ReOpen() +{ + if (next) + next->ReOpen(); +} + +void WMHandler::Started() +{ + if (next) + next->Started(); +} + +void WMHandler::Stopped() +{ + if (next) + next->Stopped(); +} + +void WMHandler::PreRollComplete() +{ + if (next) + next->PreRollComplete(); +} + +void WMHandler::EndOfFile() +{ + if (next) + next->EndOfFile(); +} + +void WMHandler::Closed() +{ + if (next) + next->Closed(); +} + +void WMHandler::BufferingStarted() +{ + if (next) + next->BufferingStarted(); +} + +void WMHandler::BufferingStopped() +{ + if (next) + next->BufferingStopped(); +} + +void WMHandler::NewMetadata() +{ + if (next) + next->NewMetadata(); +} + +void WMHandler::Individualize() +{ + if (next) + next->Individualize(); +} + +void WMHandler::SignatureState(WMT_DRMLA_TRUST *&state) +{ + if (next) + next->SignatureState(state); +} + +void WMHandler::NoRightsEx(WM_GET_LICENSE_DATA *&licenseData) +{ + if (next) + next->NoRightsEx(licenseData); +} + +void WMHandler::AcquireLicense(WM_GET_LICENSE_DATA *&licenseData) +{ + if (next) + next->AcquireLicense(licenseData); +} + +void WMHandler::AllocateOutput(long outputNum, long bufferSize, INSSBuffer *&buffer) +{ + if (next) + next->AllocateOutput(outputNum, bufferSize, buffer); +} + +void WMHandler::VideoCatchup(QWORD time) +{ + if (next) + next->VideoCatchup(time); +} + +void WMHandler::TimeToSync(QWORD timeStamp,__int64 &diff) +{ + if (next) + next->TimeToSync(timeStamp, diff); +} + +void WMHandler::Error() +{ + if (next) + next->Error(); + } + +void WMHandler::LicenseRequired() +{ + if (next) + next->LicenseRequired(); +} + +void WMHandler::NoRights(wchar_t *licenseData) +{ + if (next) + next->NoRights(licenseData); +} + + +WMHandler::WMHandler() : next(0), prev(0) + {} + WMHandler::~WMHandler() + { + if (next) + next->prev = prev; + + if (prev) + prev->next = next; + + } + + WMHandler &WMHandler::operator << (WMHandler &chain) + { + assert(chain.next == 0); + assert(prev == 0); + + prev = &chain; + chain.next = this; + + return chain; + } + + WMHandler &WMHandler::operator >> (WMHandler &chain) + { + if (chain.prev) + { + operator >>(chain.prev); + return chain; + } + + assert (next == 0); + assert (chain.prev == 0); + + next = &chain; + chain.prev = this; + + return chain; + } + WMHandler&WMHandler::operator << (WMHandler *chain) + { + return operator <<(*chain); + } + + WMHandler &WMHandler::operator >> (WMHandler *chain) + { + return operator >>(*chain); + } + + WMHandler &WMHandler::First() + { + if (prev) + return prev->First(); + else + return *this; + } + diff --git a/Src/Plugins/Input/in_wmvdrm/WMHandler.h b/Src/Plugins/Input/in_wmvdrm/WMHandler.h new file mode 100644 index 00000000..b2ac60f9 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/WMHandler.h @@ -0,0 +1,113 @@ +#ifndef NULLSOFT_WMHANDLERH +#define NULLSOFT_WMHANDLERH +#include <wmsdk.h> + +#define NEXT(x) { if (next) next->x; } + +enum DRM_INDIVIDUALIZATION_STATUS { + INDI_UNDEFINED = 0x0000, + INDI_BEGIN = 0x0001, + INDI_SUCCEED = 0x0002, + INDI_FAIL = 0x0004, + INDI_CANCEL = 0x0008, + INDI_DOWNLOAD = 0x0010, + INDI_INSTALL = 0x0020 +}; + +enum DRM_HTTP_STATUS { + HTTP_NOTINITIATED = 0, + HTTP_CONNECTING = 1, + HTTP_REQUESTING = 2, + HTTP_RECEIVING = 3, + HTTP_COMPLETED = 4 +}; + +typedef struct _WMGetLicenseData { + DWORD dwSize; + HRESULT hr; + WCHAR* wszURL; + WCHAR* wszLocalFilename; + BYTE* pbPostData; + DWORD dwPostDataSize; +} WM_GET_LICENSE_DATA; + + +typedef struct _WMIndividualizeStatus { + HRESULT hr; + DRM_INDIVIDUALIZATION_STATUS enIndiStatus; + LPSTR pszIndiRespUrl; + DWORD dwHTTPRequest; + DRM_HTTP_STATUS enHTTPStatus; + DWORD dwHTTPReadProgress; + DWORD dwHTTPReadTotal; +} WM_INDIVIDUALIZE_STATUS; + +class WMHandler //: public Chainable<WMHandler> +{ +public: + WMHandler(); + ~WMHandler(); + WMHandler &operator << (WMHandler &chain); + WMHandler &operator >> (WMHandler &chain); + WMHandler&operator << (WMHandler *chain); + WMHandler &operator >> (WMHandler *chain); + WMHandler &First(); + + virtual void Opened() NEXT(Opened()) + virtual void OpenFailed(); + virtual void ReOpen(); + + virtual void SampleReceived(QWORD &timeStamp, QWORD &duration, unsigned long &outputNum, unsigned long &flags, INSSBuffer *&sample) + NEXT(SampleReceived(timeStamp, duration, outputNum, flags, sample)) + + virtual void AudioDataReceived(void *data, unsigned long sizeBytes, DWORD timestamp) + NEXT(AudioDataReceived(data, sizeBytes, timestamp)) + + virtual void TimeReached(QWORD &timeReached) NEXT(TimeReached(timeReached)) + virtual void NewSourceFlags() NEXT(NewSourceFlags()) + virtual void HasVideo(bool &video) NEXT(HasVideo(video)) + virtual void Started(); + virtual void Stopped(); + virtual void Stopping() NEXT(Stopping()) + virtual void DRMExpired() NEXT(DRMExpired()) + + virtual void Error(); + + virtual void Kill() NEXT(Kill()) + virtual void PreRollComplete(); + + virtual void EndOfFile(); + virtual void Closed(); + virtual void BufferingStarted(); + virtual void BufferingStopped(); + virtual void NewMetadata(); + virtual void Connecting() NEXT(Connecting()) + virtual void Locating() NEXT(Locating()) + + virtual void Individualize(); + virtual void NeedsIndividualization() NEXT(NeedsIndividualization()) + virtual void IndividualizeStatus(WM_INDIVIDUALIZE_STATUS *status) NEXT(IndividualizeStatus(status)) + + virtual void SignatureState(WMT_DRMLA_TRUST *&state); + virtual void NoRights(wchar_t *licenseData); + virtual void NoRightsEx(WM_GET_LICENSE_DATA *&licenseData); + virtual void AcquireLicense(WM_GET_LICENSE_DATA *&licenseData); + virtual void LicenseRequired(); + virtual void BrowserClosed() NEXT(BrowserClosed()) + virtual void LicenseAcquired() NEXT(LicenseAcquired()) + virtual void AllocateOutput(long outputNum, long bufferSize, INSSBuffer *&buffer); + virtual void MonitorCancelled() NEXT(MonitorCancelled()) + virtual void SilentCancelled() NEXT(SilentCancelled()) + + virtual void VideoCatchup(QWORD time); + virtual void TimeToSync(QWORD timeStamp, __int64 &diff); + virtual void OpenCalled() NEXT(OpenCalled()) + + virtual void InitPlaylistBurn() NEXT(InitPlaylistBurn()) + virtual void AccessDenied() NEXT(AccessDenied()) + +private: + WMHandler *next, *prev; +}; +#undef NEXT +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/WMInformation.cpp b/Src/Plugins/Input/in_wmvdrm/WMInformation.cpp new file mode 100644 index 00000000..a9c5701b --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/WMInformation.cpp @@ -0,0 +1,880 @@ +#include "main.h" +#include "WMInformation.h" +#include "resource.h" +#include <exception> +#include <strsafe.h> + +class AutoByte +{ +public: + AutoByte(size_t bytes) + : data(0) + { + data = new BYTE[bytes]; + } + ~AutoByte() + { + if (data) + delete[] data; + data = 0; + } + operator void *() + { + return (void *)data; + } + + BYTE *data; +}; + +static void StoreData(WMT_ATTR_DATATYPE type, BYTE *value, DWORD length, wchar_t *valueStr, size_t len) +{ + switch (type) + { + case WMT_TYPE_DWORD: + StringCchPrintf(valueStr, len, L"%lu", *(DWORD *)value); + break; + + case WMT_TYPE_STRING: + lstrcpyn(valueStr, (wchar_t *)value, len); + break; + + case -1: // hack // if (attrName == L"WM/Text") + StringCchPrintf(valueStr, len, L"%s/%s", UserTextDescription(value, length), UserTextString(value, length)); + break; + case WMT_TYPE_BINARY: + BinaryString(value, length, valueStr, len); + break; + case WMT_TYPE_BOOL: + if (*(BOOL *)value) + { + lstrcpyn(valueStr, L"True", len); + } + else + { + lstrcpyn(valueStr, L"False", len); + } + break; + case WMT_TYPE_QWORD: + StringCchPrintf(valueStr, len, L"%I64u", *(QWORD *)value); + break; + case WMT_TYPE_WORD: + StringCchPrintf(valueStr, len, L"%hu", *(WORD *)value); + break; + case WMT_TYPE_GUID: + GuidString(*(GUID *)value, valueStr, len); + break; + default: + WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN,valueStr,len); + break; + } + +} +WMInformation::WMInformation(const wchar_t *fileName, bool noBlock) + : editor(0), editor2(0), header(0), header3(0), reader(0), header2(0), openError(false) +{ + if (fileName && fileName[0] + && WMCreateEditor(&editor) == S_OK) + { + if (SUCCEEDED(editor->QueryInterface(&editor2))) + { + if (SUCCEEDED(editor2->OpenEx(fileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE))) + { + // good to go + editor2->QueryInterface(&header); + editor->QueryInterface(&header2); + editor2->QueryInterface(&header3); + return ; + } + } + else + { + editor2 = 0; + if (SUCCEEDED(editor->Open(fileName))) + { + // good to go + editor->QueryInterface(&header); + editor->QueryInterface(&header2); + editor->QueryInterface(&header3); + return ; + } + } + // can't open it through the metadata editor interface, let's open a reader + + if (editor) + editor->Release(); + editor = 0; + if (editor2) + editor2->Release(); + editor2 = 0; + if (FAILED(WMCreateReader(0, WMT_RIGHT_PLAYBACK, &reader))) + { + reader = 0; + return ; + } + hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + callback >> this; + + if (FAILED(reader->Open(fileName, &callback, 0))) + { + reader->Release(); + reader = 0; + return ; + } + if (noBlock) + WaitForEvent(hEvent, INFINITE); + else + WaitForSingleObject(hEvent, INFINITE); + + CloseHandle(hEvent); + if (openError) + { + reader->Release(); + reader = 0; + } + else + { + reader->QueryInterface(&header); + reader->QueryInterface(&header2); + reader->QueryInterface(&header3); + } + + } +} + +WMInformation::WMInformation(IWMReader *_reader) + : reader(0), // reader is if we create an internal reader, we don't want to save the passed one (so we don't close it on someone else :) + editor(0), editor2(0), header(0), + header3(0), header2(0), + openError(false), hEvent(NULL) +{ + if (FAILED(_reader->QueryInterface(&header))) + header = 0; + if (FAILED(_reader->QueryInterface(&header2))) + header2 = 0; + if (FAILED(_reader->QueryInterface(&header3))) + header3 = 0; // this error is OK, we can deal with it. +} + +/* +WMInformation::WMInformation(IWMSyncReader *reader) +: editor(0), editor2(0), header(0), header3(0) +{ +reader->QueryInterface(&header); +reader->QueryInterface(&header3); +}*/ + +WMInformation::WMInformation(IWMMetadataEditor *_editor) + : editor(_editor), editor2(0), header(0), + header3(0), reader(0), header2(0), + openError(false), hEvent(NULL) +{ + editor->AddRef(); + editor->QueryInterface(&editor2); + editor->QueryInterface(&header); + editor->QueryInterface(&header2); + editor->QueryInterface(&header3); +} + + +WMInformation::~WMInformation() +{ + if (editor) + { + editor->Close(); + editor->Release(); + editor = 0; + } + if (editor2) + editor2->Release(); + editor2 = 0; + if (header) + header->Release(); + header = 0; + if (header2) + header2->Release(); + header2 = 0; + if (header3) + header3->Release(); + header3 = 0; + if (reader) + { + reader->Close(); + reader->Release(); + reader = 0; + } +} + +bool WMInformation::GetDataType(const wchar_t *name, WMT_ATTR_DATATYPE &type) +{ + if (!name) + return false; + + WORD stream = 0; + WORD dataLen = 0; + if (header && SUCCEEDED(header->GetAttributeByName(&stream, name, &type, 0, &dataLen))) + return true; + else + return false; + +} + +void WMInformation::DeleteAttribute(const wchar_t *attrName) +{ + + WORD indexCount = 0; + if (header3 && SUCCEEDED(header3->GetAttributeIndices(0, attrName, NULL, 0, &indexCount))) + { + WORD *indices = new WORD[indexCount]; + if (SUCCEEDED(header3->GetAttributeIndices(0, attrName, NULL, indices, &indexCount))) + { + for (size_t i = 0;i != indexCount;i++) + { + header3->DeleteAttribute(0, indices[i]); + } + } + } +} + + +void WMInformation::SetAttribute_BinString(const wchar_t *attrName, wchar_t *value) +{ + if (!header || !attrName || !value) + return ; + + if (!*value) + { + DeleteAttribute(attrName); + return ; + } + + AutoChar data(value); + header->SetAttribute(0, attrName, WMT_TYPE_BINARY, (BYTE *)(char *)data, (WORD)strlen(data)); +} + +void WMInformation::GetAttribute_BinString(const wchar_t attrName[], wchar_t *valueStr, size_t len) +{ + if (!header) + { + valueStr[0]=0; + return ; + } + + WMT_ATTR_DATATYPE type; + WORD length = 0; + HRESULT hr; + WORD streamNum = 0; + if (!header || FAILED(header->GetAttributeByName(&streamNum, + attrName, + &type, + 0, + &length))) + { + valueStr[0]=0; + return ; + } + AutoByte v(length); + BYTE *value = v.data; + + hr = header->GetAttributeByName(&streamNum, + attrName, + &type, + value, + &length); + if (FAILED(hr)) + { + valueStr[0]=0; + return ; + } + + + int converted = MultiByteToWideChar(CP_ACP, 0, (const char *)value, length, valueStr, len-1); + valueStr[converted]=0; +} + +void WMInformation::SetAttribute(const wchar_t *attrName, wchar_t *value, WMT_ATTR_DATATYPE defaultType) +{ + if (!header || !attrName || !value) + return ; + + if (!*value) + { + DeleteAttribute(attrName); + return ; + } + + WMT_ATTR_DATATYPE type; + + if (!GetDataType(attrName, type)) + type = defaultType; + + switch (type) + { + case WMT_TYPE_DWORD: + { + DWORD dwordValue = wcstoul(value, 0, 10); + header->SetAttribute(0, attrName, WMT_TYPE_DWORD, (BYTE *) &dwordValue, sizeof(dwordValue)); + } + break; + case WMT_TYPE_STRING: + { + WORD size = static_cast<WORD>((lstrlen(value) + 1) * sizeof(wchar_t)); + header->SetAttribute(0, attrName, WMT_TYPE_STRING, (BYTE *)value, size); + } + break; + case WMT_TYPE_BINARY: + { + // TODO + } + break; + case WMT_TYPE_BOOL: + { + BOOL boolValue; + if (!_wcsicmp(L"true", value)) + boolValue = TRUE; + else + boolValue = FALSE; + + header->SetAttribute(0, attrName, WMT_TYPE_BOOL, (BYTE *)&boolValue, sizeof(boolValue)); + } + break; + case WMT_TYPE_QWORD: + { + { + QWORD qwordValue = _wcstoui64(value, 0, 10); + header->SetAttribute(0, attrName, WMT_TYPE_QWORD, (BYTE *) &qwordValue, sizeof(qwordValue)); + } + } + break; + case WMT_TYPE_WORD: + { + { + WORD wordValue = static_cast<WORD>(wcstoul(value, 0, 10)); + header->SetAttribute(0, attrName, WMT_TYPE_WORD, (BYTE *) &wordValue, sizeof(wordValue)); + } + } + break; + case WMT_TYPE_GUID: + { + GUID guidValue = StringGUID(value); + header->SetAttribute(0, attrName, WMT_TYPE_GUID, (BYTE *) &guidValue, sizeof(guidValue)); + } + break; + } + +} + +bool WMInformation::GetAttributeSize(const wchar_t *name, size_t &size) +{ + WORD stream = 0; + WORD resultSize; + WMT_ATTR_DATATYPE type; + + if (!header || FAILED(header->GetAttributeByName(&stream, name, &type, 0, &resultSize))) + { + return false; + } + size = resultSize; + return true; +} + +DWORD WMInformation::GetDWORDAttr(const wchar_t name[]) +{ + WORD stream = 0; + DWORD result; + + WORD resultSizeWord = sizeof(result); + DWORD resultSize = sizeof(result); + WMT_ATTR_DATATYPE type = WMT_TYPE_DWORD; + WORD count = 1; + WORD indices[1] = {0}; + if ((!header3 + || FAILED(header3->GetAttributeIndices(0, name, NULL, indices, &count)) + || FAILED(header3->GetAttributeByIndexEx(0, indices[0], 0, 0, &type, NULL, (BYTE *) &result, &resultSize))) + && + (!header || FAILED(header->GetAttributeByName(&stream, name, &type, (BYTE *)&result, &resultSizeWord)))) + return 0; + else + return result; + +} + + +long WMInformation::GetLongAttr(const wchar_t name[]) +{ + WORD stream = 0; + long result; + WORD resultSize = sizeof(result); + WMT_ATTR_DATATYPE type; + + if (!header || FAILED(header->GetAttributeByName(&stream, name, &type, (BYTE *)&result, &resultSize))) + { + return 0; + } + + return result; +} + +bool WMInformation::GetBoolAttr(const wchar_t name[]) +{ + WORD stream = 0; + BOOL result; + WORD resultSize = sizeof(result); + WMT_ATTR_DATATYPE type; + + if (!header || FAILED(header->GetAttributeByName(&stream, name, &type, (BYTE *)&result, &resultSize))) + { + return false; + } + + return !!result; +} + +bool WMInformation::IsSeekable() +{ + return GetBoolAttr(g_wszWMSeekable); +} + +long WMInformation::GetLengthMilliseconds() +{ + WORD stream = 0; + long long duration = 0; + WORD resultSize = sizeof(duration); + WMT_ATTR_DATATYPE type; + + if (!header || FAILED(header->GetAttributeByName(&stream, g_wszWMDuration, &type, (BYTE *)&duration, &resultSize))) + { + return -1000; + } + + duration /= 10000LL; + return (long)duration; +} + +long WMInformation::GetBitrate() +{ + return GetDWORDAttr(g_wszWMCurrentBitrate); +} + +WORD WMInformation::GetNumberAttributes() +{ + WORD numAttr = 0; + if ((!header3 || FAILED(header3->GetAttributeCountEx(0, &numAttr))) + && (!header || FAILED(header->GetAttributeCount(0, &numAttr)))) + return 0; + else + return numAttr; +} + + + +void WMInformation::GetAttribute(WORD index, wchar_t *attrName, size_t attrLen, wchar_t *valueStr, size_t valueStrLen) +{ + wchar_t _attrName[1025] = {0}; + WORD nameLen = sizeof(_attrName) / sizeof(_attrName[0]); + WMT_ATTR_DATATYPE type; + WORD lang; + WORD stream = 0; + DWORD length = 0; + WORD lengthWord = 0; + + if ((!header3 || FAILED(header3->GetAttributeByIndexEx(0, index, _attrName, &nameLen, &type, &lang, 0, &length))) + && (!header || FAILED(header->GetAttributeByIndex(index, &stream, _attrName, &nameLen, &type, 0, &lengthWord)))) + { + attrName[0]=0; + valueStr[0]=0; + return ; + } + if (lengthWord) + length = lengthWord; + + AutoByte v(length); + BYTE *value = v.data; + + lstrcpyn(attrName, _attrName, attrLen); + if ((!header3 || FAILED(header3->GetAttributeByIndexEx(0, index, _attrName, &nameLen, &type, &lang, value, &length))) + && (!header || FAILED(header->GetAttributeByIndex(index, &stream, _attrName, &nameLen, &type, value, &lengthWord)))) + { + attrName[0]=0; + valueStr[0]=0; + + return ; + } + + if (attrName == L"WM/Text") + { + type = (WMT_ATTR_DATATYPE)-1; // hack + StringCchCat(attrName, attrLen, L":"); + StringCchCat(attrName, attrLen, UserTextDescription(value, length)); + } + StoreData(type, value, length, valueStr, valueStrLen); +} + +void WMInformation::GetAttribute(const wchar_t attrName[], wchar_t *valueStr, size_t len) +{ + if (!header) + { + valueStr[0]=0; + return ; + } + + WMT_ATTR_DATATYPE type; + WORD length = 0; + HRESULT hr; + WORD streamNum = 0; + if (!header || FAILED(header->GetAttributeByName(&streamNum, + attrName, + &type, + 0, + &length))) + { + valueStr[0]=0; + return ; + } + AutoByte v(length); + BYTE *value = v.data; + + hr = header->GetAttributeByName(&streamNum, + attrName, + &type, + value, + &length); + if (FAILED(hr)) + { + valueStr[0]=0; + return ; + } + + if (attrName == L"WM/Text") + type = (WMT_ATTR_DATATYPE)-1; // hack + StoreData(type, value, length, valueStr, len); +} + + +bool WMInformation::MakeWritable(const wchar_t *fileName) +{ + if (!editor || !editor2) + return false; + + if (FAILED(editor2->OpenEx(fileName, GENERIC_READ | GENERIC_WRITE, 0))) + { + return false; + } + return true; +} + +bool WMInformation::Flush() +{ + if (!editor2 || FAILED(editor->Flush())) + return false; + + return true; +} + +bool WMInformation::IsAttribute(const wchar_t attrName[]) +{ + WMT_ATTR_DATATYPE type = WMT_TYPE_BOOL; + WORD length = sizeof(BOOL); + WORD streamNum = 0; + BOOL value; + if (!header || FAILED(header->GetAttributeByName(&streamNum, + attrName, + &type, + (BYTE *)&value, + &length))) + { + return false; + } + else + { + return !!value; + } +} + +bool WMInformation::IsNotAttribute(const wchar_t attrName[]) +{ + if (!header) + { + return false; + } + + WMT_ATTR_DATATYPE type = WMT_TYPE_BOOL; + WORD length = sizeof(BOOL); + WORD streamNum = 0; + BOOL value; + if (!header || FAILED(header->GetAttributeByName(&streamNum, + attrName, + &type, + (BYTE *)&value, + &length))) + { + return false; + } + else + { + return !value; + } +} + +bool WMInformation::MakeReadOnly(const wchar_t *fileName) +{ + if (!editor || !editor2) + return false; + + //editor->Close(); + if (FAILED(editor2->OpenEx(fileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE))) + { + return false; + } + return true; +} + + +bool WMInformation::NonWritable() +{ + if (!editor2) + return true; + else + return false; +} + +void WMInformation::DeleteUserText(const wchar_t *description) +{ + WORD indexCount = 0; + WMT_ATTR_DATATYPE type = WMT_TYPE_BOOL; + WORD nameLen = 128; + if (header3 && SUCCEEDED(header3->GetAttributeIndices(0, L"WM/Text", NULL, 0, &indexCount))) + { + WORD *indices = new WORD[indexCount]; + if (SUCCEEDED(header3->GetAttributeIndices(0, L"WM/Text", NULL, indices, &indexCount))) + { + for (size_t index = 0;index != indexCount;index++) + { + WMT_ATTR_DATATYPE type = WMT_TYPE_BINARY; + WORD lang = 0; + DWORD length = 0; + wchar_t _attrName[128] = L"WM/Text"; + if (SUCCEEDED(header3->GetAttributeByIndexEx(0, indices[index], _attrName, &nameLen, &type, &lang, 0, &length))) + { + AutoByte v(length); + BYTE *value = v.data; + + if (SUCCEEDED(header3->GetAttributeByIndexEx(0, indices[index], _attrName, &nameLen, &type, &lang, value, &length))) + { + if (UserTextDescription(value, length) == description) + { + header3->DeleteAttribute(0, indices[index]); + } + } + } + } + } + } +} + +void WMInformation::SetUserText(const wchar_t *description, const wchar_t *valueStr) +{ + if (!header3 || !description || !valueStr) + return; + + WM_USER_TEXT userText; + userText.pwszDescription = (LPWSTR)description; + userText.pwszText = (LPWSTR) valueStr; + + WORD index; + header3->AddAttribute(0, L"WM/Text", &index, WMT_TYPE_BINARY, 0, (BYTE *) &userText, sizeof(userText)); +} + +void WMInformation::ClearAllAttributes() +{ + WORD numAttrs; + header3->GetAttributeCountEx(0xFFFF, &numAttrs); + while (numAttrs--) + { + header3->DeleteAttribute(0xFFFF, numAttrs); + } +} + +bool WMInformation::GetCodecName(wchar_t *storage, size_t len) +{ + if (!header2) + return false; + + DWORD codecs=0; + header2->GetCodecInfoCount(&codecs); + for (DWORD i=0;i!=codecs;i++) + { + WORD nameLen=0, descriptionLen=0, infoLen = 0; + WMT_CODEC_INFO_TYPE type; + header2->GetCodecInfo(i, &nameLen, 0, &descriptionLen, 0, &type, &infoLen, 0); + if (type == WMT_CODECINFO_AUDIO) + { + wchar_t *name = new wchar_t[nameLen]; + wchar_t *description = new wchar_t[descriptionLen]; + BYTE *info = new BYTE[infoLen]; + header2->GetCodecInfo(i, &nameLen, name, &descriptionLen, description, &type, &infoLen, info); + lstrcpynW(storage, name, len); + delete[] name; + delete[]description; + delete[] info; + + return true; + } + } + return false; +} + +bool WMInformation::GetPicture(void **data, size_t *len, wchar_t **mimeType, int pictype) +{ + WORD indexCount = 0; + WMT_ATTR_DATATYPE type = WMT_TYPE_BINARY; + WORD nameLen = 128; + if (header3 && SUCCEEDED(header3->GetAttributeIndices(0, g_wszWMPicture, NULL, 0, &indexCount))) + { + WORD *indices = new WORD[indexCount]; + if (SUCCEEDED(header3->GetAttributeIndices(0, g_wszWMPicture, NULL, indices, &indexCount))) + { + for (size_t index = 0;index != indexCount;index++) + { + WMT_ATTR_DATATYPE type = WMT_TYPE_BINARY; + WORD lang = 0; + DWORD length = 0; + wchar_t _attrName[128] = {0}; + if (SUCCEEDED(header3->GetAttributeByIndexEx(0, indices[index], _attrName, &nameLen, &type, &lang, 0, &length))) + { + AutoByte v(length); + BYTE *value = v.data; + + if (SUCCEEDED(header3->GetAttributeByIndexEx(0, indices[index], _attrName, &nameLen, &type, &lang, value, &length))) + { + WM_PICTURE *picture = (WM_PICTURE *)value; + if (picture->bPictureType == pictype) + { + *len = picture->dwDataLen; + *data = WASABI_API_MEMMGR->sysMalloc(*len); + memcpy(*data, picture->pbData, *len); + wchar_t *type=0; + if (picture->pwszMIMEType) + type = wcschr(picture->pwszMIMEType, L'/'); + + if (type && *type) + { + type++; + + wchar_t *type2 = wcschr(type, L'/'); + if (type2 && *type2) type2++; + else type2 = type; + + size_t mimelen = wcslen(type2)+1; + *mimeType = (wchar_t *)WASABI_API_MEMMGR->sysMalloc(mimelen*sizeof(wchar_t)); + StringCchCopyW(*mimeType, mimelen, type2); + } + else + *mimeType = 0; // unknown! + delete[] indices; + return true; + } + } + } + } + } + delete[] indices; + } + return false; +} + +bool WMInformation::SetPicture(void *data, size_t len, const wchar_t *mimeType, int type) +{ + WM_PICTURE picture; + picture.bPictureType = type; + picture.dwDataLen = len; + picture.pbData = (BYTE *)data; + picture.pwszDescription=L""; + wchar_t mt[32] = {0}; + if (wcsstr(mimeType, L"/") != 0) + { + StringCchCopyW(mt, 32, mimeType); + } + else + { + StringCchPrintfW(mt, 32, L"image/%s", mimeType); + } + picture.pwszMIMEType = mt; + return SUCCEEDED(header->SetAttribute(0, g_wszWMPicture, WMT_TYPE_BINARY, (const BYTE *)&picture, sizeof(picture))); +} + +bool WMInformation::HasPicture(int pictype) +{ + WORD indexCount = 0; + WMT_ATTR_DATATYPE type = WMT_TYPE_BINARY; + WORD nameLen = 128; + if (header3 && SUCCEEDED(header3->GetAttributeIndices(0, g_wszWMPicture, NULL, 0, &indexCount))) + { + WORD *indices = new WORD[indexCount]; + if (SUCCEEDED(header3->GetAttributeIndices(0, g_wszWMPicture, NULL, indices, &indexCount))) + { + for (size_t index = 0;index != indexCount;index++) + { + WMT_ATTR_DATATYPE type = WMT_TYPE_BINARY; + WORD lang = 0; + DWORD length = 0; + wchar_t _attrName[128] = {0}; + if (SUCCEEDED(header3->GetAttributeByIndexEx(0, indices[index], _attrName, &nameLen, &type, &lang, 0, &length))) + { + AutoByte v(length); + BYTE *value = v.data; + + if (SUCCEEDED(header3->GetAttributeByIndexEx(0, indices[index], _attrName, &nameLen, &type, &lang, value, &length))) + { + WM_PICTURE *picture = (WM_PICTURE *)value; + if (picture->bPictureType == pictype) + { + delete[] indices; + return true; + } + } + } + } + } + delete[] indices; + } + return false; +} + +bool WMInformation::DeletePicture(int pictype) +{ + WORD indexCount = 0; + WMT_ATTR_DATATYPE type = WMT_TYPE_BINARY; + WORD nameLen = 128; + if (header3 && SUCCEEDED(header3->GetAttributeIndices(0, g_wszWMPicture, NULL, 0, &indexCount))) + { + WORD *indices = new WORD[indexCount]; + if (SUCCEEDED(header3->GetAttributeIndices(0, g_wszWMPicture, NULL, indices, &indexCount))) + { + for (size_t index = 0;index != indexCount;index++) + { + WMT_ATTR_DATATYPE type = WMT_TYPE_BINARY; + WORD lang = 0; + DWORD length = 0; + wchar_t _attrName[128] = {0}; + if (SUCCEEDED(header3->GetAttributeByIndexEx(0, indices[index], _attrName, &nameLen, &type, &lang, 0, &length))) + { + AutoByte v(length); + BYTE *value = v.data; + + if (SUCCEEDED(header3->GetAttributeByIndexEx(0, indices[index], _attrName, &nameLen, &type, &lang, value, &length))) + { + WM_PICTURE *picture = (WM_PICTURE *)value; + if (picture->bPictureType == pictype) + { + header3->DeleteAttribute(0, indices[index]); + delete[] indices; + return true; + } + } + } + } + } + delete[] indices; + } + return false; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/WMInformation.h b/Src/Plugins/Input/in_wmvdrm/WMInformation.h new file mode 100644 index 00000000..a9e619b6 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/WMInformation.h @@ -0,0 +1,89 @@ +#ifndef NULLSOFT_WMINFORMATIONH +#define NULLSOFT_WMINFORMATIONH + +#include <wmsdk.h> +#include "WMCallback.h" + +class WMInformation : public WMHandler +{ +public: + WMInformation(const wchar_t *fileName, bool noBlock=false); + WMInformation(IWMReader *reader); + //WMInformation(IWMSyncReader *reader); + WMInformation(IWMMetadataEditor *_editor); + //WMInformation(); + bool ErrorOpening() + { + return openError; + } // TODO: benski> this is only valid for WMInformation(const wchar_t *fileName, bool noBlock=false)!!! + virtual ~WMInformation(); + + bool MakeWritable(const wchar_t *fileName); + bool NonWritable(); + bool MakeReadOnly(const wchar_t *fileName); + bool Flush(); + bool IsSeekable(); + long GetLengthMilliseconds(); + long GetBitrate(); + WORD GetNumberAttributes(); + void ClearAllAttributes(); + bool IsAttribute(const wchar_t attrName[]); // false might mean "attribute not found", see IsNotAttribute + bool IsNotAttribute(const wchar_t attrName[]); // false might mean "attribute not found", see IsAttribute + void GetAttribute(WORD index, wchar_t *attrName, size_t attrLen, wchar_t *valueStr, size_t valueStrLen); + void GetAttribute(const wchar_t attrName[], wchar_t *valueStr, size_t len); + void SetAttribute(const wchar_t *attrName, wchar_t *value, WMT_ATTR_DATATYPE defaultType = WMT_TYPE_STRING); + void DeleteAttribute(const wchar_t *attrName); + bool GetAttributeSize(const wchar_t *attrName, size_t &size); + void LicenseRequired() + { + First().OpenFailed(); + } + void SetAttribute_BinString(const wchar_t *attrName, wchar_t *value); + void GetAttribute_BinString(const wchar_t attrName[], wchar_t *valueStr, size_t len); + + void DeleteUserText(const wchar_t *description); + void SetUserText(const wchar_t *description, const wchar_t *valueStr); + + bool GetCodecName(wchar_t *storage, size_t len); + bool GetPicture(void **data, size_t *len, wchar_t **mimeType, int type); + bool SetPicture(void *data, size_t len, const wchar_t *mimeType, int type); + bool DeletePicture(int type); + bool HasPicture(int type); +private: + bool GetDataType(const wchar_t *name, WMT_ATTR_DATATYPE &type); + long GetLongAttr(const wchar_t name[]); + bool GetBoolAttr(const wchar_t name[]); + DWORD GetDWORDAttr(const wchar_t name[]); + struct IWMMetadataEditor *editor; + struct IWMMetadataEditor2 *editor2; + struct IWMHeaderInfo *header; + struct IWMHeaderInfo2 *header2; + struct IWMHeaderInfo3 *header3; + struct IWMReader *reader; + + WMCallback callback; + HANDLE hEvent; + bool openError; + void NeedsIndividualization() + { + First().OpenFailed(); + } + void Opened() + { + openError=false; + SetEvent(hEvent); + } + void OpenFailed() + { + openError=true; + SetEvent(hEvent); + } + void Error() + { + openError=true; + SetEvent(hEvent); + } + +}; + +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/WMPlaylist.cpp b/Src/Plugins/Input/in_wmvdrm/WMPlaylist.cpp new file mode 100644 index 00000000..7f1d6a20 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/WMPlaylist.cpp @@ -0,0 +1,45 @@ +#include "main.h" +#include "WMPlaylist.h" + +WMPlaylist activePlaylist; + +void WMPlaylist::OnFile( const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info ) +{ + //if (playstring.empty()) + if ( playstring ) + free( playstring ); + + playstring = _wcsdup( filename ); +} + +const wchar_t *WMPlaylist::GetFileName() +{ + return ( playstring ? playstring : L"" ); +} + +const wchar_t *WMPlaylist::GetOriginalFileName() +{ + return ( playlistFilename ? playlistFilename : L"" ); +} + +bool WMPlaylist::IsMe( const char *filename ) +{ + return IsMe( (const wchar_t *)AutoWide( filename ) ); +} + +bool WMPlaylist::IsMe( const wchar_t *filename ) +{ + if ( playlistFilename && !_wcsicmp( playlistFilename, filename ) ) + return true; + + if ( playstring && !_wcsicmp( playstring, filename ) ) + return true; + + return false; +} + +#define CBCLASS WMPlaylist +START_DISPATCH; +VCB( IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile ) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/WMPlaylist.h b/Src/Plugins/Input/in_wmvdrm/WMPlaylist.h new file mode 100644 index 00000000..8147e5d0 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/WMPlaylist.h @@ -0,0 +1,56 @@ +#ifndef NULLSOFT_IN_WMVDRM_WMPLAYLIST_H +#define NULLSOFT_IN_WMVDRM_WMPLAYLIST_H + +#include "../playlist/ifc_playlistloadercallback.h" + +class WMPlaylist : public ifc_playlistloadercallback +{ +public: + WMPlaylist() {} + + ~WMPlaylist() + { + if ( playstring ) + free( playstring ); + + if ( playlistFilename ) + free( playlistFilename ); + } + + void Clear() + { + if ( playstring ) + free( playstring ); + + playstring = 0; + + if ( playlistFilename ) + free( playlistFilename ); + + playlistFilename = 0; + } + + void OnFile( const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info ); + + const wchar_t *GetFileName(); + const wchar_t *GetOriginalFileName(); + /* TODO: need something like these, just not sure exact what yet + bool ForceStartTime(int &); + bool ForceLength(int &); + bool ForceNoSeek(); + */ + bool IsMe( const char *filename ); + bool IsMe( const wchar_t *filename ); + + +protected: + RECVS_DISPATCH; + +public: + wchar_t *playstring = 0; + wchar_t *playlistFilename = 0; +}; + +extern WMPlaylist activePlaylist; + +#endif // !NULLSOFT_IN_WMVDRM_WMPLAYLIST_H
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/WPLLoader.cpp b/Src/Plugins/Input/in_wmvdrm/WPLLoader.cpp new file mode 100644 index 00000000..6978cae8 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/WPLLoader.cpp @@ -0,0 +1,128 @@ +#include "main.h" +#include "WPLLoader.h" +#include <stdio.h> +#include "../nu/AutoWide.h" +#include "../xml/ifc_xmlreadercallback.h" +#include "../xml/obj_xml.h" +#include "api.h" +#include <api/service/waservicefactory.h> +#include <shlwapi.h> +#include <strsafe.h> + +class WPLXML : public ifc_xmlreadercallback +{ +public: + WPLXML(ifc_playlistloadercallback *_playlist, const wchar_t *wplFilename) : playlist(_playlist) + { + lstrcpynW(rootPath, wplFilename, MAX_PATH); + PathRemoveFileSpecW(rootPath); + } + void StartTag(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params) +{ + //not necessary YET, it will be if we register for more things: if (!_wcsicmp(xmlpath, L"smil\fbody\fseq\fmedia")) + { + const wchar_t *track = params->getItemValue(L"src"); + + if (track) + { + if (PathIsRootW(track) || PathIsURLW(track)) + { + playlist->OnFile(track, 0, -1, 0); // TODO: more info!!! + } + else + { + wchar_t fullPath[MAX_PATH] = {0}, canonicalizedPath[MAX_PATH] = {0}; + PathCombineW(fullPath, rootPath, track); + PathCanonicalizeW(canonicalizedPath, fullPath); + playlist->OnFile(canonicalizedPath, 0, -1, 0); // TODO: more info!!! + } + } + } +} +ifc_playlistloadercallback *playlist; +wchar_t rootPath[MAX_PATH]; +protected: + RECVS_DISPATCH; +}; + +#ifdef CBCLASS +#undef CBCLASS +#endif + +#define CBCLASS WPLXML +START_DISPATCH; +VCB(ONSTARTELEMENT, StartTag) +END_DISPATCH; + + +WPLLoader::WPLLoader() +{ +} + +WPLLoader::~WPLLoader(void) +{ + //Close(); +} + +int WPLLoader::Load(const wchar_t *filename, ifc_playlistloadercallback *playlist) +{ + HANDLE file = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL); + + if (file == INVALID_HANDLE_VALUE) + return IFC_PLAYLISTLOADER_FAILED; + + obj_xml *parser=0; + waServiceFactory *parserFactory=0; + + parserFactory = plugin.service->service_getServiceByGuid(obj_xmlGUID); + if (parserFactory) + parser = (obj_xml *)parserFactory->getInterface(); + + if (parser) + { + WPLXML wplXml(playlist, filename); + parser->xmlreader_registerCallback(L"smil\fbody\fseq\fmedia", &wplXml); + parser->xmlreader_open(); + parser->xmlreader_setEncoding(L"UTF-8"); // WPL is always UTF-8, but doesn't explicitly have it + + while (true) + { + char data[1024] = {0}; + DWORD bytesRead = 0; + if (ReadFile(file, data, 1024, &bytesRead, NULL) && bytesRead) + { + if (parser->xmlreader_feed(data, bytesRead) != API_XML_SUCCESS) + { + CloseHandle(file); + parser->xmlreader_unregisterCallback(&wplXml); + parser->xmlreader_close(); + parserFactory->releaseInterface(parser); + return IFC_PLAYLISTLOADER_FAILED; + } + } + else + break; + } + + CloseHandle(file); + parser->xmlreader_feed(0, 0); + + parser->xmlreader_unregisterCallback(&wplXml); + parser->xmlreader_close(); + parserFactory->releaseInterface(parser); + return IFC_PLAYLISTLOADER_SUCCESS; + } + + CloseHandle(file); + return IFC_PLAYLISTLOADER_FAILED; +} + + +#ifdef CBCLASS +#undef CBCLASS +#endif + +#define CBCLASS WPLLoader +START_DISPATCH; +CB(IFC_PLAYLISTLOADER_LOAD, Load) +END_DISPATCH; diff --git a/Src/Plugins/Input/in_wmvdrm/WPLLoader.h b/Src/Plugins/Input/in_wmvdrm/WPLLoader.h new file mode 100644 index 00000000..650cc58b --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/WPLLoader.h @@ -0,0 +1,19 @@ +#ifndef NULLSOFT_PLAYLIST_WPL_LOADER_H +#define NULLSOFT_PLAYLIST_WPL_LOADER_H + +#include "../playlist/ifc_playlistloader.h" +#include "../playlist/ifc_playlistloadercallback.h" +#include <stdio.h> +class WPLLoader : public ifc_playlistloader +{ +public: + int Load(const wchar_t *filename, ifc_playlistloadercallback *playlist); + +public: + WPLLoader(); + virtual ~WPLLoader(void); + +protected: + RECVS_DISPATCH; +}; +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/WaitLayer.cpp b/Src/Plugins/Input/in_wmvdrm/WaitLayer.cpp new file mode 100644 index 00000000..39192a8e --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/WaitLayer.cpp @@ -0,0 +1,49 @@ +#include "main.h" +#include "WaitLayer.h" +#include "util.h" + +WaitLayer::WaitLayer(IWMReader *_reader) +: reader(_reader), stopEvent(0) +{ + reader->AddRef(); + openEvent = CreateEvent(0, TRUE, FALSE, 0); +} + +WaitLayer::~WaitLayer() +{ + reader->Release(); + reader=0; +} + +void WaitLayer::Opened() +{ + SetEvent(openEvent); + WMHandler::Opened(); +} + +bool WaitLayer::IsOpen() +{ + return WaitForSingleObject(openEvent, 0) == WAIT_OBJECT_0; +} + +void WaitLayer::OpenCalled() +{ + SetEvent(openEvent); + WMHandler::OpenCalled(); +} + +void WaitLayer::OpenFailed() +{ + SetEvent(openEvent); + WMHandler::OpenFailed(); +} + +bool WaitLayer::WaitForOpen(int time_ms) +{ + return WaitForSingleObject(openEvent, time_ms) == WAIT_OBJECT_0; +} + +void WaitLayer::ResetForOpen() +{ + ResetEvent(openEvent); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/WaitLayer.h b/Src/Plugins/Input/in_wmvdrm/WaitLayer.h new file mode 100644 index 00000000..5b4c7add --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/WaitLayer.h @@ -0,0 +1,25 @@ +#ifndef NULLSOFT_WAITLAYERH +#define NULLSOFT_WAITLAYERH + +#include "WMHandler.h" + +class WaitLayer : public WMHandler +{ +public: + WaitLayer(IWMReader *_reader); + ~WaitLayer(); + + void ResetForOpen(); + bool WaitForOpen(int time_ms); + bool IsOpen(); +protected: + /* inherited from WMCallback */ + void OpenCalled(); + void OpenFailed(); + void Opened(); + + IWMReader *reader; // not ours + HANDLE stopEvent, openEvent; +}; + +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/WinampInterface.cpp b/Src/Plugins/Input/in_wmvdrm/WinampInterface.cpp new file mode 100644 index 00000000..1dfc8ecf --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/WinampInterface.cpp @@ -0,0 +1,183 @@ +#include "Main.h" +#include "WinampInterface.h" +#include "../Winamp/wa_ipc.h" +#include <cassert> +#include "WMDRMModule.h" +#include <strsafe.h> +#include "WMPlaylist.h" +#include "../nu/AutoChar.h" +WinampInterface winamp; + +extern WMDRM mod; +using namespace Nullsoft::Utility; + +#ifndef NO_DRM +#include "vid_overlay.h" +#include "vid_ddraw.h" + +OverlayVideoOutput overlay; +DDrawVideoOutput ddraw; +#endif + + +WinampInterface::WinampInterface() + : videoWindow(0), bufferCount(0), + statusGuard(GUARDNAME("WinampInterface::statusGuard")) +{ + statusFilename[0] = 0; + status[0] = 0; +} + +/* +@returns winamp's video window handle +*/ +HWND WinampInterface::GetVideoWindow() +{ + return (HWND)GetVideoOutput()->extended(VIDUSER_GET_VIDEOHWND, 0, 0); // ask for the video hwnd +} + +IVideoOutput *WinampInterface::GetVideoOutput() +{ + if (!videoWindow) + videoWindow = (IVideoOutput *)SendMessage(GetWinampWindow(), WM_WA_IPC, 0, IPC_GET_IVIDEOOUTPUT); // ask winamp for an interface to the video output + return videoWindow; + +} + +void WinampInterface::EndOfFile() +{ + PostMessage(GetWinampWindow(), WM_WA_MPEG_EOF, 0, 0); +} + +HWND WinampInterface::GetWinampWindow() +{ + return plugin.hMainWindow; +} + +void WinampInterface::SetStatus(wchar_t *_status) +{ + { + AutoLock lock (statusGuard); + StringCchCopy(status, 1024, _status); + StringCchCopy(statusFilename, FILENAME_SIZE, activePlaylist.GetFileName()); + } + PostMessage(GetWinampWindow(), WM_WA_IPC, 0, IPC_UPDTITLE); +} + +bool WinampInterface::GetStatus(wchar_t *title, size_t titleLen, const wchar_t *filename) +{ + AutoLock lock (statusGuard); + + if (status[0] && title && filename && !lstrcmpi(statusFilename, filename)) + { + StringCchPrintf(title, titleLen, L"[%s]%s", status, filename); + return true; + } + else + return false; +} + +bool WinampInterface::GetStatusHook(wchar_t *title, size_t titleLen, const wchar_t *filename) +{ + AutoLock lock (statusGuard); + + if (status[0] && title && filename && !lstrcmpi(statusFilename, filename)) + { + wchar_t *oldTitle = _wcsdup(title); + StringCchPrintf(title, titleLen, L"[%s]%s", status, oldTitle); + free(oldTitle); + return true; + } + else + return false; +} + +bool WinampInterface::HasStatus(const wchar_t *filename) +{ + AutoLock lock (statusGuard); + if (status[0] && filename && !lstrcmpi(statusFilename, filename)) + return true; + return false; +} + +void WinampInterface::ClearStatus() +{ + { + //AutoLock lock (statusGuard); // should be safe not to lock here + status[0] = 0; + statusFilename[0]=0; + } + PostMessage(GetWinampWindow(), WM_WA_IPC, 0, IPC_UPDTITLE); +} + +void WinampInterface::EncryptedDrawFrame(void *frame) +{ +#ifndef NO_DRM + overlay.SetFrame(frame); + ddraw.SetFrame(frame); + SecureZeroMemory(&frame, sizeof(void *)); + GetVideoOutput()->draw((void *)1); +#endif +} + +bool WinampInterface::OpenEncryptedVideo(int width, int height, bool flip, double aspect, int fourcc) +{ +#ifndef NO_DRM + VideoOpenStruct openVideo = {width, height, flip, aspect, fourcc}; + + bool openedOK = false; + if (config_video.overlays()) + { + openedOK = !!GetVideoOutput()->extended(VIDUSER_OPENVIDEORENDERER, (intptr_t)(VideoRenderer *)&overlay, (intptr_t)&openVideo); + if (openedOK) + return true; + } + + openedOK = !!GetVideoOutput()->extended(VIDUSER_OPENVIDEORENDERER, (intptr_t)(VideoRenderer *)&ddraw, (intptr_t)&openVideo); + if (openedOK) + return true; +#endif + + return false; +} + +void WinampInterface::CloseEncryptedVideo() +{ + GetVideoOutput()->extended(VIDUSER_CLOSEVIDEORENDERER, 0, 0); +} + +void WinampInterface::Buffering(int bufStatus, const wchar_t *displayString) +{ + char tempdata[75*2] = {0, }; + + int csa = plugin.SAGetMode(); + if (csa & 1) + { + for (int x = 0; x < bufStatus*75 / 100; x ++) + tempdata[x] = x * 16 / 75; + } + else if (csa&2) + { + int offs = (csa & 1) ? 75 : 0; + int x = 0; + while (x < bufStatus*75 / 100) + { + tempdata[offs + x++] = -6 + x * 14 / 75; + } + while (x < 75) + { + tempdata[offs + x++] = 0; + } + } + else if (csa == 4) + { + tempdata[0] = tempdata[1] = (bufStatus * 127 / 100); + } + if (csa) plugin.SAAdd(tempdata, ++bufferCount, (csa == 3) ? 0x80000003 : csa); + + wchar_t temp[64] = {0}; + StringCchPrintf(temp, 64, L"%s: %d%%",displayString, bufStatus); + SetStatus(temp); + //SetVideoStatusText(temp); // TODO: find a way to set the old status back + GetVideoOutput()->notifyBufferState(static_cast<int>(bufStatus*2.55f)); +} diff --git a/Src/Plugins/Input/in_wmvdrm/WinampInterface.h b/Src/Plugins/Input/in_wmvdrm/WinampInterface.h new file mode 100644 index 00000000..227b834c --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/WinampInterface.h @@ -0,0 +1,121 @@ +#ifndef NULLSOFT_WINAMPINTERFACEH +#define NULLSOFT_WINAMPINTERFACEH + +#include "Main.h" +#include <windows.h> +#include "../Winamp/wa_ipc.h" +#include "../Winamp/In2.h" +#include "../Winamp/strutil.h" +#include "output/AudioOut.h" +#include "../nu/AutoLock.h" + +extern AudioOut *out; +extern In_Module plugin; + +class WinampInterface +{ +public: + WinampInterface(); + + HWND GetVideoWindow(); + + IVideoOutput *GetVideoOutput(); + void EndOfFile(); + HWND GetWinampWindow(); + + void RefreshTitle() + { + PostMessage(plugin.hMainWindow, WM_USER, 0, IPC_UPDTITLE); + } + + const char *GetProxy() + { + return (const char *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GET_PROXY_STRING); + } + + void ResetBuffering() { bufferCount=0;} + void Buffering(int bufStatus, const wchar_t *displayString); + + bool OpenEncryptedVideo(int width, int height, bool flip, double aspect, int fourcc); + bool OpenVideo(int width, int height, bool flip, double aspect, int fourcc) + { + GetVideoOutput()->extended(VIDUSER_SET_THREAD_SAFE, 1, 0); + bool video = (GetVideoOutput()->open(width, height, flip ? 1 : 0, aspect, fourcc) == 0); + return video; + } + + ULONG_PTR GetStart() + { + return SendMessage(plugin.hMainWindow, WM_WA_IPC,0,IPC_GETPLAYITEM_START); + } + + ULONG_PTR GetEnd() + { + return SendMessage(plugin.hMainWindow, WM_WA_IPC,0,IPC_GETPLAYITEM_END); + } + + void PressStop() + { + SendMessage(plugin.hMainWindow, WM_COMMAND, 40047, 0); + } + + void PressPlay() + { + SendMessage(plugin.hMainWindow, WM_COMMAND,40045, 0); + } + + void DrawFrame(void *frame) + { + GetVideoOutput()->draw(frame); + } + + void EncryptedDrawFrame(void *frame); + void SetVideoStatusText(char *text) + { + GetVideoOutput()->extended(VIDUSER_SET_INFOSTRING,(INT_PTR)text,0); + } + void SetVideoPalette(RGBQUAD *palette) + { + GetVideoOutput()->extended(VIDUSER_SET_PALETTE,(INT_PTR)palette,0); + } + void CloseViz() + { + plugin.SAVSADeInit(); + } + void CloseEncryptedVideo(); + void CloseVideo() + { + GetVideoOutput()->close(); + } + + void SetAudioInfo(int bitRateKiloBits, int sampleRateKiloHertz, int channels) + { + plugin.SetInfo(bitRateKiloBits, sampleRateKiloHertz, channels, 1); + } + + void OpenViz(int maxLatency, int sampleRate) + { + plugin.SAVSAInit(maxLatency, sampleRate); + } + + void SetVizInfo(int sampleRate, int channels) + { + plugin.VSASetInfo(sampleRate, channels); + } + + bool GetStatusHook(wchar_t *title, size_t titleLen, const wchar_t *filename); + bool HasStatus(const wchar_t *filename); + void SetStatus(wchar_t *_status); + bool GetStatus(wchar_t *title, size_t titleLen, const wchar_t *filename); + + void ClearStatus(); + Nullsoft::Utility::LockGuard statusGuard; + int bufferCount; + +private: + wchar_t status[1024]; + wchar_t statusFilename[FILENAME_SIZE]; + IVideoOutput *videoWindow; +}; + +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/XMLString.cpp b/Src/Plugins/Input/in_wmvdrm/XMLString.cpp new file mode 100644 index 00000000..bc36a151 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/XMLString.cpp @@ -0,0 +1,45 @@ +#include "main.h" +#include "XMLString.h" +#include <strsafe.h> + + XMLString::XMLString() + { + data[0]=0; + } + + void XMLString::Reset() + { + data[0]=0; + } + + const wchar_t *XMLString::GetString() + { + return data; + } + +void XMLString::StartTag(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params) +{ + data[0]=0; +} + + +void XMLString::TextHandler(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *str) +{ + StringCchCatW(data, 256, str); +} + + +void XMLString::ManualSet(const wchar_t *string) +{ +StringCchCatW(data, 256, string); +} + +#ifdef CBCLASS +#undef CBCLASS +#endif + +#define CBCLASS XMLString +START_DISPATCH; +VCB(ONSTARTELEMENT, StartTag) +VCB(ONCHARDATA, TextHandler) +END_DISPATCH; diff --git a/Src/Plugins/Input/in_wmvdrm/XMLString.h b/Src/Plugins/Input/in_wmvdrm/XMLString.h new file mode 100644 index 00000000..b80380eb --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/XMLString.h @@ -0,0 +1,29 @@ +#ifndef NULLSOFT_WINAMP_XMLSTRING_H +#define NULLSOFT_WINAMP_XMLSTRING_H + + +#include "../xml/ifc_xmlreadercallback.h" +/* +this one is an xml callback that just saves the last encountered string +*/ + + +class XMLString : public ifc_xmlreadercallback +{ +public: + XMLString(); + void Reset(); + const wchar_t *GetString(); + void ManualSet(const wchar_t *string); +private: + /* XML callbacks */ + void StartTag(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params); + void EndTag(const wchar_t *xmlpath, const wchar_t *xmltag); + void TextHandler(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *str); + + wchar_t data[256]; // for now, we'll make it dynamic later + + RECVS_DISPATCH; +}; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/api.cpp b/Src/Plugins/Input/in_wmvdrm/api.cpp new file mode 100644 index 00000000..05749577 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/api.cpp @@ -0,0 +1,56 @@ +#include "main.h" +#include "api.h" +#include <windows.h> +#include "../Winamp/wa_ipc.h" +#include "FactoryHelper.h" +#include "MetaTagFactory.h" +#include "factory_Handler.h" +#include "AlbumArt.h" +#include "RawReader.h" +#include "../nu/Singleton.h" +MetaTagFactory metaTagFactory; + +api_service *serviceManager = 0; +api_playlistmanager *playlistManager=0; +api_config *AGAVE_API_CONFIG=0; +api_application *applicationApi=0; + +// wasabi based services for localisation support +api_language *WASABI_API_LNG = 0; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; +api_memmgr *WASABI_API_MEMMGR = 0; +WPLHandlerFactory wplHandlerFactory; +ASXHandlerFactory asxHandlerFactory; +AlbumArtFactory albumArtFactory; +static RawMediaReaderService raw_media_reader_service; +static SingletonServiceFactory<svc_raw_media_reader, RawMediaReaderService> raw_factory; + +int LoadWasabi() +{ + ServiceBuild(AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceBuild(playlistManager, api_playlistmanagerGUID); + ServiceBuild(WASABI_API_APP, applicationApiServiceGuid); + ServiceBuild(WASABI_API_LNG, languageApiGUID); + ServiceBuild(WASABI_API_MEMMGR, memMgrApiServiceGuid); + plugin.service->service_register(&metaTagFactory); + plugin.service->service_register(&wplHandlerFactory); + plugin.service->service_register(&asxHandlerFactory); + plugin.service->service_register(&albumArtFactory); + raw_factory.Register(plugin.service, &raw_media_reader_service); + + return TRUE; +} + +void UnloadWasabi() +{ + plugin.service->service_deregister(&metaTagFactory); + plugin.service->service_deregister(&wplHandlerFactory); + plugin.service->service_deregister(&asxHandlerFactory); + plugin.service->service_deregister(&albumArtFactory); + plugin.service->service_deregister(&raw_factory); + ServiceRelease(playlistManager, api_playlistmanagerGUID); + ServiceRelease(AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceRelease(WASABI_API_APP, applicationApiServiceGuid); + ServiceRelease(WASABI_API_LNG, languageApiGUID); + ServiceRelease(WASABI_API_MEMMGR, memMgrApiServiceGuid); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/api.h b/Src/Plugins/Input/in_wmvdrm/api.h new file mode 100644 index 00000000..4e226ea1 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/api.h @@ -0,0 +1,42 @@ +#ifndef NULLSOFT_API_H +#define NULLSOFT_API_H + +#include <windows.h> +int LoadWasabi(); +void UnloadWasabi(); + +#include "../playlist/api_playlistmanager.h" +extern api_playlistmanager *playlistManager; +#define AGAVE_API_PLAYLISTMANAGER playlistManager + +#include "../Agave/Config/api_config.h" +extern api_config *config; +#define AGAVE_API_CONFIG config + +#include <api/application/api_application.h> +extern api_application *applicationApi; +#define WASABI_API_APP applicationApi + +#include <api/memmgr/api_memmgr.h> +extern api_memmgr *memmgr; +#define WASABI_API_MEMMGR memmgr + +#include "../Agave/Language/api_language.h" + +// these are custom defines for the out_wave and out_ds embedded implementations +extern HINSTANCE WASABI_API_LNG_HINST_WAV, WASABI_API_LNG_HINST_WAV_ORIG; +extern HINSTANCE WASABI_API_LNG_HINST_DS, WASABI_API_LNG_HINST_DS_ORIG; + +#define WASABI_API_LNGSTRING_WAV(uID) WASABI_API_LNGSTR(WASABI_API_LNG_HINST_WAV,WASABI_API_LNG_HINST_WAV_ORIG,uID) +#define WASABI_API_LNGSTRING_BUF_WAV(uID,buf,len) WASABI_API_LNGSTR(WASABI_API_LNG_HINST_WAV,WASABI_API_LNG_HINST_WAV_ORIG,uID,buf,len) + +#define WASABI_API_LNGSTRINGW_WAV(uID) WASABI_API_LNGSTRW(WASABI_API_LNG_HINST_WAV,WASABI_API_LNG_HINST_WAV_ORIG,uID) +#define WASABI_API_LNGSTRINGW_BUF_WAV(uID,buf,len) WASABI_API_LNGSTRW(WASABI_API_LNG_HINST_WAV,WASABI_API_LNG_HINST_WAV_ORIG,uID,buf,len) + +#define WASABI_API_LNGSTRING_DS(uID) WASABI_API_LNGSTR(WASABI_API_LNG_HINST_DS,WASABI_API_LNG_HINST_DS_ORIG,uID) +#define WASABI_API_LNGSTRING_BUF_DS(uID,buf,len) WASABI_API_LNGSTR(WASABI_API_LNG_HINST_DS,WASABI_API_LNG_HINST_DS_ORIG,uID,buf,len) + +#define WASABI_API_LNGSTRINGW_DS(uID) WASABI_API_LNGSTRW(WASABI_API_LNG_HINST_DS,WASABI_API_LNG_HINST_DS_ORIG,uID) +#define WASABI_API_LNGSTRINGW_BUF_DS(uID,buf,len) WASABI_API_LNGSTRW(WASABI_API_LNG_HINST_DS,WASABI_API_LNG_HINST_DS_ORIG,uID,buf,len) + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/config.cpp b/Src/Plugins/Input/in_wmvdrm/config.cpp new file mode 100644 index 00000000..f0f93737 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/config.cpp @@ -0,0 +1,118 @@ +#define WM_DEFINE_CONFIG 1 +#include "config.h" +#include "loadini.h" +#include "main.h" +#include "../nu/Config.h" + +bool config_no_video = false; +extern Nullsoft::Utility::Config wmConfig; +#pragma warning(disable:4800) +#define READ(type, name) config_##name = (type)wmConfig.cfg_int(TEXT("config_") TEXT(#name), default_##name) +#define WRITE(type, name) wmConfig.cfg_int(TEXT("config_") TEXT(#name), default_##name) = (int)config_##name +#define DEFAULT(name) config_##name = default_##name + +void ReadConfig() +{ + READ(bool, lowmemory); + READ(bool, clock); + + READ(bool, video_dedicated_thread); + READ(bool, video_early); + READ(int, video_early_pad); + READ(bool, video_outoforder); + READ(bool, video_catchup); + READ(int, video_jitter); + READ(int, video_drop_threshold); + READ(size_t, video_cache_frames); + READ(bool, video_notifylate); + READ(bool, video_framedropoffset); + + READ(bool, audio_outoforder); + READ(bool, audio_dedicated_thread); + READ(int, audio_early_pad); + READ(bool, audio_early); + READ(size_t, audio_cache_frames); + READ(size_t, audio_num_channels); + +// READ(bool, no_silent); +// READ(bool, untrusted_ok); + + READ(bool, http_metadata); + READ(size_t, buffer_time); + + READ(bool, extra_asx_extensions); + + READ(int, col1); + READ(int, col2); +} + +void WriteConfig() +{ + WRITE(bool, lowmemory); + + WRITE(bool, clock); + + WRITE(bool, video_dedicated_thread); + WRITE(bool, video_early); + WRITE(int, video_early_pad); + WRITE(bool, video_outoforder); + WRITE(bool, video_catchup); + WRITE(int, video_jitter); + WRITE(int, video_drop_threshold); + WRITE(size_t, video_cache_frames); + WRITE(bool, video_notifylate); + WRITE(bool, video_framedropoffset); + + WRITE(bool, audio_outoforder); + WRITE(bool, audio_dedicated_thread); + WRITE(int, audio_early_pad); + WRITE(bool, audio_early); + WRITE(size_t, audio_cache_frames); + WRITE(size_t, audio_num_channels); + +// WRITE(bool, no_silent); +// WRITE(bool, untrusted_ok); + + WRITE(bool, http_metadata); + WRITE(size_t, buffer_time); + + WRITE(bool, extra_asx_extensions); + + WRITE(int, col1); + WRITE(int, col2); +} + +void DefaultConfig() +{ + DEFAULT(http_metadata); +// DEFAULT(no_silent); +// DEFAULT(untrusted_ok); + DEFAULT(buffer_time); + DEFAULT(audio_num_channels); + + DEFAULT(audio_outoforder); + DEFAULT(audio_dedicated_thread); + DEFAULT(audio_early_pad); + DEFAULT(audio_early); + DEFAULT(audio_cache_frames); + + DEFAULT(lowmemory); + + DEFAULT(clock); + + DEFAULT(video_dedicated_thread); + DEFAULT(video_early); + DEFAULT(video_early_pad); + DEFAULT(video_outoforder); + DEFAULT(video_catchup); + DEFAULT(video_jitter); + DEFAULT(video_drop_threshold); + DEFAULT(video_cache_frames); + DEFAULT(video_notifylate); + DEFAULT(video_framedropoffset); + + DEFAULT(extra_asx_extensions); + + DEFAULT(col1); + DEFAULT(col2); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/config.h b/Src/Plugins/Input/in_wmvdrm/config.h new file mode 100644 index 00000000..09920677 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/config.h @@ -0,0 +1,50 @@ +#ifndef NULLSOFT_CONFIGH +#define NULLSOFT_CONFIGH +#include "dsound.h" +#ifdef WM_DEFINE_CONFIG +#define DEFVAL(x) =x +#define CFGEXTERN +#else +#define DEFVAL(x) +#define CFGEXTERN extern +#endif + +#define CFG(type, name, defval) CFGEXTERN type config_##name DEFVAL(defval); CFGEXTERN type default_##name DEFVAL(defval); + +CFG(bool, lowmemory, true); +CFG(bool, clock, true); + +CFG(bool, video_dedicated_thread, true); +CFG(bool, video_early, false); +CFG(int, video_early_pad, 500); +CFG(bool, video_outoforder, true); +CFG(bool, video_catchup, true); +CFG(int, video_jitter, 5); +CFG(int, video_drop_threshold, 15); +CFG(size_t, video_cache_frames, 16); +CFG(bool, video_notifylate, true); +CFG(bool, video_framedropoffset, false); +//CFG(bool, video_flip, false); + +CFG(bool, audio_outoforder, false); +CFG(bool, audio_dedicated_thread, true); +CFG(int, audio_early_pad, 0); +CFG(bool, audio_early, false); +CFG(size_t, audio_cache_frames, 12); +CFG(DWORD, audio_num_channels, DSSPEAKER_5POINT1); + +CFG(bool, no_silent, false); +CFG(bool, untrusted_ok, false); + +CFG(bool, http_metadata, false); +CFG(size_t, buffer_time, 5000); + +CFG(int, col1, -1); +CFG(int, col2, -1); + +extern bool config_no_video; + +CFG(bool, extra_asx_extensions, false); +void ReadConfig(), WriteConfig(), DefaultConfig(); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/directdraw.cpp b/Src/Plugins/Input/in_wmvdrm/directdraw.cpp new file mode 100644 index 00000000..a62de00e --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/directdraw.cpp @@ -0,0 +1,23 @@ +#include "main.h" +#include "directdraw.h" + +HRESULT (WINAPI *_DirectDrawCreate)(GUID FAR *lpGUID, LPDIRECTDRAW FAR *lplpDD, IUnknown FAR *pUnkOuter) = 0; + +HRESULT DDrawCreate(GUID FAR *lpGUID, LPDIRECTDRAW FAR *lplpDD, IUnknown FAR *pUnkOuter) +{ + static int a = 0; + if (!_DirectDrawCreate && !a) + { + a++; + HINSTANCE h = LoadLibrary(L"ddraw.dll"); + if (h) + { + *(void**)&_DirectDrawCreate = (void*)GetProcAddress(h, "DirectDrawCreate"); + } + } + + if (_DirectDrawCreate) + return _DirectDrawCreate(lpGUID, lplpDD, pUnkOuter); + else + return S_OK; // TODO: uhhh no this should be an error :) +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/directdraw.h b/Src/Plugins/Input/in_wmvdrm/directdraw.h new file mode 100644 index 00000000..caa80038 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/directdraw.h @@ -0,0 +1,8 @@ +#ifndef NULLSOFT_DDRAWH +#define NULLSOFT_DDRAWH +#include <ddraw.h> + + extern HRESULT (WINAPI *_DirectDrawCreate)(GUID FAR *lpGUID, LPDIRECTDRAW FAR *lplpDD, IUnknown FAR *pUnkOuter); + + HRESULT DDrawCreate(GUID FAR *lpGUID, LPDIRECTDRAW FAR *lplpDD, IUnknown FAR *pUnkOuter); +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/factory_Handler.cpp b/Src/Plugins/Input/in_wmvdrm/factory_Handler.cpp new file mode 100644 index 00000000..ffb825dc --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/factory_Handler.cpp @@ -0,0 +1,80 @@ +#include "main.h" +#include "api.h" +#include "factory_Handler.h" +#include "PlaylistHandler.h" +#include <api/service/services.h> + +WPLHandler wplHandler; +ASXHandler asxHandler; + + +#define DEFINE_HANDLER_FACTORY(CLASSNAME, className) const char *CLASSNAME ## HandlerFactory::GetServiceName() { return #CLASSNAME ## "Playlist Handler"; }\ + GUID CLASSNAME ## HandlerFactory::GetGUID(){ return className ## HandlerGUID;}\ + void *CLASSNAME ## HandlerFactory::GetInterface(int global_lock){ return &className ## Handler;} + + +DEFINE_HANDLER_FACTORY(WPL, wpl); +DEFINE_HANDLER_FACTORY(ASX, asx); + +/* --------------------------------------------------------------------- */ +FOURCC CommonHandlerFactory::GetServiceType() +{ + return WaSvc::PLAYLISTHANDLER; +} + +int CommonHandlerFactory::SupportNonLockingInterface() +{ + return 1; +} + +int CommonHandlerFactory::ReleaseInterface(void *ifc) +{ + return 1; +} + +const char *CommonHandlerFactory::GetTestString() +{ + return NULL; +} + +int CommonHandlerFactory::ServiceNotify(int msg, int param1, int param2) +{ + return 1; +} + + + + +#undef CBCLASS +#define CBCLASS WPLHandlerFactory +START_DISPATCH; +CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName) +CB(WASERVICEFACTORY_GETGUID, GetGUID) +CB(WASERVICEFACTORY_GETINTERFACE, GetInterface) +#undef CBCLASS +#define CBCLASS CommonHandlerFactory +CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType) +CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface) +CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface) +CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString) +CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify) +END_DISPATCH; + +#undef CBCLASS +#define CBCLASS ASXHandlerFactory +START_DISPATCH; +CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName) +CB(WASERVICEFACTORY_GETGUID, GetGUID) +CB(WASERVICEFACTORY_GETINTERFACE, GetInterface) +#undef CBCLASS +#define CBCLASS CommonHandlerFactory +CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType) +CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface) +CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface) +CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString) +CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify) +END_DISPATCH; + + + + diff --git a/Src/Plugins/Input/in_wmvdrm/factory_Handler.h b/Src/Plugins/Input/in_wmvdrm/factory_Handler.h new file mode 100644 index 00000000..16215543 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/factory_Handler.h @@ -0,0 +1,30 @@ +#ifndef NULLSOFT_FACTORY_HANDLER_H +#define NULLSOFT_FACTORY_HANDLER_H + +#include <api/service/waservicefactory.h> +#include <api/service/services.h> + +class CommonHandlerFactory : public waServiceFactory +{ +public: + + FOURCC GetServiceType(); + int SupportNonLockingInterface(); + int ReleaseInterface(void *ifc); + const char *GetTestString(); + int ServiceNotify(int msg, int param1, int param2); + +}; + +#define DECLARE_HANDLER_FACTORY(CLASSNAME) class CLASSNAME : public CommonHandlerFactory {\ +public:\ + const char *GetServiceName();\ + GUID GetGUID();\ + void *GetInterface(int global_lock);\ +protected:\ + RECVS_DISPATCH;} + +DECLARE_HANDLER_FACTORY(ASXHandlerFactory); +DECLARE_HANDLER_FACTORY(WPLHandlerFactory); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/in_wm.rc b/Src/Plugins/Input/in_wmvdrm/in_wm.rc new file mode 100644 index 00000000..1b57130b --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/in_wm.rc @@ -0,0 +1,331 @@ +// 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_CONFIG DIALOGEX 0, 0, 344, 142 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Windows Media Decoder Settings" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "General",IDC_STATIC,4,4,156,59 + CONTROL "Retrieve metadata for HTTP streams",IDC_HTTPMETA,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,16,133,10 + LTEXT "Streaming prebuffer: ",IDC_STATIC,10,30,70,8 + EDITTEXT IDC_BUFFER_TIME,81,28,41,12,ES_AUTOHSCROLL + LTEXT "ms",IDC_STATIC,126,30,10,8 + LTEXT "Speaker Configuration:",IDC_STATIC,10,43,75,12,SS_CENTERIMAGE + COMBOBOX IDC_AUDIO_SPEAKER_COUNT,88,43,66,83,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Advanced Settings",IDC_STATIC,4,67,156,52 + LTEXT "Warning: For advanced users only!",IDC_STATIC,10,83,128,10 + PUSHBUTTON "Advanced ...",IDC_ADVANCED,52,98,50,13 +/* GROUPBOX "Protected Media",IDC_STATIC,4,80,156,42 + CONTROL "Try to acquire licenses silently",IDC_SILENT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,93,111,10 + CONTROL "Always accept untrusted certificates",IDC_UNTRUSTED, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,106,133,10 +*/ + GROUPBOX "Filetypes",IDC_STATIC,164,4,176,134 + CONTROL "",IDC_TYPELIST,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,172,17,161,85 + PUSHBUTTON "Add ...",IDC_ADDTYPE,172,107,51,13 + PUSHBUTTON "Edit ...",IDC_EDITTYPE,227,107,50,13 + PUSHBUTTON "Remove",IDC_REMOVETYPE,281,107,52,13 + CONTROL "Enable WAX/WMX/WVX playlist extensions",IDC_EXTRA_ASX, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,172,124,151,10 + PUSHBUTTON "Save",IDOK,4,125,46,13 + PUSHBUTTON "Cancel",IDCANCEL,54,125,48,13 + PUSHBUTTON "Defaults",IDC_DEFAULTTYPE,110,125,50,13 +END + +IDD_ADDTYPE DIALOGEX 0, 0, 168, 124 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Add File Type" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Type",IDC_STATIC_TYPE,4,9,36,8,NOT WS_GROUP + EDITTEXT IDC_TYPE,44,7,120,12,ES_AUTOHSCROLL + LTEXT "Description",IDC_STATIC,4,25,36,8,NOT WS_GROUP + EDITTEXT IDC_DESCRIPTION,44,23,120,12,ES_AUTOHSCROLL + GROUPBOX "Extension Type",IDC_STATIC,4,40,160,30 + CONTROL "File Extension",IDC_FILEEXTENSION,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,11,53,60,10 + CONTROL "Protocol",IDC_PROTOCOL,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,84,53,42,10 + GROUPBOX "Media Type",IDC_STATIC,4,73,160,30 + CONTROL "Audio Only",IDC_TYPE_AUDIO,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,11,86,51,10 + CONTROL "Audio/Video",IDC_TYPE_VIDEO,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,84,86,54,10 + DEFPUSHBUTTON "OK",IDOK,60,107,50,13 + PUSHBUTTON "Cancel",IDCANCEL,114,107,50,13 +END + +IDD_ADVANCED DIALOGEX 0, 0, 190, 214 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Windows Media Decoder Advanced Settings" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Audio",IDC_STATIC,4,4,182,60 + CONTROL "Dedicated Delivery Thread",IDC_AUDIO_THREAD,"Button",BS_AUTOCHECKBOX | NOT WS_VISIBLE | WS_TABSTOP,204,15,101,10 + CONTROL "Deliver samples early",IDC_AUDIO_EARLY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,18,80,10 + LTEXT "and add",IDC_STATIC,96,18,30,10,SS_CENTERIMAGE + EDITTEXT IDC_AUDIO_EARLYPAD,126,17,40,12,ES_AUTOHSCROLL + LTEXT "ms",IDC_STATIC,168,18,11,10,SS_CENTERIMAGE + CONTROL "Allow out-of-order frames",IDC_AUDIO_OUTOFORDER,"Button",BS_AUTOCHECKBOX | NOT WS_VISIBLE | WS_TABSTOP,204,25,99,10 + LTEXT "cache",IDC_STATIC,12,32,21,12,SS_CENTERIMAGE + EDITTEXT IDC_AUDIO_CACHE_FRAMES,37,32,40,12,ES_AUTOHSCROLL + LTEXT "frames",IDC_STATIC,81,32,30,12,SS_CENTERIMAGE + CONTROL "Drop audio frames instead of video frames",IDC_AUDIO_DROP, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,48,152,10 + GROUPBOX "General",IDC_STATIC,4,164,182,28 + CONTROL "Low Memory",IDC_LOWMEMORY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,175,56,10 + CONTROL "Real Time",IDC_REALTIME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,108,175,47,10 + GROUPBOX "Video",IDC_STATIC,4,68,182,93 + CONTROL "Dedicated Delivery Thread",IDC_VIDEO_THREAD,"Button",BS_AUTOCHECKBOX | NOT WS_VISIBLE | WS_TABSTOP,205,113,101,10 + CONTROL "Deliver samples early",IDC_VIDEO_EARLY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,81,80,10 + LTEXT "by",IDC_STATIC,96,81,10,10,SS_CENTERIMAGE + EDITTEXT IDC_VIDEO_EARLYPAD,108,81,40,12,ES_AUTOHSCROLL + LTEXT "ms",IDC_STATIC,150,81,11,10,SS_CENTERIMAGE + CONTROL "Allow out-of-order frames",IDC_VIDEO_OUTOFORDER,"Button",BS_AUTOCHECKBOX | NOT WS_VISIBLE | WS_TABSTOP,205,126,99,10 + LTEXT "Cache",IDC_STATIC,14,97,21,12,SS_CENTERIMAGE + EDITTEXT IDC_VIDEO_CACHE_FRAMES,42,97,40,12,ES_AUTOHSCROLL + LTEXT "frames",IDC_STATIC,88,97,30,12,SS_CENTERIMAGE + LTEXT "Jitter:",IDC_STATIC,14,113,20,12,SS_CENTERIMAGE + EDITTEXT IDC_VIDEO_JITTER,42,113,40,12,ES_AUTOHSCROLL + LTEXT "ms",IDC_STATIC,88,113,10,12,SS_CENTERIMAGE + LTEXT "Frame drop threshold:",IDC_STATIC,12,131,72,10,SS_CENTERIMAGE + EDITTEXT IDC_VIDEO_DROP_THRESHOLD,88,129,40,12,ES_AUTOHSCROLL + LTEXT "ms",IDC_STATIC,132,131,10,10,SS_CENTERIMAGE + CONTROL "Report dropped frames to decoder",IDC_VIDEO_NOTIFY, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,145,127,10 + DEFPUSHBUTTON "OK",IDOK,82,197,50,13 + PUSHBUTTON "Cancel",IDCANCEL,136,197,50,13 +END + +IDD_INFO DIALOGEX 0, 0, 341, 164 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Advanced",-1,0,0,341,164 + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,6,13,327,143 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_CONFIG, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 340 + TOPMARGIN, 4 + BOTTOMMARGIN, 138 + END + + IDD_ADDTYPE, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 164 + TOPMARGIN, 7 + BOTTOMMARGIN, 120 + END + + IDD_ADVANCED, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 186 + TOPMARGIN, 4 + BOTTOMMARGIN, 210 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_WINDOWS_MEDIA_DECODER "Nullsoft Windows Media Decoder v%s" + 65535 "{C5B78F09-3222-4a64-AA98-F1ABC5A9E355}" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_WINDOWS_MEDIA_DECODER_OLD "Nullsoft Windows Media Decoder" + IDS_REALTIME "Realtime" + IDS_CONNECTING "Connecting" + IDS_LOCATING "Locating" + IDS_ACCESS_DENIED "Access Denied" + IDS_STEREO "Stereo" + IDS_QUADROPHONIC "Quadrophonic" + IDS_SURROUND "Surround" + IDS_5_1 "5.1" + IDS_7_1 "7.1" + IDS_EXT "Ext" + IDS_DESCRIPTION "Description" + IDS_PROTOCOL "Protocol" + IDS_EXTENSION "Extension" +END + +STRINGTABLE +BEGIN + IDS_EDIT_FILE_TYPE "Edit File Type" + IDS_WMA_AUDIO_FILE "Windows Media Audio File (*.WMA)" + IDS_WMA_VIDEO_FILE "Windows Media Video File (*.WMV)" + IDS_ASF_STREAM "Advanced Streaming Format (*.ASF)" + IDS_WINDOWS_MEDIA_STREAM "Windows Media Stream" + IDS_UNKNOWN_ERROR "unknown error: %x" + IDS_ATTRIBUTE "Attribute" + IDS_VALUE "Value" + IDS_CLOSE "Close" + IDS_UNABLE_TO_WRITE_TO_FILE_DOES_FILE_EXIST + "Unable to write to file\nDoes the file still exist?" + IDS_UNABLE_TO_WRITE_TO_FILE "Unable to write to file" + IDS_UNABLE_TO_WRITE_TO_FILE_MAY_NOT_HAVE_ACCESS + "Unable to write to file\nYou may not have access to modify this file." + IDS_SAVE_FAILED "Save failed" + IDS_TRUE "True" + IDS_FALSE "False" + IDS_UNKNOWN "Unknown" +END + +STRINGTABLE +BEGIN + IDS_WINDOWS_MEDIA_XXX "Windows Media: %dx%d %c%c%c%c" + IDS_WINDOWS_MEDIA_PROTECTED_CONTENT_NEED_LICENSE + "Windows Media Protected Content\n\nIn order to play this file, you must acquire a license.\nA website will launch to perform this process.\n\nProceed?" + IDS_LICENSE_REQUIRED "License required" + IDS_BEGINNING_UPDATE "Beginning Update" + IDS_UPDATE_FAILED "Update Failed" + IDS_UPDATE_CANCELLED "Update Cancelled" + IDS_IDLE "Idle" + IDS_REQUESTING "Requesting" + IDS_RECEIVING_DATA "Receiving Data" + IDS_COMPLETED "Completed" + IDS_INSTALLING "Installing" + IDS_FILE_PROTECTED_CANNOT_PLAY_IN_WINAMP + "This file is protected, but cannot be played in Winamp." + IDS_LICENSE_ACQUIRED "License Acquired" + IDS_WINDOWS_MEDIA_PROTECTED_CONTENT_UNTRUSTED_SOURCE + "Windows Media Protected Content\n\nThe license for this license is from an untrusted source.\n\nProceed?" + IDS_UNTRUSTED_LICENSE "Untrusted License" + IDS_WINDOWS_MEDIA_PROTECTED_CONTENT_TAMPERED_LICENSE + "Windows Media Protected Content\n\nThe license for this license appeared to have been tampered.\nIt is recommended that you answer ""No"".\n\nProceed?" +END + +STRINGTABLE +BEGIN + IDS_TAMPERED_LICENSE "Tampered License" + IDS_WINDOWS_MEDIA_PROTECTED_CONTENT_SECURITY_UPDATE + "Windows Media Protected Content\n\nIn order to play this file, a security update is required.\n\nProceed?" + IDS_SECURITY_UPDATE_REQUIRED "Security Update Required" + IDS_UPDATING "Updating" + IDS_ENCRYPTED_IN_OP_LEVEL_HIGHER_THAN_SUPPORTED + "This file is encrypted with an Output Protection Level higher than supported in Winamp." + IDS_WINDOWS_MEDIA_PLAYBACK_FAILURE "Windows Media Playback Failure" + IDS_UPDATE_REQUIRED "Update required" + IDS_ACQUIRING_LICENSE "Acquiring License" + IDS_DRM_LICENSE_EXPIRED_VISIT_WINAMP_COM + "DRM License has expired! Please visit Winamp.com" + IDS_ERROR "Error" + IDS_BUFFERING "Buffering" + IDS_UNKNOWN_ERROR_WAV "Unknown Error." + IDS_CODEC "Codec" + IDS_DURATION "Duration" + IDS_BITRATE "Bitrate" + IDS_FILESIZE "File Size" +END + +STRINGTABLE +BEGIN + IDS_YES "Yes" + IDS_NO "No" + IDS_WMVER "WM Version" + IDS_SEEKABLE "Seekable" + IDS_STRIDABLE "Stridable" + IDS_BROADCAST "Broadcast" + IDS_PROTECTED "Protected" + IDS_TRUSTED "Trusted" + IDS_CONTAINS "Contains" + IDS_NAME "Name" + IDS_KBPS "kbps" + IDS_ABOUT_TEXT "%s\n© 2005-2023 Winamp SA\nWritten by: Ben Allison\nBuild date: %s\n\nWindows Media is a trademark\nof Microsoft Corporation." +END + +STRINGTABLE +BEGIN + IDS_AUDIO "Audio" + IDS_VIDEO "Video" + IDS_IMAGE "Image" + IDS_SCRIPT "Script" + IDS_NONE "None" + IDS_WINDOWS_MEDIA_PLAYLIST "Windows Media Playlist" + IDS_ASX_PLAYLIST "Advanced Streaming Redirector (ASX)" +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_wmvdrm/in_wmvdrm.sln b/Src/Plugins/Input/in_wmvdrm/in_wmvdrm.sln new file mode 100644 index 00000000..af0ece5c --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/in_wmvdrm.sln @@ -0,0 +1,30 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29613.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_wm", "in_wmvdrm.vcxproj", "{9362E6C2-EFCC-4104-8671-78222E0A5FCC}" +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 + {9362E6C2-EFCC-4104-8671-78222E0A5FCC}.Debug|Win32.ActiveCfg = Debug|Win32 + {9362E6C2-EFCC-4104-8671-78222E0A5FCC}.Debug|Win32.Build.0 = Debug|Win32 + {9362E6C2-EFCC-4104-8671-78222E0A5FCC}.Debug|x64.ActiveCfg = Debug|x64 + {9362E6C2-EFCC-4104-8671-78222E0A5FCC}.Debug|x64.Build.0 = Debug|x64 + {9362E6C2-EFCC-4104-8671-78222E0A5FCC}.Release|Win32.ActiveCfg = Release|Win32 + {9362E6C2-EFCC-4104-8671-78222E0A5FCC}.Release|Win32.Build.0 = Release|Win32 + {9362E6C2-EFCC-4104-8671-78222E0A5FCC}.Release|x64.ActiveCfg = Release|x64 + {9362E6C2-EFCC-4104-8671-78222E0A5FCC}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3A0E00EA-C715-4273-A005-BCCCC6158254} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_wmvdrm/in_wmvdrm.vcxproj b/Src/Plugins/Input/in_wmvdrm/in_wmvdrm.vcxproj new file mode 100644 index 00000000..e4d8d491 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/in_wmvdrm.vcxproj @@ -0,0 +1,361 @@ +<?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"> + <ProjectName>in_wm</ProjectName> + <ProjectGuid>{9362E6C2-EFCC-4104-8671-78222E0A5FCC}</ProjectGuid> + <RootNamespace>in_wmvdrm</RootNamespace> + <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" 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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|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> + <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> + <GenerateManifest>false</GenerateManifest> + <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir> + <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir> + <IncludePath>$(IncludePath)</IncludePath> + <LibraryPath>$(LibraryPath)</LibraryPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + <GenerateManifest>false</GenerateManifest> + <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;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_USRDLL;IN_WMVDRM_EXPORTS;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;NO_DRM;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalOptions>/ignore:4253 /ignore:4254 %(AdditionalOptions)</AdditionalOptions> + <AdditionalDependencies>wmvcore.lib;comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <TargetMachine>MachineX86</TargetMachine> + <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;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_USRDLL;IN_WMVDRM_EXPORTS;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;NO_DRM;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4244;4267;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalOptions>/ignore:4253 /ignore:4254 %(AdditionalOptions)</AdditionalOptions> + <AdditionalDependencies>wmvcore.lib;comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <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\ +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> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_USRDLL;IN_WMVDRM_EXPORTS;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;NO_DRM;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <ObjectFileName>$(IntDir)</ObjectFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4005;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalOptions>/ignore:4253 /ignore:4254 %(AdditionalOptions)</AdditionalOptions> + <AdditionalDependencies>wmvcore.lib;comctl32.lib;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> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_USRDLL;IN_WMVDRM_EXPORTS;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;NO_DRM;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <ObjectFileName>$(IntDir)</ObjectFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4005;4244;4267;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalOptions>/ignore:4253 /ignore:4254 %(AdditionalOptions)</AdditionalOptions> + <AdditionalDependencies>wmvcore.lib;comctl32.lib;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="..\..\..\nu\CCVersion.cpp" /> + <ClCompile Include="..\..\..\nu\listview.cpp" /> + <ClCompile Include="..\..\..\Winamp\strutil.cpp" /> + <ClCompile Include="AlbumArt.cpp" /> + <ClCompile Include="api.cpp" /> + <ClCompile Include="ASXLoader.cpp" /> + <ClCompile Include="AudioFormat.cpp" /> + <ClCompile Include="AudioLayer.cpp" /> + <ClCompile Include="AudioThread.cpp" /> + <ClCompile Include="BufferLayer.cpp" /> + <ClCompile Include="ClockLayer.cpp" /> + <ClCompile Include="config.cpp"> + <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">WM_DEFINE_CONFIG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">WM_DEFINE_CONFIG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + </ClCompile> + <ClCompile Include="ConfigDialog.cpp" /> + <ClCompile Include="directdraw.cpp" /> + <ClCompile Include="ExtendedFileInfo.cpp" /> + <ClCompile Include="ExtendedRead.cpp" /> + <ClCompile Include="factory_Handler.cpp" /> + <ClCompile Include="FileInfoDialog.cpp" /> + <ClCompile Include="FileTypes.cpp" /> + <ClCompile Include="GainLayer.cpp" /> + <ClCompile Include="loadini.cpp" /> + <ClCompile Include="main.cpp" /> + <ClCompile Include="MediaThread.cpp" /> + <ClCompile Include="MetaTag.cpp" /> + <ClCompile Include="MetaTagFactory.cpp" /> + <ClCompile Include="output\OutPlugin.cpp" /> + <ClCompile Include="PlaylistHandler.cpp" /> + <ClCompile Include="RawReader.cpp" /> + <ClCompile Include="SeekLayer.cpp" /> + <ClCompile Include="StatusHook.cpp" /> + <ClCompile Include="TagAlias.cpp" /> + <ClCompile Include="util.cpp" /> + <ClCompile Include="VideoDataConverter.cpp" /> + <ClCompile Include="VideoLayer.cpp" /> + <ClCompile Include="VideoOutputChildDDraw.cpp" /> + <ClCompile Include="VideoThread.cpp" /> + <ClCompile Include="vidutils.cpp" /> + <ClCompile Include="vid_ddraw.cpp" /> + <ClCompile Include="vid_overlay.cpp" /> + <ClCompile Include="WaitLayer.cpp" /> + <ClCompile Include="WinampInterface.cpp" /> + <ClCompile Include="WMCallback.cpp" /> + <ClCompile Include="WMDRMModule.cpp" /> + <ClCompile Include="WMHandler.cpp" /> + <ClCompile Include="WMInformation.cpp" /> + <ClCompile Include="WMPlaylist.cpp" /> + <ClCompile Include="WPLLoader.cpp" /> + <ClCompile Include="XMLString.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\nu\listview.h" /> + <ClInclude Include="..\..\Output\out_ds\res_wa2\resource.h" /> + <ClInclude Include="..\..\..\Winamp\strutil.h" /> + <ClInclude Include="AlbumArt.h" /> + <ClInclude Include="api.h" /> + <ClInclude Include="ASXLoader.h" /> + <ClInclude Include="AudioFormat.h" /> + <ClInclude Include="AudioLayer.h" /> + <ClInclude Include="AudioThread.h" /> + <ClInclude Include="AutoChar.h" /> + <ClInclude Include="AutoWide.h" /> + <ClInclude Include="BufferLayer.h" /> + <ClInclude Include="ClockLayer.h" /> + <ClInclude Include="config.h" /> + <ClInclude Include="ConfigDialog.h" /> + <ClInclude Include="directdraw.h" /> + <ClInclude Include="ExtendedRead.h" /> + <ClInclude Include="factory_Handler.h" /> + <ClInclude Include="FileInfoDialog.h" /> + <ClInclude Include="FileTypes.h" /> + <ClInclude Include="GainLayer.h" /> + <ClInclude Include="loadini.h" /> + <ClInclude Include="Main.h" /> + <ClInclude Include="MediaThread.h" /> + <ClInclude Include="MetaTag.h" /> + <ClInclude Include="MetaTagFactory.h" /> + <ClInclude Include="OutputStream.h" /> + <ClInclude Include="output\AudioOut.h" /> + <ClInclude Include="output\OutPlugin.h" /> + <ClInclude Include="PlaylistHandler.h" /> + <ClInclude Include="RawReader.h" /> + <ClInclude Include="Remaining.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="SeekLayer.h" /> + <ClInclude Include="StatusHook.h" /> + <ClInclude Include="TagAlias.h" /> + <ClInclude Include="util.h" /> + <ClInclude Include="VideoDataConverter.h" /> + <ClInclude Include="VideoLayer.h" /> + <ClInclude Include="VideoOutputChildDDraw.h" /> + <ClInclude Include="VideoThread.h" /> + <ClInclude Include="vidutils.h" /> + <ClInclude Include="vid_ddraw.h" /> + <ClInclude Include="vid_overlay.h" /> + <ClInclude Include="WaitLayer.h" /> + <ClInclude Include="WinampInterface.h" /> + <ClInclude Include="WMCallback.h" /> + <ClInclude Include="WMDRMModule.h" /> + <ClInclude Include="WMHandler.h" /> + <ClInclude Include="WMInformation.h" /> + <ClInclude Include="WMPlaylist.h" /> + <ClInclude Include="WPLLoader.h" /> + <ClInclude Include="XMLString.h" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_wm.rc" /> + </ItemGroup> + <ItemGroup> + <Text Include="DESIGN.txt" /> + <Text Include="TODO.txt" /> + </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_wmvdrm/in_wmvdrm.vcxproj.filters b/Src/Plugins/Input/in_wmvdrm/in_wmvdrm.vcxproj.filters new file mode 100644 index 00000000..98ac6298 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/in_wmvdrm.vcxproj.filters @@ -0,0 +1,330 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="AlbumArt.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="api.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ASXLoader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="AudioFormat.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="AudioLayer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="AudioThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="BufferLayer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ClockLayer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="config.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ConfigDialog.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="directdraw.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedFileInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedRead.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="factory_Handler.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="FileInfoDialog.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="FileTypes.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="GainLayer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="loadini.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="MediaThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="MetaTag.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="MetaTagFactory.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="output\OutPlugin.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="PlaylistHandler.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="RawReader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="SeekLayer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="StatusHook.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="TagAlias.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="util.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="vid_ddraw.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="vid_overlay.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="VideoDataConverter.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="VideoLayer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="VideoOutputChildDDraw.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="VideoThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="vidutils.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="WaitLayer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="WinampInterface.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="WMCallback.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="WMDRMModule.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="WMHandler.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="WMInformation.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="WMPlaylist.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="WPLLoader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="XMLString.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\CCVersion.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\listview.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\Winamp\strutil.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="AlbumArt.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="api.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ASXLoader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="AudioFormat.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="AudioLayer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="output\AudioOut.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="AudioThread.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="AutoChar.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="AutoWide.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="BufferLayer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ClockLayer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="config.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ConfigDialog.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="directdraw.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ExtendedRead.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="factory_Handler.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FileInfoDialog.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="FileTypes.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="GainLayer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="loadini.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Main.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="MediaThread.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="MetaTagFactory.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="MetaTag.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="output\OutPlugin.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="OutputStream.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="PlaylistHandler.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="RawReader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Remaining.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="SeekLayer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="StatusHook.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="TagAlias.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="util.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="vid_ddraw.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="vid_overlay.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="VideoDataConverter.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="VideoLayer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="VideoOutputChildDDraw.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="VideoThread.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="vidutils.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="WaitLayer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="WinampInterface.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="WMCallback.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="WMDRMModule.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="WMHandler.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="WMInformation.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="WMPlaylist.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="WPLLoader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="XMLString.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\listview.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\Output\out_ds\res_wa2\resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\Winamp\strutil.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Text Include="DESIGN.txt" /> + <Text Include="TODO.txt" /> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{c26aaf40-61a0-4d01-b985-d9da875ad265}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{e22165b9-a669-4451-bca6-7d77a42f2d99}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{375a8628-880b-412e-b643-d12761afdc4b}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_wm.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/loadini.cpp b/Src/Plugins/Input/in_wmvdrm/loadini.cpp new file mode 100644 index 00000000..482abeb7 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/loadini.cpp @@ -0,0 +1,12 @@ +#include "main.h" +#include "loadini.h" +#include "AutoWide.h" +#include "../Winamp/wa_ipc.h" +extern wchar_t INI_FILE[MAX_PATH]; +void IniFile(HWND hMainWindow) +{ + if (!INI_FILE[0]) + { + lstrcpyn(INI_FILE, (wchar_t *)SendMessage(hMainWindow, WM_WA_IPC, 0, IPC_GETINIFILEW), MAX_PATH); + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/loadini.h b/Src/Plugins/Input/in_wmvdrm/loadini.h new file mode 100644 index 00000000..4d57eb6b --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/loadini.h @@ -0,0 +1,8 @@ +#ifndef NULLSOFT_LOADINIH +#define NULLSOFT_LOADINIH + +#include <windows.h> +extern wchar_t INI_FILE[MAX_PATH]; +void IniFile(HWND hMainWindow); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/main.cpp b/Src/Plugins/Input/in_wmvdrm/main.cpp new file mode 100644 index 00000000..af26d4e0 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/main.cpp @@ -0,0 +1,136 @@ +#include "Main.h" +#include "api.h" +#include "loadini.h" +#include "FileTypes.h" +#include <commctrl.h> +#include "../nu/Config.h" +#include "../nu/CCVersion.h" +#include "resource.h" + +wchar_t INI_FILE[MAX_PATH] = L""; +IDispatch *winampExternal = 0; +Nullsoft::Utility::Config wmConfig; +WMDRM mod; +HINSTANCE WASABI_API_LNG_HINST_WAV = 0; +HINSTANCE WASABI_API_LNG_HINST_DS = 0; + +int Init() +{ + if (!IsWindow(plugin.hMainWindow)) + return IN_INIT_FAILURE; + + if (!LoadWasabi()) + return IN_INIT_FAILURE; + + plugin.UsesOutputPlug |= 8; + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(plugin.hDllInstance,InWmLangGUID); + + static wchar_t szDescription[256]; + swprintf(szDescription,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_WINDOWS_MEDIA_DECODER),WMDRM_VERSION); + plugin.description = (char*)szDescription; + + IniFile(plugin.hMainWindow); + wmConfig.SetFile(INI_FILE, L"in_wm"); + ReadConfig(); + fileTypes.ReadConfig(); + if (NULL == winampExternal) + { + winampExternal = (IDispatch *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GET_DISPATCH_OBJECT); // ask for winamp's + if (winampExternal == (IDispatch *)1) + winampExternal = 0; + } + + mod.Init(); + return IN_INIT_SUCCESS; +} + +void Quit() +{ + mod.Quit(); + UnloadWasabi(); + fileTypes.types.clear(); + + if (NULL != winampExternal) + { + winampExternal->Release(); + winampExternal = NULL; + } +} + +void Config(HWND parent) +{ + mod.Config(parent); + fileTypes.SaveConfig(); + WriteConfig(); +} + +void About(HWND parent) +{ + wchar_t message[1024] = {0}, text[1024] = {0}; + WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_WINDOWS_MEDIA_DECODER_OLD,text,1024); + wsprintfW(message, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), + plugin.description, TEXT(__DATE__)); + DoAboutMessageBox(parent,text,message); +} + +void GetFileInfo(const in_char *file, wchar_t *title, int *length_in_ms) { mod.GetFileInfo(file, title, length_in_ms); } +int InfoDialog(const in_char *file, HWND parent) { return mod.InfoBox(file, parent); } +int IsOurFile(const in_char *fn) { return mod.IsOurFile(fn); } +int Play(const in_char *fn) {return mod.Play(fn); } +void Pause() { mod.Pause(); } +void Resume() { mod.UnPause(); } +int IsPaused() { return mod.IsPaused(); } +void Stop() { mod.Stop(); } +int GetLength() { return mod.GetLength(); } +int GetOutputTime() { return mod.GetOutputTime(); } +void SetOutputTime(int time_in_ms) { return mod.SetOutputTime(time_in_ms); } +void SetVolume(int volume) { mod.SetVolume(volume); } +void SetPan(int pan) { mod.SetPan(pan); } +void EQSet(int on, char data[10], int preamp) { mod.EQSet(on, data, preamp); } + +In_Module plugin = +{ + IN_VER_RET, // defined in IN2.H + "nullsoft(in_wm.dll)", + 0, // hMainWindow (filled in by winamp) + 0, // hDllInstance (filled in by winamp) + 0, // this is a double-null limited list. "EXT\0Description\0EXT\0Description\0" etc. + 0, // is_seekable + 1, // uses output plug-in system + Config, + About, + Init, + Quit, + GetFileInfo, + InfoDialog, + IsOurFile, + Play, + Pause, + Resume, + IsPaused, + Stop, + + GetLength, + GetOutputTime, + SetOutputTime, + + SetVolume, + SetPan, + + 0,0,0,0,0,0,0,0,0, // visualization calls filled in by winamp + + 0,0, // dsp calls filled in by winamp + + EQSet, + + NULL, // setinfo call filled in by winamp + + 0, // out_mod filled in by winamp +}; + +extern "C" __declspec( dllexport ) In_Module * winampGetInModule2() +{ + return &plugin; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/output/AudioOut.h b/Src/Plugins/Input/in_wmvdrm/output/AudioOut.h new file mode 100644 index 00000000..90d36202 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/output/AudioOut.h @@ -0,0 +1,38 @@ +#ifndef NULLSOFT_AUDIOOUTH +#define NULLSOFT_AUDIOOUTH + +#include <windows.h> +#include "../../../../Winamp/out.h" + +enum InitState +{ + StatusNone = 0, + StatusInit, + StatusQuit +}; + +class AudioOut +{ +public: + AudioOut() : dllInstance(0), winampWnd(NULL) {} + virtual void Init() = 0; + virtual void Quit() = 0; + virtual int CanWrite() = 0; + virtual int GetWrittenTime() = 0; + virtual int IsPlaying() = 0; + virtual int Open(int samplerate, int numchannels, int bitspersamp, int bufferlenms, int prebufferms) = 0; + virtual void Close() = 0; + virtual int Write(char *buf, int len) = 0; + virtual void Flush(int t) = 0; + virtual void SetVolume(int _volume) = 0; + virtual int Pause(int new_state) = 0; + virtual int GetOutputTime() = 0; + virtual void SetPan(int _pan) = 0; + virtual void About(HWND p) = 0; + virtual void Config(HWND w) = 0; + + HINSTANCE dllInstance; + HWND winampWnd; +}; + +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/output/OutPlugin.cpp b/Src/Plugins/Input/in_wmvdrm/output/OutPlugin.cpp new file mode 100644 index 00000000..3bf43ea6 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/output/OutPlugin.cpp @@ -0,0 +1,70 @@ +#include "OutPlugin.h" +#include "../Winamp/In2.h" + +#include "WMDRMModule.h" +extern In_Module plugin; +OutPlugin pluginOut; + +OutPlugin::OutPlugin() +{} + +void OutPlugin::Init() +{ + plugin.outMod->Init(); +} +void OutPlugin::Quit() +{ + plugin.outMod->Quit(); +} +int OutPlugin::CanWrite() +{ + return plugin.outMod->CanWrite(); +} +int OutPlugin::GetWrittenTime() +{ + return plugin.outMod->GetWrittenTime(); +} +int OutPlugin::IsPlaying() +{ + return plugin.outMod->IsPlaying(); +} +int OutPlugin::Open(int samplerate, int numchannels, int bitspersamp, int bufferlenms, int prebufferms) +{ + return plugin.outMod->Open(samplerate, numchannels, bitspersamp, bufferlenms, prebufferms); +} +void OutPlugin::Close() +{ + plugin.outMod->Close(); +} +int OutPlugin::Write(char *buf, int len) +{ + return plugin.outMod->Write(buf, len); +} +void OutPlugin::Flush(int t) +{ + plugin.outMod->Flush(t); +} +void OutPlugin::SetVolume(int _volume) +{ + plugin.outMod->SetVolume(_volume); +} +int OutPlugin::Pause(int new_state) +{ + return plugin.outMod->Pause(new_state); +} +int OutPlugin::GetOutputTime() +{ + return plugin.outMod->GetOutputTime(); +} +void OutPlugin::SetPan(int _pan) +{ + plugin.outMod->SetPan(_pan); +} +void OutPlugin::About(HWND p) +{ + plugin.outMod->About(p); +} +void OutPlugin::Config(HWND w) +{ + plugin.outMod->Config(w); +} diff --git a/Src/Plugins/Input/in_wmvdrm/output/OutPlugin.h b/Src/Plugins/Input/in_wmvdrm/output/OutPlugin.h new file mode 100644 index 00000000..10d0ead7 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/output/OutPlugin.h @@ -0,0 +1,28 @@ +#ifndef NULLSOFT_OUTPLUGINH +#define NULLSOFT_OUTPLUGINH + +#include "AudioOut.h" + +class OutPlugin : public AudioOut +{ +public: + OutPlugin(); + void Init(); + void Quit(); + int CanWrite(); + int GetWrittenTime(); + int IsPlaying(); + int Open(int samplerate, int numchannels, int bitspersamp, int bufferlenms, int prebufferms); + void Close(); + int Write(char *buf, int len); + void Flush(int t); + void SetVolume(int _volume); + int Pause(int new_state); + int GetOutputTime(); + void SetPan(int _pan); + void About(HWND p); + void Config(HWND w); +}; + +extern OutPlugin pluginOut; +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/res_wav/resource.h b/Src/Plugins/Input/in_wmvdrm/res_wav/resource.h new file mode 100644 index 00000000..479d2ebf --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/res_wav/resource.h @@ -0,0 +1,60 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by out_wave.rc +// +#define IDS_NULLSOFT_WAVEOUT_OLD 0 +#define IDS_NOT_ACTIVE 1 +#define IDS_UNKNOWN_MMSYSTEM_ERROR 2 +#define IDC_RESET 3 +#define IDS_UNSUPPORTED_PCM_FORMAT 3 +#define IDS_ANOTHER_PROGRAM_IS_USING_THE_SOUNDCARD 4 +#define IDS_NO_SOUND_DEVICES_FOUND 5 +#define IDS_INTERNAL_DRIVER_ERROR 6 +#define IDS_REINSTALL_SOUNDCARD_DRIVERS 7 +#define IDS_ERROR_CODE_WINDOWS_ERROR_MESSAGE 8 +#define IDS_ERROR_CODE 9 +#define IDS_DATA_FORMAT 10 +#define IDS_BUFFER_STATUS 11 +#define IDS_LATENCY 12 +#define IDS_ABOUT 13 +#define IDS_PREFS_TITLE 15 +#define IDS_ERROR 16 +#define IDS_WAVE_U_MS 17 +#define IDS_ABOUT_STRING 18 +#define IDS_ABOUT_TEXT 18 +#define IDD_CONFIG 300 +#define IDD_WAVE_CONFIG 300 +#define IDC_GAPLESS 1000 +#define IDC_BUF_SIZE 1001 +#define IDC_SPIN1 1002 +#define IDC_PRIMARY 1003 +#define IDC_PREBUF_SIZE 1003 +#define IDC_DEV 1004 +#define IDC_HACK 1005 +#define IDC_EXCLUSIVE 1006 +#define IDC_PREBUFFER 1007 +#define IDC_SPIN2 1008 +#define IDC_VOL_ENABLE 1009 +#define IDC_ALT_VOL 1010 +#define IDC_VOL_RESET 1011 +#define IDC_PREB_TEXT 1012 +#define IDC_STATE 1013 +#define IDC_PREBUFFER_1 1014 +#define IDC_PREBUFFER_2 1015 +#define IDC_PREBUF_DISP_1 1016 +#define IDC_PREBUF_DISP_2 1017 +#define IDC_BLAH 1018 +#define IDC_BUFFER 1020 +#define IDC_BUF_DISP 1021 +#define IDS_NULLSOFT_WAVEOUT 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 301 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1019 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/resource.h b/Src/Plugins/Input/in_wmvdrm/resource.h new file mode 100644 index 00000000..87c2538f --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/resource.h @@ -0,0 +1,266 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by fileinfo.rc +// +#define IDS_NULLSOFT_WINDOWS_MEDIA_DECODER_OLD 0 +#define IDS_WMDRM_ABOUT 1 +#define IDS_ABOUT 2 +#define IDS_REALTIME 3 +#define IDS_CONNECTING 4 +#define IDS_LOCATING 5 +#define IDS_ACCESS_DENIED 6 +#define IDS_STEREO 7 +#define IDS_QUADROPHONIC 8 +#define IDS_SURROUND 9 +#define IDS_5_1 10 +#define IDS_7_1 11 +#define IDS_EXT 12 +#define IDS_DESCRIPTION 13 +#define IDS_PROTOCOL 14 +#define IDS_EXTENSION 15 +#define IDS_EDIT_FILE_TYPE 16 +#define IDS_WMA_AUDIO_FILE 17 +#define IDS_WMA_VIDEO_FILE 18 +#define IDS_ASF_STREAM 19 +#define IDS_WINDOWS_MEDIA_STREAM 20 +#define IDS_UNKNOWN_ERROR 21 +#define IDS_ATTRIBUTE 22 +#define IDS_VALUE 23 +#define IDS_CLOSE 24 +#define IDS_UNABLE_TO_WRITE_TO_FILE_DOES_FILE_EXIST 25 +#define IDS_UNABLE_TO_WRITE_TO_FILE 26 +#define IDS_UNABLE_TO_WRITE_TO_FILE_MAY_NOT_HAVE_ACCESS 27 +#define IDS_SAVE_FAILED 28 +#define IDS_TRUE 29 +#define IDS_FALSE 30 +#define IDS_UNKNOWN 31 +#define IDS_WINDOWS_MEDIA_XXX 32 +#define IDS_WINDOWS_MEDIA_PROTECTED_CONTENT_NEED_LICENSE 33 +#define IDS_LICENSE_REQUIRED 34 +#define IDS_BEGINNING_UPDATE 35 +#define IDS_UPDATE_FAILED 36 +#define IDS_UPDATE_CANCELLED 37 +#define IDS_IDLE 38 +#define IDS_REQUESTING 39 +#define IDS_RECEIVING_DATA 40 +#define IDS_COMPLETED 41 +#define IDS_INSTALLING 42 +#define IDS_FILE_PROTECTED_CANNOT_PLAY_IN_WINAMP 43 +#define IDS_LICENSE_ACQUIRED 44 +#define IDS_WINDOWS_MEDIA_PROTECTED_CONTENT_UNTRUSTED_SOURCE 45 +#define IDS_UNTRUSTED_LICENSE 46 +#define IDS_WINDOWS_MEDIA_PROTECTED_CONTENT_TAMPERED_LICENSE 47 +#define IDS_TAMPERED_LICENSE 48 +#define IDS_WINDOWS_MEDIA_PROTECTED_CONTENT_SECURITY_UPDATE 49 +#define IDS_SECURITY_UPDATE_REQUIRED 50 +#define IDS_UPDATING 51 +#define IDS_ENCRYPTED_IN_OP_LEVEL_HIGHER_THAN_SUPPORTED 52 +#define IDS_WINDOWS_MEDIA_PLAYBACK_FAILURE 53 +#define IDS_UPDATE_REQUIRED 54 +#define IDS_ACQUIRING_LICENSE 55 +#define IDS_DRM_LICENSE_EXPIRED_VISIT_WINAMP_COM 56 +#define IDS_ERROR 57 +#define IDS_STRING58 58 +#define IDS_BUFFERING 58 +#define IDS_STRING59 59 +#define IDS_UNKNOWN_ERROR_WAV 59 +#define IDS_CODEC 60 +#define IDS_DURATION 61 +#define IDS_BITRATE 62 +#define IDS_FILESIZE 63 +#define IDS_YES 64 +#define IDS_NO 65 +#define IDS_WMVER 66 +#define IDS_SEEKABLE 67 +#define IDS_STRIDABLE 68 +#define IDS_BROADCAST 69 +#define IDS_PROTECTED 70 +#define IDS_TRUSTED 71 +#define IDS_CONTAINS 72 +#define IDS_NAME 73 +#define IDS_STRING74 74 +#define IDS_KBPS 74 +#define IDS_ABOUT_TEXT 75 +#define IDD_CONFIG 105 +#define IDD_CONFIG_STATUS 112 +#define IDD_ADDTYPE 113 +#define IDD_ADVANCED 114 +#define IDS_AUDIO 117 +#define IDS_VIDEO 118 +#define IDS_IMAGE 119 +#define IDS_SCRIPT 120 +#define IDS_NONE 121 +#define IDS_WINDOWS_MEDIA_PLAYLIST 122 +#define IDS_STRING124 123 +#define IDS_ASX_PLAYLIST 123 +#define IDD_INFO 131 +#define IDC_EDIT 1003 +#define IDC_DEV 1004 +#define IDC_REVERT 1006 +#define IDC_BUTTON5 1007 +#define IDC_DELETE 1007 +#define IDC_REMOVETYPE 1007 +#define IDC_ADD 1008 +#define IDC_APPLY 1009 +#define IDC_TEMP 1009 +#define IDC_VOL_ENABLE 1009 +#define IDC_METADATALIST 1010 +#define IDC_ALT_VOL 1010 +#define IDC_VOL_RESET 1011 +#define IDC_EDIT1 1011 +#define IDC_AUDIO_EARLY_PAD 1011 +#define IDC_VIDEO_EARLYPAD 1011 +#define IDC_BUFFER_TIME 1011 +#define IDC_VIDEO_EARLY_PAD 1012 +#define IDC_EDIT2 1012 +#define IDC_AUDIO_EARLYPAD 1012 +#define IDC_STATE 1013 +#define IDC_FILENAME 1014 +#define IDC_STATIC_TITLE 1015 +#define IDC_STATIC_ARTIST 1016 +#define IDC_STATIC_ALBUM 1017 +#define IDC_STATIC_TRACK 1018 +#define IDC_BLAH 1018 +#define IDC_STATIC_YEAR 1019 +#define IDC_STATIC_GENRE 1020 +#define IDC_STATIC_COMMENTS 1021 +#define IDC_EDIT_TITLE 1022 +#define IDC_EDIT_ARTIST 1023 +#define IDC_EDIT_ALBUM 1024 +#define IDC_EDIT_COMMENTS 1025 +#define IDC_EDIT_GENRE 1026 +#define IDC_EDIT_YEAR 1027 +#define IDC_EDIT_TRACK 1028 +#define IDC_BUTTON_DELALL 1028 +#define IDC_LOWMEMORY 1029 +#define IDC_EDIT_GENRE2 1029 +#define IDC_EDIT_PUBLISHER 1029 +#define IDC_STATIC_GENRE2 1030 +#define IDC_AUDIO_OUTOFORDER 1031 +#define IDC_EDIT_GENRE3 1031 +#define IDC_EDIT_ALBUMARTIST 1031 +#define IDC_AUDIO_DEDICATED_THREAD 1032 +#define IDC_VIDEO_OUTOFORDER 1033 +#define IDC_AUDIO_EARLY 1034 +#define IDC_VIDEO_DEDICATED_THREAD 1035 +#define IDC_VIDEO_EARLY 1036 +#define IDC_VIDEO_JITTER 1039 +#define IDC_VIDEO_CACHE_FRAMES 1040 +#define IDC_VIDEO_CATCHUP 1042 +#define IDC_VIDEO_NOTIFYLATE 1043 +#define IDC_CHECK3 1044 +#define IDC_VIDEO_FRAMEDROPOFFSET 1044 +#define IDC_SILENT 1044 +#define IDC_REALTIME 1044 +#define IDC_BROWSER 1045 +#define IDC_EDIT5 1049 +#define IDC_RESET 1052 +#define IDC_GLOBAL_FADES 1053 +#define IDC_CUSTOM_FADE_SPIN 1054 +#define IDC_LOGVOL_SPIN 1055 +#define IDC_REFRESH_SPIN 1056 +#define IDC_FADE 1057 +#define IDC_FADE_SPIN 1058 +#define IDC_CREATE_PRIMARY 1059 +#define IDC_PAUSEFADE2 1060 +#define IDC_DEVICE 1061 +#define IDC_WAITx 1062 +#define IDC_PREBUFFER_2 1063 +#define IDC_KILLSIL 1064 +#define IDC_DB 1065 +#define IDC_DB_DISPLAY 1066 +#define IDC_PREBUFFER_1 1067 +#define IDC_LIST 1068 +#define IDC_PREBUF_DISP_1 1069 +#define IDC_CUSTOM_FADE 1070 +#define IDC_PREBUF_DISP_2 1071 +#define IDC_USE_CUSTOM_FADE 1072 +#define IDC_BUFFER 1073 +#define IDC_BUF_DISP 1074 +#define IDC_STATIC_MS 1075 +#define IDC_BUF_RESET 1076 +#define IDC_VOLUME 1077 +#define IDC_TAB1 1078 +#define IDC_TAB 1079 +#define IDC_DEVICE_INFO 1080 +#define IDC_PDS_FAQ 1081 +#define IDC_FADE_GROUP 1082 +#define IDC_FADE_ENABLED 1083 +#define IDC_REFRESH 1084 +#define IDC_STAT_COPY 1085 +#define IDC_LOGVOL_MIN 1086 +#define IDC_LOGVOL_STATIC 1087 +#define IDC_LOGVOL_STATIC2 1088 +#define IDC_FADEVOL 1089 +#define IDC_PREBUF_AUTO 1090 +#define IDC_STATIC_BLEH 1091 +#define IDC_WAVE_GAPLESS 1092 +#define IDC_WAVE_RESET 1093 +#define IDC_WAVE_BUF_SIZE 1093 +#define IDC_WAVE_SPIN1 1094 +#define IDC_WAVE_PRIMARY 1095 +#define IDC_WAVE_PREBUF_SIZE 1095 +#define IDC_WAVE_DEV 1096 +#define IDC_WAVE_HACK 1097 +#define IDC_WAVE_EXCLUSIVE 1098 +#define IDC_WAVE_PREBUFFER 1099 +#define IDC_WAVE_SPIN2 1100 +#define IDC_WAVE_VOL_ENABLE 1101 +#define IDC_WAVE_ALT_VOL 1102 +#define IDC_WAVE_VOL_RESET 1103 +#define IDC_WAVE_PREB_TEXT 1104 +#define IDC_WAVE_STATE 1105 +#define IDC_WAVE_PREBUFFER_1 1106 +#define IDC_WAVE_PREBUFFER_2 1107 +#define IDC_WAVE_PREBUF_DISP_1 1108 +#define IDC_WAVE_PREBUF_DISP_2 1109 +#define IDC_WAVE_BLAH 1110 +#define IDC_WAVE_BUFFER 1111 +#define IDC_WAVE_BUF_DISP 1112 +#define IDC_VOLMODE 1113 +#define IDC_LOGFADES 1114 +#define IDC_TEXT1 1115 +#define IDC_HW_MIX 1116 +#define IDC_VER 1117 +#define IDC_LIST2 1119 +#define IDC_FLIP 1120 +#define IDC_HTTPMETA 1121 +#define IDC_ADVANCED 1122 +#define IDC_DEFAULTTYPE 1123 +#define IDC_ADDTYPE 1124 +#define IDC_EDITTYPE 1125 +#define IDC_TYPELIST 1126 +#define IDC_UNTRUSTED 1127 +#define IDC_TYPE 1129 +#define IDC_DESCRIPTION 1130 +#define IDC_FILEEXTENSION 1131 +#define IDC_PROTOCOL 1132 +#define IDC_VIDEO_THREAD 1135 +#define IDC_AUDIO_DROP 1138 +#define IDC_CHECK8 1139 +#define IDC_VIDEO_NOTIFY 1139 +#define IDC_AUDIO_THREAD 1140 +#define IDC_EDIT4 1145 +#define IDC_EDIT7 1146 +#define IDC_AUDIO_CACHE_FRAMES 1146 +#define IDC_EDIT6 1148 +#define IDC_VIDEO_DROP_THRESHOLD 1148 +#define IDC_EDIT9 1149 +#define IDC_TYPE_AUDIO 1151 +#define IDC_TYPE_VIDEO 1152 +#define IDC_STATIC_TYPE 1153 +#define IDC_TYPE_AUDIO2 1154 +#define IDC_EXTRA_ASX 1154 +#define IDC_AUDIO_SPEAKER_COUNT 10000 +#define IDS_NULLSOFT_WINDOWS_MEDIA_DECODER 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 127 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1155 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/util.cpp b/Src/Plugins/Input/in_wmvdrm/util.cpp new file mode 100644 index 00000000..a783cea7 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/util.cpp @@ -0,0 +1,169 @@ +#include "main.h" +#include "resource.h" +#include <stdio.h> +#include <strsafe.h> + +//static const GUID INVALID_GUID = +//{ 0x00000000, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }; +void WaitForEvent(HANDLE hEvent, DWORD msMaxWaitTime) +{ + // DWORD i; + MSG msg; + const unsigned long eachWait = 10; + unsigned long totalWait = 0; + + while (WaitForSingleObject(hEvent, eachWait) == WAIT_TIMEOUT) + { + while (PeekMessage(&msg, (HWND) NULL, 0, 0, PM_REMOVE)) + { + //TranslateMessage(&msg); + DispatchMessage(&msg); + } + + totalWait += eachWait; + if (totalWait >= msMaxWaitTime) + break; + + } +} + +char *HRErrorCode(HRESULT hr) +{ +#define HR_ERROR_CODE(x) case x: return #x + switch (hr) + { + HR_ERROR_CODE(E_OUTOFMEMORY); + HR_ERROR_CODE(E_UNEXPECTED); + HR_ERROR_CODE(S_OK); + HR_ERROR_CODE(S_FALSE); + HR_ERROR_CODE(E_NOINTERFACE); + HR_ERROR_CODE(NS_E_PROTECTED_CONTENT); + HR_ERROR_CODE(E_INVALIDARG); + HR_ERROR_CODE(NS_E_DRM_NO_RIGHTS); + HR_ERROR_CODE(NS_E_DRM_LICENSE_NOTACQUIRED); + HR_ERROR_CODE(NS_E_DRM_ACQUIRING_LICENSE); + HR_ERROR_CODE(NS_S_DRM_ACQUIRE_CANCELLED); + HR_ERROR_CODE(NS_E_LICENSE_OUTOFDATE); + HR_ERROR_CODE(NS_E_LICENSE_INCORRECT_RIGHTS); + HR_ERROR_CODE(NS_E_DRM_REOPEN_CONTENT); + HR_ERROR_CODE(NS_E_DRM_LICENSE_APPSECLOW); + HR_ERROR_CODE(E_ABORT); + HR_ERROR_CODE(NS_E_INVALID_REQUEST); + HR_ERROR_CODE(NS_S_DRM_LICENSE_ACQUIRED); + HR_ERROR_CODE(NS_S_DRM_MONITOR_CANCELLED); + HR_ERROR_CODE(NS_E_FILE_NOT_FOUND); + HR_ERROR_CODE(NS_E_FILE_OPEN_FAILED); + HR_ERROR_CODE(NS_E_SERVER_NOT_FOUND); + HR_ERROR_CODE(NS_E_UNRECOGNIZED_STREAM_TYPE); + HR_ERROR_CODE(NS_E_NO_STREAM); + HR_ERROR_CODE(E_ACCESSDENIED); + HR_ERROR_CODE(NS_S_DRM_BURNABLE_TRACK); + HR_ERROR_CODE(NS_S_DRM_BURNABLE_TRACK_WITH_PLAYLIST_RESTRICTION); + HR_ERROR_CODE(NS_E_DRM_TRACK_EXCEEDED_PLAYLIST_RESTICTION); + HR_ERROR_CODE(NS_E_DRM_TRACK_EXCEEDED_TRACKBURN_RESTRICTION); + } + static char temp[50]; + StringCchPrintfA(temp, 50, WASABI_API_LNGSTRING(IDS_UNKNOWN_ERROR), hr); + return temp; + +} + +void GuidString(GUID guid, wchar_t *target, size_t len) +{ + StringCchPrintf( target, len, L"{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", + (int)guid.Data1, (int)guid.Data2, (int)guid.Data3, + (int)guid.Data4[0], (int)guid.Data4[1], + (int)guid.Data4[2], (int)guid.Data4[3], + (int)guid.Data4[4], (int)guid.Data4[5], + (int)guid.Data4[6], (int)guid.Data4[7] ); +} + + const wchar_t *UserTextDescription(unsigned char *binary, size_t size) +{ + WM_USER_TEXT *userText = (WM_USER_TEXT *)binary; + + return userText->pwszDescription; +} + +const wchar_t *UserTextString(unsigned char *binary, size_t size) +{ + WM_USER_TEXT *userText = (WM_USER_TEXT *)binary; + + return userText->pwszText; +} + +void BinaryString(unsigned char *binary, size_t size, wchar_t *final, size_t len) +{ + wchar_t * const start = new wchar_t[2 + size*2 + 1]; // 0x + 2 hex per byte + null terminator + wchar_t *target = start; + *target++='0'; + *target++='x'; + + size_t i; + for (i = 0;i!=size;i++) + { + wchar_t temp[3] = {0}; + _itow(binary[i], temp, 16); + if (!temp[0]) + { + *target++ = '0'; + *target++ = '0'; + } + else if (!temp[1]) + { + *target++ = '0'; + *target++ = temp[0]; + } + else + { + *target++ = temp[0]; + *target++ = temp[1]; + } + } + *target = 0; + lstrcpyn(final, start, len); + delete [] start; +} + +GUID StringGUID(const wchar_t *source) +{ + if (source == NULL) return INVALID_GUID; + + GUID guid = GUID_NULL; + int Data1, Data2, Data3; + int Data4[8] = {0}; + + // {1B3CA60C-DA98-4826-B4A9-D79748A5FD73} + int n = swscanf(source, L" { %08x - %04x - %04x - %02x%02x - %02x%02x%02x%02x%02x%02x } ", + &Data1, &Data2, &Data3, Data4 + 0, Data4 + 1, + Data4 + 2, Data4 + 3, Data4 + 4, Data4 + 5, Data4 + 6, Data4 + 7 ); + + if (n != 11) return INVALID_GUID; + + // Cross assign all the values + guid.Data1 = Data1; + guid.Data2 = Data2; + guid.Data3 = Data3; + guid.Data4[0] = Data4[0]; + guid.Data4[1] = Data4[1]; + guid.Data4[2] = Data4[2]; + guid.Data4[3] = Data4[3]; + guid.Data4[4] = Data4[4]; + guid.Data4[5] = Data4[5]; + guid.Data4[6] = Data4[6]; + guid.Data4[7] = Data4[7]; + + return guid; +} + +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); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/util.h b/Src/Plugins/Input/in_wmvdrm/util.h new file mode 100644 index 00000000..8c8890da --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/util.h @@ -0,0 +1,20 @@ +#ifndef NULLSOFT_UTILH +#define NULLSOFT_UTILH + +#include <windows.h> + +void WaitForEvent(HANDLE hEvent, DWORD msMaxWaitTime); +char *HRErrorCode(HRESULT hr); + +void GuidString(GUID guid, wchar_t *target, size_t len); + +GUID StringGUID(const wchar_t *source); + +void BinaryString(unsigned char *binary, size_t size, wchar_t *final, size_t len); + +const wchar_t *UserTextDescription(unsigned char *binary, size_t size); +const wchar_t *UserTextString(unsigned char *binary, size_t size); + +int DoAboutMessageBox(HWND parent, wchar_t* title, wchar_t* message); + +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/version.rc2 b/Src/Plugins/Input/in_wmvdrm/version.rc2 new file mode 100644 index 00000000..ade63c0d --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 3,9,5,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", "3,9,5,0" + VALUE "InternalName", "Nullsoft Windows Media Decoder" + VALUE "LegalCopyright", "Copyright © 2005-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_wm.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_wmvdrm/vid_ddraw.cpp b/Src/Plugins/Input/in_wmvdrm/vid_ddraw.cpp new file mode 100644 index 00000000..7aed89de --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/vid_ddraw.cpp @@ -0,0 +1,771 @@ +#include "main.h" +#include "vid_ddraw.h" +#include "directdraw.h" + + void *frame; + int width, height, flip; + int needchange; + unsigned int type; + LPDIRECTDRAW lpDD; + LPDIRECTDRAWSURFACE lpddsOverlay, lpddsPrimary; + DDCAPS capsDrv; + unsigned int uDestSizeAlign, uSrcSizeAlign; + DWORD dwUpdateFlags; + RECT rs, rd; + RECT lastresizerect; + bool initing; + + LPDIRECTDRAWCLIPPER lpddsClipper; + DDPIXELFORMAT m_ddpf; + int m_depth; + RGBQUAD *m_palette; + + RECT winRect; + RECT m_monRect; + +void getViewport(RECT *r, HWND wnd, int full, RECT *sr); +#define INIT_DIRECTDRAW_STRUCT(x) (ZeroMemory(&x, sizeof(x)), x.dwSize=sizeof(x)) +DDrawVideoOutput::DDrawVideoOutput() : frame(0), width(0), height(0), + flip(0), type(0), uDestSizeAlign(0), + uSrcSizeAlign(0), dwUpdateFlags(0), + m_depth(0) +{ + lpDD = NULL; + lpddsOverlay = NULL; + lastresizerect.bottom = 0; + lastresizerect.top = 0; + lastresizerect.left = 0; + lastresizerect.right = 0; + + lpddsPrimary = NULL; + lpddsClipper = NULL; + + initing = false; + needchange = 0; + m_palette = NULL; + memset(&winRect, 0, sizeof(winRect)); +} + +void DDrawVideoOutput::close() +{ + // LPDIRECTDRAWSURFACE o=lpddsOverlay; + lpddsOverlay = NULL; + // if(o) o->Release(); + //if(lpddsPrimary) lpddsPrimary->Release(); + // if(lpddsClipper) lpddsClipper->Release(); + if (lpDD) lpDD->Release(); lpDD = 0;// BU added NULL check in response to talkback +// removeFullScreen(); +} + +DDrawVideoOutput::~DDrawVideoOutput() +{ + DDrawVideoOutput::close(); +} + +void DDrawVideoOutput::drawSubtitle(SubsItem *item) +{ +} + +int DDrawVideoOutput::create(HWND parent, VideoAspectAdjuster *_adjuster, int w, int h, unsigned int ptype, int flipit, double aspectratio) +{ + this->parent = parent; + type = ptype; + width = w; + height = h; + flip = flipit; + adjuster = _adjuster; + + initing = true; + HWND hwnd = this->parent; + + if (lpDD) lpDD->Release(); + lpDD = NULL; + + update_monitor_coords(); + + if (!foundGUID) DDrawCreate(NULL, &lpDD, NULL); + else DDrawCreate(&m_devguid, &lpDD, NULL); + + + if (!lpDD) + { + initing = false; + return 0; + } + + lpDD->SetCooperativeLevel(hwnd, DDSCL_NOWINDOWCHANGES | DDSCL_NORMAL); + + DDSURFACEDESC ddsd; + INIT_DIRECTDRAW_STRUCT(ddsd); + ddsd.dwFlags = DDSD_CAPS; + ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; + HRESULT ddrval = lpDD->CreateSurface(&ddsd, &lpddsPrimary, NULL ); + + if (FAILED(ddrval) || !lpddsPrimary) + { + initing=false; + return 0; + } + + HRESULT v = -1; + DDSURFACEDESC DDsd = {sizeof(DDsd), }; + lpddsPrimary->GetSurfaceDesc(&ddsd); + DDsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT; //create the surface at screen depth + DDsd.dwWidth = w; + DDsd.dwHeight = h; + DDsd.ddsCaps.dwCaps = DDSCAPS_VIDEOMEMORY; + if (config_video.ddraw()) v = lpDD->CreateSurface(&DDsd, &lpddsOverlay, NULL); + if (!config_video.ddraw() || FAILED(v)) + { + // fall back to system memory if video mem doesn't work + DDsd.ddsCaps.dwCaps = DDSCAPS_SYSTEMMEMORY; + v = lpDD->CreateSurface(&DDsd, &lpddsOverlay, NULL); + } + if (FAILED(v)) + { + // this video card sucks then :) + lpddsOverlay = NULL; + initing = false; + return 0; + } + + // get the depth + m_depth = 8; + INIT_DIRECTDRAW_STRUCT(m_ddpf); + if (lpddsOverlay->GetPixelFormat(&m_ddpf) >= 0) + { + m_depth = m_ddpf.dwRGBBitCount; + if (m_depth == 16 && m_ddpf.dwGBitMask == 0x03e0) m_depth = 15; + } + + if (lpDD->CreateClipper(0, &lpddsClipper, NULL) != DD_OK) + { + lpddsOverlay = NULL; + initing = false; + return 0; + } + + lpddsClipper->SetHWnd(0, hwnd); + lpddsPrimary->SetClipper(lpddsClipper); + initing = false; + + //get current monitor + getViewport(&m_monRect, hwnd, 1, NULL); + + return 1; +} + +int DDrawVideoOutput::onPaint(HWND hwnd) +{ + return 0; +} + +void DDrawVideoOutput::displayFrame(const char * /*buf*/, int size, int time) +{ + DDSURFACEDESC dd = {sizeof(dd), }; + if (config_video.vsync()) lpDD->WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN, 0); + if (lpddsOverlay->Lock(NULL, &dd, DDLOCK_WAIT, NULL) != DD_OK) + { + needchange = 1; + return ; + } + if (type == VIDEO_MAKETYPE('Y', 'V', '1', '2')) + { + const YV12_PLANES *planes = (YV12_PLANES *)frame; + // convert yv12 to rgb + int bytes = m_depth >> 3; + if (m_depth == 15) bytes = 2; + int i, j, y00, y01, y10, y11, u, v; + unsigned char *pY = (unsigned char *)planes->y.baseAddr; + unsigned char *pU = (unsigned char *)planes->u.baseAddr; + unsigned char *pV = (unsigned char *)planes->v.baseAddr; + unsigned char *pOut = (unsigned char*)dd.lpSurface; + const int rvScale = (int) (2.017 * 65536.0); //91881; + const int gvScale = - (int) (0.392 * 65536.0); // -22553; + const int guScale = - (int) (0.813 * 65536.0); // -46801; + const int buScale = (int) (1.596 * 65536.0); //116129; + const int yScale = (int)( 1.164 * 65536.0 ); //(1.164*65536.0); + int addOut = dd.lPitch * 2 - width * bytes; + int yrb = planes->y.rowBytes; + int addL = dd.lPitch; + + /* LIMIT: convert a 16.16 fixed-point value to a byte, with clipping. */ +#define LIMIT(x) ((x)>0xffffff?0xff: ((x)<=0xffff?0:((x)>>16))) + + if (flip) + { + pOut += (dd.lPitch) * (height - 1); + addOut = -dd.lPitch * 2 - width * bytes; + addL = -addL; + } + + for (j = 0; j <= height - 2; j += 2) + { + for (i = 0; i <= width - 2; i += 2) + { + y00 = *pY - 16; + y01 = *(pY + 1) - 16; + y10 = *(pY + yrb) - 16; + y11 = *(pY + yrb + 1) - 16; + u = (*pU++) - 128; + v = (*pV++) - 128; + + { + int r, g, b; + + g = guScale * v + gvScale * u; + r = buScale * v; + b = rvScale * u; + + y00 *= yScale; y01 *= yScale; + y10 *= yScale; y11 *= yScale; + + switch (m_depth) + { + case 15: + { + unsigned short *rgb = (unsigned short *)pOut; + rgb[0] = ((LIMIT(r + y00) >> 3) << 10) | ((LIMIT(g + y00) >> 3) << 5) | (LIMIT(b + y00) >> 3); + rgb[1] = ((LIMIT(r + y01) >> 3) << 10) | ((LIMIT(g + y01) >> 3) << 5) | (LIMIT(b + y01) >> 3); + rgb += addL / 2; + rgb[0] = ((LIMIT(r + y10) >> 3) << 10) | ((LIMIT(g + y10) >> 3) << 5) | (LIMIT(b + y10) >> 3); + rgb[1] = ((LIMIT(r + y11) >> 3) << 10) | ((LIMIT(g + y11) >> 3) << 5) | (LIMIT(b + y11) >> 3); + } + break; + case 16: + { + unsigned short *rgb = (unsigned short *)pOut; + rgb[0] = ((LIMIT(r + y00) >> 3) << 11) | ((LIMIT(g + y00) >> 2) << 5) | (LIMIT(b + y00) >> 3); + rgb[1] = ((LIMIT(r + y01) >> 3) << 11) | ((LIMIT(g + y01) >> 2) << 5) | (LIMIT(b + y01) >> 3); + rgb += addL / 2; + rgb[0] = ((LIMIT(r + y10) >> 3) << 11) | ((LIMIT(g + y10) >> 2) << 5) | (LIMIT(b + y10) >> 3); + rgb[1] = ((LIMIT(r + y11) >> 3) << 11) | ((LIMIT(g + y11) >> 2) << 5) | (LIMIT(b + y11) >> 3); + } + break; + case 24: + { + unsigned char *rgb = pOut; + /* Write out top two pixels */ + rgb[0] = LIMIT(b + y00); rgb[1] = LIMIT(g + y00); rgb[2] = LIMIT(r + y00); + rgb[3] = LIMIT(b + y01); rgb[4] = LIMIT(g + y01); rgb[5] = LIMIT(r + y01); + + /* Skip down to next line to write out bottom two pixels */ + rgb += addL; + rgb[0] = LIMIT(b + y10); rgb[1] = LIMIT(g + y10); rgb[2] = LIMIT(r + y10); + rgb[3] = LIMIT(b + y11); rgb[4] = LIMIT(g + y11); rgb[5] = LIMIT(r + y11); + } + break; + case 32: + { + unsigned char *rgb = pOut; + /* Write out top two pixels */ + rgb[0] = LIMIT(b + y00); rgb[1] = LIMIT(g + y00); rgb[2] = LIMIT(r + y00); + rgb[4] = LIMIT(b + y01); rgb[5] = LIMIT(g + y01); rgb[6] = LIMIT(r + y01); + + /* Skip down to next line to write out bottom two pixels */ + rgb += addL; + rgb[0] = LIMIT(b + y10); rgb[1] = LIMIT(g + y10); rgb[2] = LIMIT(r + y10); + rgb[4] = LIMIT(b + y11); rgb[5] = LIMIT(g + y11); rgb[6] = LIMIT(r + y11); + } + break; + } + } + + pY += 2; + pOut += 2 * bytes; + } + pY += yrb + yrb - width; + pU += planes->u.rowBytes - width / 2; + pV += planes->v.rowBytes - width / 2; + pOut += addOut; + } + } + else if (type == VIDEO_MAKETYPE('R', 'G', '3', '2')) + { + //FUCKO: do we need to support 8bits depth? + switch (m_depth) + { + case 15: + { // convert RGB32 -> RGB16 (555) + const char *a = (const char *)frame; + char *b = (char *)dd.lpSurface; + int l = width * 4, l2 = dd.lPitch; + int ladj = l; + if (flip) { a += l * (height - 1); ladj = -ladj; } + for (int i = 0;i < height;i++) + { + short *dest = (short *)b; + int *src = (int *)a; + for (int j = 0;j < width;j++) + { + int c = *(src++); + int r = c >> 16; + int g = (c >> 8) & 0xff; + int b = (c) & 0xff; + *(dest++) = ((r >> 3) << 10) | ((g >> 3) << 5) | (b >> 3); + } + a += ladj; b += l2; + } + } + break; + case 16: + { // convert RGB32 -> RGB16 + //FUCKO: this assumes 565 + const char *a = (const char *)frame; + char *b = (char *)dd.lpSurface; + int l = width * 4, l2 = dd.lPitch; + int ladj = l; + if (flip) { a += l * (height - 1); ladj = -ladj; } + for (int i = 0;i < height;i++) + { + short *dest = (short *)b; + int *src = (int *)a; + for (int j = 0;j < width;j++) + { + //FUCKO: optimize here + int c = *(src++); + int r = c >> 16; + int g = (c >> 8) & 0xff; + int b = (c) & 0xff; + *(dest++) = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); + } + a += ladj; b += l2; + } + } + break; + case 24: + { // convert RGB32 -> RGB24 + const char *a = (const char *)frame; + char *b = (char *)dd.lpSurface; + int l = width * 4, l2 = dd.lPitch; + int ladj = l; + if (flip) { a += l * (height - 1); ladj = -ladj; } + for (int i = 0;i < height;i++) + { + char *dest = (char *)b; + int *src = (int *)a; + for (int j = 0;j < width;j++) + { + //FUCKO: optimize here + int c = *(src++); + int r = c >> 16; + int g = (c >> 8) & 0xff; + int b = (c) & 0xff; + *dest++ = b; + *dest++ = g; + *dest++ = r; + } + a += ladj; b += l2; + } + } + break; + case 32: + { // straight RGB32 copy + const char *a = (const char *)frame; + char *b = (char *)dd.lpSurface; + int l = width * 4, l2 = dd.lPitch; + int ladj = l; + if (flip) { a += l * (height - 1); ladj = -ladj; } + for (int i = 0;i < height;i++) + { + memcpy(b, a, l); + a += ladj; b += l2; + } + } + break; + } + } + else if (type == VIDEO_MAKETYPE('Y', 'U', 'Y', '2') || type == VIDEO_MAKETYPE('U', 'Y', 'V', 'Y')) + { + //const char *a = (const char *)frame; + //char *b = (char *)dd.lpSurface; + int /*l = width * 2, */l2 = dd.lPitch; + if (flip) + { + //b += (height - 1) * l2; + l2 = -l2; + } + switch (m_depth) + { + case 15: + { + // yuy2->rgb16 (555) conversion + unsigned char *src = (unsigned char *)frame; + unsigned short *dst = (unsigned short *)dd.lpSurface; + int line, col; //, linewidth; + int y, yy; + int u, v; + int vr, ug, vg, ub; + unsigned char *py, *pu, *pv; + + //linewidth = width - (width >> 1); + py = src; + pu = src + 1; + pv = src + 3; + + int pitchadd = dd.lPitch / 2 - width; + + for (line = 0; line < height; line++) + { + for (col = 0; col < width; col++) + { +#undef LIMIT + #define LIMIT(x) ( (x) > 0xffff ? 0xff : ( (x) <= 0xff ? 0 : ( (x) >> 8 ) ) ) + + y = *py; + yy = y << 8; + u = *pu - 128; + ug = 88 * u; + ub = 454 * u; + v = *pv - 128; + vg = 183 * v; + vr = 359 * v; + + unsigned char b = LIMIT(yy + ub ); + unsigned char g = LIMIT(yy - ug - vg); + unsigned char r = LIMIT(yy + vr); + *(dst++) = ((r >> 3) << 10) | ((g >> 3) << 5) | (b >> 3); + + py += 2; + if ( (col & 1) == 1) + { + pu += 4; // skip yvy every second y + pv += 4; // skip yuy every second y + } + } // ..for col + dst += pitchadd; + } /* ..for line */ + } + break; + case 16: + { + // yuy2->rgb16 conversion + //FUCKO: only supports 565 + unsigned char *src = (unsigned char *)frame; + unsigned short *dst = (unsigned short *)dd.lpSurface; + int line, col; //, linewidth; + int y, yy; + int u, v; + int vr, ug, vg, ub; + unsigned char *py, *pu, *pv; + + //linewidth = width - (width >> 1); + py = src; + pu = src + 1; + pv = src + 3; + + int pitchadd = dd.lPitch / 2 - width; + + for (line = 0; line < height; line++) + { + for (col = 0; col < width; col++) + { +#undef LIMIT + #define LIMIT(x) ( (x) > 0xffff ? 0xff : ( (x) <= 0xff ? 0 : ( (x) >> 8 ) ) ) + + y = *py; + yy = y << 8; + u = *pu - 128; + ug = 88 * u; + ub = 454 * u; + v = *pv - 128; + vg = 183 * v; + vr = 359 * v; + + unsigned char b = LIMIT(yy + ub ); + unsigned char g = LIMIT(yy - ug - vg); + unsigned char r = LIMIT(yy + vr); + *(dst++) = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); + + py += 2; + if ( (col & 1) ) + { + pu += 4; // skip yvy every second y + pv += 4; // skip yuy every second y + } + } // ..for col + dst += pitchadd; + } /* ..for line */ + } + break; + case 24: + { + // yuy2->rgb24 conversion + unsigned char *src = (unsigned char *)frame; + unsigned char *dst = (unsigned char *)dd.lpSurface; + int line, col; //, linewidth; + int y, yy; + int u, v; + int vr, ug, vg, ub; + unsigned char *py, *pu, *pv; + + //linewidth = width - (width >> 1); + py = src; + pu = src + 1; + pv = src + 3; + + int pitchadd = dd.lPitch - (width * 3); + + for (line = 0; line < height; line++) + { + for (col = 0; col < width; col++) + { +#undef LIMIT + #define LIMIT(x) ( (x) > 0xffff ? 0xff : ( (x) <= 0xff ? 0 : ( (x) >> 8 ) ) ) + + y = *py; + yy = y << 8; + u = *pu - 128; + ug = 88 * u; + ub = 454 * u; + v = *pv - 128; + vg = 183 * v; + vr = 359 * v; + + *(dst++) = LIMIT(yy + ub ); + *(dst++) = LIMIT(yy - ug - vg); + *(dst++) = LIMIT(yy + vr); + + py += 2; + if ( (col & 1) == 1) + { + pu += 4; // skip yvy every second y + pv += 4; // skip yuy every second y + } + } // ..for col + dst += pitchadd; + } /* ..for line */ + } + break; + case 32: + { + // yuy2->rgb32 conversion + unsigned char *src = (unsigned char *)frame; + unsigned char *dst = (unsigned char *)dd.lpSurface; + int line, col; //, linewidth; + int y, yy; + int u, v; + int vr, ug, vg, ub; + unsigned char *py, *pu, *pv; + + //linewidth = width - (width >> 1); + py = src; + pu = src + 1; + pv = src + 3; + + int pitchadd = dd.lPitch - (width * 4); + + for (line = 0; line < height; line++) + { + for (col = 0; col < width; col++) + { +#undef LIMIT + #define LIMIT(x) ( (x) > 0xffff ? 0xff : ( (x) <= 0xff ? 0 : ( (x) >> 8 ) ) ) + + y = *py; + yy = y << 8; + u = *pu - 128; + ug = 88 * u; + ub = 454 * u; + v = *pv - 128; + vg = 183 * v; + vr = 359 * v; + + *dst++ = LIMIT(yy + ub ); // b + *dst++ = LIMIT(yy - ug - vg); // g + *dst++ = LIMIT(yy + vr); // r + dst++; + + py += 2; + if ( (col & 1) == 1) + { + pu += 4; // skip yvy every second y + pv += 4; // skip yuy every second y + } + } // ..for col + dst += pitchadd; + } /* ..for line */ + } + break; + } + } + else if (type == VIDEO_MAKETYPE('R', 'G', '2', '4')) + { + //FUCKO: only ->RGB32 conversion supported + switch (m_depth) + { + case 32: + { + const char *a = (const char *)frame; + char *b = (char *)dd.lpSurface; + int l = width, l2 = dd.lPitch; + int ladj = l * 3; + if (!flip) ladj = 0; + if (flip) { a += (l * 3) * (height - 1); ladj = -(ladj + l * 3); } + l2 -= l * 4; + for (int i = 0;i < height;i++) + { + //memcpy(b,a,l); + for (int j = 0;j < l;j++) + { + b[0] = a[0]; + b[1] = a[1]; + b[2] = a[2]; + b += 4; a += 3; + } + a += ladj; b += l2; + } + } + break; + } + } + else if (type == VIDEO_MAKETYPE('R', 'G', 'B', '8') && m_palette) + { + unsigned char *d = (unsigned char *)dd.lpSurface; + int pitch = dd.lPitch; + unsigned char *src = (unsigned char *)frame; + int newwidth = (width + 3) & 0xfffc; + src += newwidth * height - 1; + for (int j = 0;j < height;j++) + { + switch (m_depth) + { + case 15: + case 16: + { + unsigned short *dest = (unsigned short *)d; + for (int i = 0;i < newwidth;i++) + { + unsigned char c = src[ -newwidth + 1 + i]; + RGBQUAD *rgb = &m_palette[c]; + switch (m_depth) + { + case 15: *(dest++) = ((rgb->rgbRed >> 3) << 10) | ((rgb->rgbGreen >> 3) << 5) | (rgb->rgbBlue >> 3); break; + case 16: *(dest++) = ((rgb->rgbRed >> 3) << 11) | ((rgb->rgbGreen >> 2) << 5) | (rgb->rgbBlue >> 3); break; + } + } + } + break; + case 24: + case 32: + { + unsigned char *dest = d; + for (int i = 0;i < newwidth;i++) + { + unsigned char c = src[ -newwidth + 1 + i]; + RGBQUAD *rgb = &m_palette[c]; + *dest++ = rgb->rgbBlue; + *dest++ = rgb->rgbGreen; + *dest++ = rgb->rgbRed; + if (m_depth == 32) dest++; + } + } + break; + } + d += pitch; + src -= newwidth; + } + } + + lpddsOverlay->Unlock(&dd); + + + RECT r; + HWND hwnd = this->parent; + if (!IsWindow(hwnd)) return ; + + // if(GetParent(hwnd)) hwnd=GetParent(hwnd); + + GetClientRect(hwnd, &r); + RECT fullr = r; + adjuster->adjustAspect(r); + if (r.left != lastresizerect.left || r.right != lastresizerect.right || r.top != lastresizerect.top || + r.bottom != lastresizerect.bottom) + { + if (r.left != 0) + { + RECT tmp = {0, 0, r.left, fullr.bottom}; + InvalidateRect(hwnd, &tmp, TRUE); + } + + if (r.right != fullr.right) + { + RECT tmp = {r.right, 0, fullr.right, fullr.bottom}; + InvalidateRect(hwnd, &tmp, TRUE); + } + if (r.top != 0) + { + RECT tmp = {r.left, 0, r.right, r.top}; + InvalidateRect(hwnd, &tmp, TRUE); + } + if (r.bottom != fullr.bottom) + { + RECT tmp = {r.left, r.bottom, r.right, fullr.bottom}; + InvalidateRect(hwnd, &tmp, TRUE); + } + + lastresizerect = r; + } + + ClientToScreen(hwnd, (LPPOINT)&r); + ClientToScreen(hwnd, ((LPPOINT)&r) + 1); + + // transform coords from windows desktop coords (where 0,0==upper-left corner of box encompassing all monitors) + // to the coords for the monitor we're displaying on: + r.left -= m_mon_x; + r.right -= m_mon_x; + r.top -= m_mon_y; + r.bottom -= m_mon_y; + + HDC hdc = NULL; + HDC inhdc = NULL; + + RECT *pSrcRect = NULL; + + { + if (!config_video.ddraw() || lpddsPrimary->Blt(&r, lpddsOverlay, pSrcRect, DDBLT_WAIT, 0) != DD_OK) + { + // as a last resort, BitBlt(). + if (lpddsOverlay->GetDC(&inhdc) != DD_OK) + { + needchange = 1; + return ; + } + if (lpddsPrimary->GetDC(&hdc) != DD_OK) + { + lpddsOverlay->ReleaseDC(inhdc); inhdc = NULL; + needchange = 1; + return ; + } + + int src_w = width; + int src_h = pSrcRect ? (pSrcRect->bottom - pSrcRect->top) : height; + if (r.right - r.left == src_w && r.bottom - r.top == src_h) + BitBlt(hdc, r.left, r.top, r.right - r.left, r.bottom - r.top, inhdc, 0, 0, SRCCOPY); + else + StretchBlt(hdc, r.left, r.top, r.right - r.left, r.bottom - r.top, inhdc, 0, 0, src_w, src_h, SRCCOPY); + } + } + + if (hdc) { lpddsPrimary->ReleaseDC(hdc); hdc = NULL; } + if (inhdc) { lpddsOverlay->ReleaseDC(inhdc); inhdc = NULL; } +} + +void DDrawVideoOutput::timerCallback() +{ + //check if the video has been dragged to another monitor + RECT curRect; + + getViewport(&curRect, parent, 1, NULL); + if (memcmp(&curRect, &m_monRect, sizeof(RECT))) + needchange = 1; +} + +void DDrawVideoOutput::resetSubtitle() +{ +} + +void DDrawVideoOutput::Refresh() +{ + // do nothing, we'll refresh on the next frame +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/vid_ddraw.h b/Src/Plugins/Input/in_wmvdrm/vid_ddraw.h new file mode 100644 index 00000000..184d5817 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/vid_ddraw.h @@ -0,0 +1,50 @@ +#ifndef NULLSOFT_IN_WMVDRM_VID_DDRAW_H +#define NULLSOFT_IN_WMVDRM_VID_DDRAW_H + +#include <ddraw.h> +#include "VideoOutputChildDDraw.h" + +class SubsItem; + +class DDrawVideoOutput : public VideoOutputChildDDraw +{ +public: + DDrawVideoOutput(); + virtual ~DDrawVideoOutput(); + int create(HWND parent, VideoAspectAdjuster *_adjuster, int w, int h, unsigned int ptype, int flipit, double aspectratio); + int needChange() { return needchange; } + int onPaint(HWND hwnd); + void displayFrame(const char *buf, int size, int time); + void timerCallback(); + void setPalette(RGBQUAD *pal) { m_palette = pal; } + void drawSubtitle(SubsItem *item); + void resetSubtitle(); + void setVFlip(int on) { flip = on; } + void Refresh(); + void close(); + void SetFrame(void *_frame) { frame = _frame; } +private: + void *frame; + int width, height, flip; + int needchange; + unsigned int type; + LPDIRECTDRAW lpDD; + LPDIRECTDRAWSURFACE lpddsOverlay, lpddsPrimary; + DDCAPS capsDrv; + unsigned int uDestSizeAlign, uSrcSizeAlign; + DWORD dwUpdateFlags; + RECT rs, rd; + RECT lastresizerect; + bool initing; + + LPDIRECTDRAWCLIPPER lpddsClipper; + DDPIXELFORMAT m_ddpf; + int m_depth; + RGBQUAD *m_palette; + + RECT winRect; + RECT m_monRect; +}; + +extern DDrawVideoOutput ddraw; +#endif diff --git a/Src/Plugins/Input/in_wmvdrm/vid_overlay.cpp b/Src/Plugins/Input/in_wmvdrm/vid_overlay.cpp new file mode 100644 index 00000000..dd686489 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/vid_overlay.cpp @@ -0,0 +1,708 @@ +#include "main.h" +#include <windows.h> +#include <multimon.h> +#include "vid_overlay.h" +#include "directdraw.h" +#include <api.h> +#include "../Winamp/wa_ipc.h" +#include <assert.h> +void getViewport(RECT *r, HWND wnd, int full, RECT *sr); + + +void YV12_to_YUY2(unsigned char *output, const YV12_PLANES *planes, int pitch, int width, int height, int flip) +{ + const unsigned char *yi = planes->y.baseAddr; + const unsigned char *ui = planes->u.baseAddr; + const unsigned char *vi = planes->v.baseAddr; + if (flip) + output += pitch * (height - 1); + while (height > 0) + { + int x = width; + unsigned char *oo = output; + + while (x > 0) + { + output[0] = *yi++; output[1] = *ui++; output[2] = *yi++; output[3] = *vi++; + output += 4; x -= 2; + } + ui -= width / 2; + vi -= width / 2; + yi += planes->y.rowBytes - width; + x = width; + if (flip) output = oo - pitch; + else output += pitch - width * 2; + oo = output; + while (x > 0) + { + output[0] = *yi++; output[1] = *ui++; output[2] = *yi++; output[3] = *vi++; + output += 4; x -= 2; + } + if (flip) output = oo - pitch; + else output += pitch - width * 2; + ui += planes->u.rowBytes - (width / 2); + vi += planes->v.rowBytes - (width / 2); + yi += planes->y.rowBytes - width; + height -= 2; + } +} + +void YV12_to_UYVY(unsigned char *output, const YV12_PLANES *planes, int pitch, int width, int height, int flip) +{ + const unsigned char *yi = planes->y.baseAddr; + const unsigned char *ui = planes->u.baseAddr; + const unsigned char *vi = planes->v.baseAddr; + + if (flip) output += pitch * (height - 1); + while (height > 0) + { + int x = width; + unsigned char *oo = output; + + while (x > 0) + { + output[0] = *ui++; output[1] = *yi++; output[2] = *vi++; output[3] = *yi++; + output += 4; x -= 2; + } + + ui -= width / 2; + vi -= width / 2; + yi += planes->y.rowBytes - width; + x = width; + if (flip) output = oo - pitch; + else output += pitch - width * 2; + oo = output; + while (x > 0) + { + output[0] = *ui++; output[1] = *yi++; output[2] = *vi++; output[3] = *yi++; + output += 4; x -= 2; + } + + if (flip) output = oo - pitch; + else output += pitch - width * 2; + ui += planes->u.rowBytes - (width / 2); + vi += planes->v.rowBytes - (width / 2); + yi += planes->y.rowBytes - width; + height -= 2; + } +} + +void YV12_to_YV12(unsigned char *output, const YV12_PLANES *planes, int pitch, int width, int height, int flip) +{ // woo native YV12 copy + int f = !!flip; + unsigned char *o = output + (f * height * pitch); + const char *i = (const char*)planes->y.baseAddr; + + + int d_o = pitch; + if (f) d_o = -d_o; + else o -= d_o; + + int h2 = height; + while (h2--) + { + o += d_o; memcpy(o, i, width); i += planes->y.rowBytes; + } + + d_o /= 2; + + int w2 = width / 2; + h2 = height / 2; + i = (const char*)planes->v.baseAddr; + o = output + (height * pitch * (f + 4)) / 4; + + if (!f) o -= d_o; + while (h2--) + { + o += d_o; memcpy(o, i, w2); i += planes->v.rowBytes; + } + o = output + (height * pitch * (f + 5)) / 4; + i = (const char*)planes->u.baseAddr; + h2 = height / 2; + + if (!f) o -= d_o; + while (h2--) + { + o += d_o; memcpy(o, i, w2);i += planes->u.rowBytes; + } +} + + +void YUY2_to_YUY2(unsigned char *output, const char *buf, int pitch, int width, int height, int flip) +{ + const char *a = buf; + unsigned char *b = output; + int l = width * 2, l2 = pitch; + if (flip) + { + b += (height - 1) * l2; + l2 = -l2; + } + + + //wee straight YUY2 copy + for (int i = 0;i < height;i++) + { + memcpy(b, a, l); + b += l2; + a += l; + } + +} + +void YUY2_to_UYVY(unsigned char *output, const char *buf, int pitch, int width, int height, int flip) +{ + const char *a = buf; + unsigned char *b = output; + int l = width * 2, l2 = pitch; + if (flip) + { + b += (height - 1) * l2; + l2 = -l2; + } + + for (int i = 0;i < height;i++) + { + int x = width / 2; + while (x-- > 0) + { + b[0] = a[1]; + b[1] = a[0]; + b[2] = a[3]; + b[3] = a[2]; + a += 4; + b += 4; + } + memcpy(b, a, l); + b += l2; + a += l; + } +} + +#define INIT_DIRECTDRAW_STRUCT(x) (ZeroMemory(&x, sizeof(x)), x.dwSize=sizeof(x)) +// I like to set these to 255,0,255 to test that we arent drawing this color too many playces +#define OV_COL_R 16 +#define OV_COL_G 0 +#define OV_COL_B 16 + +OverlayVideoOutput::OverlayVideoOutput() : frame(NULL), width(0), height(0), + flip(0), type(0), uDestSizeAlign(0), uSrcSizeAlign(0), dwUpdateFlags(0) +{ + lpDD = NULL; + m_closed = 0; + overlay_color = RGB(OV_COL_R, OV_COL_G, OV_COL_B); + lpddsOverlay = NULL; + lpddsPrimary = NULL; + lpBackBuffer = NULL; + + yuy2_output = uyvy_output = 0; + initing = false; + needchange = 0; + memset(&m_oldrd, 0, sizeof(m_oldrd)); + memset(&winRect, 0, sizeof(winRect)); + m_fontsize = 0; +} + +OverlayVideoOutput::~OverlayVideoOutput() +{ + OverlayVideoOutput::close(); +} + + +static DWORD DD_ColorMatch(LPDIRECTDRAWSURFACE pdds, COLORREF rgb) +{ + COLORREF rgbT; + HDC hdc; + DWORD dw = CLR_INVALID; + DDSURFACEDESC ddsd; + HRESULT hres; + + // + // use GDI SetPixel to color match for us + // + if (rgb != CLR_INVALID && pdds->GetDC(&hdc) == DD_OK) + { + rgbT = GetPixel(hdc, 0, 0); // save current pixel value + SetPixel(hdc, 0, 0, rgb); // set our value + pdds->ReleaseDC(hdc); + } + + // now lock the surface so we can read back the converted color + ddsd.dwSize = sizeof(ddsd); + while ((hres = pdds->Lock(NULL, &ddsd, 0, NULL)) == + DDERR_WASSTILLDRAWING) + ; + + if (hres == DD_OK) + { + dw = *(DWORD *)ddsd.lpSurface; // get DWORD + if (ddsd.ddpfPixelFormat.dwRGBBitCount < 32) + dw &= (1 << ddsd.ddpfPixelFormat.dwRGBBitCount) - 1; // mask it to bpp + pdds->Unlock(NULL); + } + + + // now put the color that was there back. + if (rgb != CLR_INVALID && pdds->GetDC(&hdc) == DD_OK) + { + SetPixel(hdc, 0, 0, rgbT); + pdds->ReleaseDC(hdc); + } + + return dw; +} + +int OverlayVideoOutput::create(HWND parent, VideoAspectAdjuster *_adjuster, int w, int h, unsigned int ptype, int flipit, double aspectratio) +{ + OverlayVideoOutput::close(); + this->parent = parent; + type = ptype; + width = w; + height = h; + flip = flipit; + + adjuster = _adjuster; + + initing = true; + HWND hwnd = this->parent; + + if (lpDD) lpDD->Release(); + lpDD = NULL; + + update_monitor_coords(); + + if (!foundGUID) DDrawCreate(NULL, &lpDD, NULL); + else DDrawCreate(&m_devguid, &lpDD, NULL); + + + if (!lpDD) + { + initing = false; + return 0; + } + + lpDD->SetCooperativeLevel(hwnd, DDSCL_NOWINDOWCHANGES | DDSCL_NORMAL); + + DDSURFACEDESC ddsd; + INIT_DIRECTDRAW_STRUCT(ddsd); + ddsd.dwFlags = DDSD_CAPS; + ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; + lpDD->CreateSurface(&ddsd, &lpddsPrimary, NULL ); + + if (!lpddsPrimary) + { + if (lpDD) lpDD->Release(); + lpDD = NULL; + initing=false; + return 0; + } + + // init overlay + DDSURFACEDESC ddsdOverlay; + INIT_DIRECTDRAW_STRUCT(ddsdOverlay); + //ddsdOverlay.ddsCaps.dwCaps=DDSCAPS_OVERLAY | DDSCAPS_VIDEOMEMORY; + //ddsdOverlay.dwFlags= DDSD_CAPS|DDSD_HEIGHT|DDSD_WIDTH|DDSD_PIXELFORMAT|DDSD_PITCH; + //ddsdOverlay.dwBackBufferCount=0; + ddsdOverlay.ddsCaps.dwCaps = DDSCAPS_OVERLAY | DDSCAPS_FLIP | DDSCAPS_COMPLEX; + ddsdOverlay.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_PITCH | DDSD_BACKBUFFERCOUNT; + ddsdOverlay.dwBackBufferCount = 1; + ddsdOverlay.dwWidth = w; + ddsdOverlay.dwHeight = h; + ddsdOverlay.lPitch = w * 4; + DDPIXELFORMAT pf[] = + { + {sizeof(DDPIXELFORMAT), DDPF_FOURCC, MAKEFOURCC('Y', 'U', 'Y', '2'), 0, 0, 0, 0, 0}, + {sizeof(DDPIXELFORMAT), DDPF_FOURCC, MAKEFOURCC('U', 'Y', 'V', 'Y'), 0, 0, 0, 0, 0}, // UYVY + {sizeof(DDPIXELFORMAT), DDPF_FOURCC, MAKEFOURCC('Y', 'V', '1', '2'), 0, 0, 0, 0, 0}, + }; + int tab[5] = {0}; + if (type == VIDEO_MAKETYPE('Y', 'U', 'Y', '2')) + { + tab[0] = 0; // default is YUY2 + tab[1] = 1; + tab[2] = -1; + } + else if (type == VIDEO_MAKETYPE('U', 'Y', 'V', 'Y')) + { + tab[0] = 1; // make UYVY default + tab[1] = 0; + tab[2] = -1; + } + else if (type == VIDEO_MAKETYPE('Y', 'V', '1', '2')) + { + if (config_video.yv12()) + { + tab[0] = 2; + tab[1] = 0; + tab[2] = 1; + tab[3] = -1; + } + else + { + //use YUY2 + tab[0] = 0; // default is YUY2 + tab[1] = 1; + tab[2] = -1; + } + } + else + { + tab[0] = -1; // default is RGB + } + + int x = 4096; + HRESULT v = -1; + for (x = 0; x < sizeof(tab) / sizeof(tab[0]) && tab[x] >= 0; x ++) + { + ddsdOverlay.ddpfPixelFormat = pf[tab[x]]; + v = lpDD->CreateSurface(&ddsdOverlay, &lpddsOverlay, NULL); + if (!FAILED(v)) break; + } + if (FAILED(v) || x >= sizeof(tab) / sizeof(tab[0]) || tab[x] < 0) + { + initing = false; + return 0; + } + + yuy2_output = (tab[x] == 0); + uyvy_output = (tab[x] == 1); + + //get the backbuffer surface + DDSCAPS ddscaps; + ddscaps.dwCaps = DDSCAPS_BACKBUFFER; + v = lpddsOverlay->GetAttachedSurface(&ddscaps, &lpBackBuffer); + if (v != DD_OK || lpBackBuffer == 0) + { + // make it use normal vsync + lpBackBuffer=0; + initing = FALSE; + return 0; + } + + INIT_DIRECTDRAW_STRUCT(capsDrv); + lpDD->GetCaps(&capsDrv, NULL); + + uDestSizeAlign = capsDrv.dwAlignSizeDest; + uSrcSizeAlign = capsDrv.dwAlignSizeSrc; + + dwUpdateFlags = DDOVER_SHOW | DDOVER_KEYDESTOVERRIDE; + + DEVMODE d; + d.dmSize = sizeof(d); + d.dmDriverExtra = 0; + EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &d); + + int rv = OV_COL_R, gv = OV_COL_G, bv = OV_COL_B; + overlay_color = RGB(rv, gv, bv); + + if (d.dmBitsPerPel == 8) + { + overlay_color = RGB(255, 0, 255); + } + + INIT_DIRECTDRAW_STRUCT(ovfx); + ovfx.dwDDFX = 0; + switch (d.dmBitsPerPel) + { + case 8: + ovfx.dckDestColorkey.dwColorSpaceLowValue = 253; + break; + case 16: + ovfx.dckDestColorkey.dwColorSpaceLowValue = ((rv >> 3) << 11) | ((gv >> 2) << 5) | (bv >> 3); + break; + case 15: + ovfx.dckDestColorkey.dwColorSpaceLowValue = ((rv >> 3) << 10) | ((gv >> 3) << 5) | (bv >> 3); + break; + case 24: + case 32: + ovfx.dckDestColorkey.dwColorSpaceLowValue = (rv << 16) | (gv << 8) | bv; + break; + } + + //try to get the correct bit depth thru directdraw (for fucked up 16 bits displays for ie.) + { + DDSURFACEDESC DDsd = {sizeof(DDsd), }; + lpddsPrimary->GetSurfaceDesc(&ddsd); + DDsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT; //create the surface at screen depth + DDsd.dwWidth = 8; + DDsd.dwHeight = 8; + DDsd.ddsCaps.dwCaps = DDSCAPS_SYSTEMMEMORY; + LPDIRECTDRAWSURFACE tempsurf; + if (lpDD->CreateSurface(&DDsd, &tempsurf, NULL) == DD_OK) + { + int res = DD_ColorMatch(tempsurf, overlay_color); + if (res != CLR_INVALID) ovfx.dckDestColorkey.dwColorSpaceLowValue = res; + tempsurf->Release(); + } + } + + ovfx.dckDestColorkey.dwColorSpaceHighValue = ovfx.dckDestColorkey.dwColorSpaceLowValue; + + getRects(&rs, &rd); + if (FAILED(lpddsOverlay->UpdateOverlay(&rs, lpddsPrimary, &rd, dwUpdateFlags, &ovfx))) + { + initing = false; + return 0; + } + initing = false; + + DDSURFACEDESC dd = {sizeof(dd), }; + if (lpddsOverlay->Lock(NULL, &dd, DDLOCK_WAIT, NULL) != DD_OK) return 0; + unsigned char *o = (unsigned char*)dd.lpSurface; + if (uyvy_output || yuy2_output) + { + int x = dd.lPitch * height / 2; + while (x--) + { + if (uyvy_output) + { + *o++ = 128; + *o++ = 0; + } + else + { + *o++ = 0; + *o++ = -128; + } + } + } + else + { + memset(o, 0, dd.lPitch*height); o += dd.lPitch * height; + memset(o, 128, dd.lPitch*height / 2); + } + lpddsOverlay->Unlock(&dd); + + m_closed=0; + needchange = 0; + InvalidateRect(hwnd, NULL, TRUE); + + return 1; +} + +void OverlayVideoOutput::close() +{ + m_closed = 1; + if (lpddsOverlay) lpddsOverlay->UpdateOverlay(NULL, lpddsPrimary, NULL, DDOVER_HIDE , NULL); + if (lpBackBuffer) lpBackBuffer->Release(); lpBackBuffer=0; + if (lpddsOverlay) lpddsOverlay->Release(); lpddsOverlay=0; + if (lpddsPrimary) lpddsPrimary->Release(); lpddsPrimary=0; + if (lpDD) lpDD->Release(); lpDD=0; // BU added NULL check in response to talkback +} + +void OverlayVideoOutput::getRects(RECT *drs, RECT *drd, int fixmultimon) const +{ + //if(GetParent(hwnd)) hwnd=GetParent(hwnd); + + RECT rd, rs; + GetClientRect(parent, &rd); + ClientToScreen(parent, (LPPOINT)&rd); + ClientToScreen(parent, ((LPPOINT)&rd) + 1); + + adjuster->adjustAspect(rd); + + rd.left -= m_mon_x; + rd.right -= m_mon_x; + rd.top -= m_mon_y; + rd.bottom -= m_mon_y; + + memset(&rs, 0, sizeof(rs)); + rs.right = width; + rs.bottom = height; + + if (fixmultimon) + { + //resize overlay for off-screen + RECT rfull; + getViewport(&rfull, parent, 1, NULL); + + rfull.left -= m_mon_x; + rfull.right -= m_mon_x; + rfull.top -= m_mon_y; + rfull.bottom -= m_mon_y; + + if (rd.right > rfull.right) + { + int diff = rd.right - rfull.right; + float sc = (float)(width) / (float)(rd.right - rd.left); + rd.right = rfull.right; + rs.right = width - (int)(diff * sc); + } + if (rd.left < rfull.left) + { + int diff = rfull.left - rd.left; + float sc = (float)(width) / (float)(rd.right - rd.left); + rd.left = rfull.left; + rs.left = (int)(diff * sc); + } + if (rd.bottom > rfull.bottom) + { + int diff = rd.bottom - rfull.bottom; + float sc = (float)(height) / (float)(rd.bottom - rd.top); + rd.bottom = rfull.bottom; + rs.bottom = height - (int)(diff * sc); + } + if (rd.top < rfull.top) + { + int diff = rfull.top - rd.top; + float sc = (float)(height) / (float)(rd.bottom - rd.top); + rd.top = rfull.top; + rs.top = (int)(diff * sc); + } + } + + if (capsDrv.dwCaps & DDCAPS_ALIGNSIZESRC && uDestSizeAlign) + { + rs.left = (int)((rs.left + uDestSizeAlign - 1) / uDestSizeAlign) * uDestSizeAlign; + rs.right = (int)((rs.right + uDestSizeAlign - 1) / uDestSizeAlign) * uDestSizeAlign; + } + if (capsDrv.dwCaps & DDCAPS_ALIGNSIZEDEST && uDestSizeAlign) + { + rd.left = (int)((rd.left + uDestSizeAlign - 1) / uDestSizeAlign) * uDestSizeAlign; + rd.right = (int)((rd.right + uDestSizeAlign - 1) / uDestSizeAlign) * uDestSizeAlign; + } + + *drd = rd; + *drs = rs; +} + +void OverlayVideoOutput::timerCallback() +{ + if (!adjuster) + return; + RECT rd, rs; + getRects(&rs, &rd); + + if (memcmp(&m_oldrd, &rd, sizeof(RECT))) + { + m_oldrd = rd; + if (!initing && lpddsOverlay) + if (FAILED(lpddsOverlay->UpdateOverlay(&rs, lpddsPrimary, &rd, dwUpdateFlags, &ovfx))) + { + needchange = 1; + } + InvalidateRect(parent, NULL, FALSE); + } +} + +int OverlayVideoOutput::onPaint(HWND hwnd) +{ + if (!adjuster) + return 0; + PAINTSTRUCT p; + BeginPaint(hwnd, &p); + + if (!m_closed) + { + RECT r, rs, rfull, clientRect; + RECT drawRect; + getRects(&rs, &r, 0); // we don't just fill the entire client rect, cause that looks gross + + getViewport(&rfull, hwnd, 1, NULL); + + // go from this screen coords to global coords + r.left += rfull.left; + r.top += rfull.top; + r.right += rfull.left; + r.bottom += rfull.top; + + // go from global back to client + ScreenToClient(hwnd, (LPPOINT)&r); + ScreenToClient(hwnd, ((LPPOINT)&r) + 1); + + + HBRUSH br = (HBRUSH) GetStockObject(BLACK_BRUSH); + GetClientRect(hwnd, &clientRect); + + // left black box + drawRect.left = clientRect.left; + drawRect.right = r.left; + drawRect.top = clientRect.top; + drawRect.bottom = clientRect.bottom; + FillRect(p.hdc, &drawRect, br); + + // right black box + drawRect.left = r.right; + drawRect.right = clientRect.right; + drawRect.top = clientRect.top; + drawRect.bottom = clientRect.bottom; + FillRect(p.hdc, &drawRect, br); + + // top black box + drawRect.left = clientRect.left; + drawRect.right = clientRect.right; + drawRect.top = clientRect.top; + drawRect.bottom = r.top; + FillRect(p.hdc, &drawRect, br); + + // bottom black box + drawRect.left = clientRect.left; + drawRect.right = clientRect.right; + drawRect.top = r.bottom; + drawRect.bottom = clientRect.bottom; + FillRect(p.hdc, &drawRect, br); + + LOGBRUSH lb = {BS_SOLID, (COLORREF)overlay_color, }; + br = CreateBrushIndirect(&lb); + + FillRect(p.hdc, &r, br); + DeleteObject(br); + } + + EndPaint(hwnd, &p); + + return 1; +} + +void OverlayVideoOutput::displayFrame(const char * /*buf*/, int size, int time) +{ + if (m_closed) + return; + DDSURFACEDESC dd = {sizeof(dd), }; + //CT> vsync wait not used anymore + //if (config_video.vsync()) lpDD->WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN,0); + HRESULT result; + //if ((result=lpddsOverlay->Lock(NULL,&dd,DDLOCK_WAIT,NULL)) != DD_OK) { + if ((result = lpBackBuffer->Lock(NULL, &dd, DDLOCK_WAIT, NULL)) != DD_OK) + { + if (result == DDERR_SURFACELOST) needchange = 1; + return ; + } + if (type == VIDEO_MAKETYPE('Y', 'V', '1', '2')) + { + const YV12_PLANES *planes = (YV12_PLANES *)frame; + if (uyvy_output) + YV12_to_UYVY((unsigned char*)dd.lpSurface, planes, dd.lPitch, width, height, flip); + else if (yuy2_output) + YV12_to_YUY2((unsigned char*)dd.lpSurface, planes, dd.lPitch, width, height, flip); + else + YV12_to_YV12((unsigned char*)dd.lpSurface, planes, dd.lPitch, width, height, flip); + } + else if (type == VIDEO_MAKETYPE('Y', 'U', 'Y', '2')) + { + if (yuy2_output) + YUY2_to_YUY2((unsigned char*)dd.lpSurface, (const char *)frame, dd.lPitch, width, height, flip); + else if (uyvy_output) + YUY2_to_UYVY((unsigned char*)dd.lpSurface, (const char *)frame, dd.lPitch, width, height, flip); + else + YUY2_to_YUY2((unsigned char*)dd.lpSurface, (const char *)frame, dd.lPitch, width, height, flip); // is this right? + } + else if (type == VIDEO_MAKETYPE('U', 'Y', 'V', 'Y')) + { + if (yuy2_output) + YUY2_to_UYVY((unsigned char*)dd.lpSurface, (const char *)frame, dd.lPitch, width, height, flip); + else + YUY2_to_YUY2((unsigned char*)dd.lpSurface, (const char *)frame, dd.lPitch, width, height, flip); + } + + lpBackBuffer->Unlock(&dd); + lpddsOverlay->Flip(lpBackBuffer, DDFLIP_WAIT); +} + +void OverlayVideoOutput::drawSubtitle(SubsItem *item) +{ +} + +void OverlayVideoOutput::resetSubtitle() +{ +} diff --git a/Src/Plugins/Input/in_wmvdrm/vid_overlay.h b/Src/Plugins/Input/in_wmvdrm/vid_overlay.h new file mode 100644 index 00000000..d7d811a6 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/vid_overlay.h @@ -0,0 +1,55 @@ +#ifndef NULLSOFT_VIDEO_OVERLAY_H +#define NULLSOFT_VIDEO_OVERLAY_H + +#include <ddraw.h> +#include <multimon.h> +#include "VideoOutputChildDDraw.h" + +class SubsItem; + +class OverlayVideoOutput : public VideoOutputChildDDraw { + +public: + OverlayVideoOutput(); + virtual ~OverlayVideoOutput(); + int create(HWND parent,VideoAspectAdjuster *_adjuster, int w, int h, unsigned int type, int flipit, double aspectratio); //return 1 if ok + int needChange() { return needchange; } + int onPaint(HWND hwnd); + void displayFrame(const char *buf, int size, int time); + void timerCallback(); + void close(); + void drawSubtitle(SubsItem *item); + void resetSubtitle(); + void setVFlip(int on) { flip=on; } + void Refresh() { if (parent) InvalidateRect(parent, NULL, TRUE); } + void SetFrame(void *_frame) { frame = _frame; } +private: + void *frame; + int width, height, flip; + int m_closed; + int needchange; + unsigned int type; + LPDIRECTDRAW lpDD; + LPDIRECTDRAWSURFACE lpddsOverlay, lpddsPrimary; + LPDIRECTDRAWSURFACE lpBackBuffer; + + DDCAPS capsDrv; + unsigned int uDestSizeAlign, uSrcSizeAlign; + DWORD dwUpdateFlags; + DDOVERLAYFX ovfx; + RECT rs,rd; + RECT m_oldrd; + RECT winRect; + + int overlay_color; + bool initing; + int yuy2_output, uyvy_output; + + void getRects(RECT *drs, RECT *drd, int fixmultimon=1) const; + + int m_fontsize; +}; + +extern OverlayVideoOutput overlay; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wmvdrm/vidutils.cpp b/Src/Plugins/Input/in_wmvdrm/vidutils.cpp new file mode 100644 index 00000000..6361afe3 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/vidutils.cpp @@ -0,0 +1,130 @@ +#include "main.h" +#include "api.h" +#include <multimon.h> + +#undef GetSystemMetrics +void getViewport(RECT *r, HWND wnd, int full, RECT *sr) +{ + POINT *p = NULL; + if (p || sr || wnd) + { + static int initted = 0; + static HMONITOR (WINAPI *Mfp)(POINT pt, DWORD dwFlags); + static HMONITOR (WINAPI *Mfr)(LPCRECT lpcr, DWORD dwFlags); + static HMONITOR (WINAPI *Mfw)(HWND wnd, DWORD dwFlags); + static BOOL (WINAPI *Gmi)(HMONITOR mon, LPMONITORINFOEX lpmi); + if (!initted) + { + HINSTANCE h = LoadLibraryW(L"user32.dll"); + if (h) + { + Mfp = (HMONITOR (WINAPI *)(POINT, DWORD)) GetProcAddress(h, "MonitorFromPoint"); + Mfr = (HMONITOR (WINAPI *)(LPCRECT, DWORD)) GetProcAddress(h, "MonitorFromRect"); + Mfw = (HMONITOR (WINAPI *)(HWND, DWORD)) GetProcAddress(h, "MonitorFromWindow"); + Gmi = (BOOL (WINAPI *)(HMONITOR, LPMONITORINFOEX)) GetProcAddress(h, "GetMonitorInfoW"); + } + initted = 1; + } + + if (Mfp && Mfr && Mfw && Gmi) + { + HMONITOR hm = NULL; + + if (sr) + hm = Mfr(sr, MONITOR_DEFAULTTONEAREST); + else if (wnd) + hm = Mfw(wnd, MONITOR_DEFAULTTONEAREST); + else if (p) + hm = Mfp(*p, MONITOR_DEFAULTTONEAREST); + + if (hm) + { + MONITORINFOEXW mi; + memset(&mi, 0, sizeof(mi)); + mi.cbSize = sizeof(mi); + + if (Gmi(hm, &mi)) + { + if (!full) + *r = mi.rcWork; + else + *r = mi.rcMonitor; + + return ; + } + } + } + } + if (full) + { // this might be borked =) + r->top = r->left = 0; + r->right = GetSystemMetrics(SM_CXSCREEN); + r->bottom = GetSystemMetrics(SM_CYSCREEN); + } + else + { + SystemParametersInfoW(SPI_GETWORKAREA, 0, r, 0); + } +} + +VideoConfig::VideoConfig() : group(0), +itemYV12(0), itemOverlay(0), itemVsync(0), itemDDraw(0) +{} + +bool VideoConfig::yv12() +{ + GetGroup(); + if (!itemYV12) + return false; + + return itemYV12->GetBool(); +} + +bool VideoConfig::overlays() +{ + GetGroup(); + if (!itemOverlay) + return true; // overlay by default + + return itemOverlay->GetBool(); +} + +bool VideoConfig::vsync() +{ + GetGroup(); + if (!itemVsync) + return false; // no vsync by default + + return itemVsync->GetBool(); +} + +bool VideoConfig::ddraw() +{ + GetGroup(); + if (!itemDDraw) + return true; + + return itemDDraw->GetBool(); +} + +void VideoConfig::GetGroup() +{ + if (group) + return ; + + // {2135E318-6919-4bcf-99D2-62BE3FCA8FA6} + static const GUID videoConfigGroupGUID = + { 0x2135e318, 0x6919, 0x4bcf, { 0x99, 0xd2, 0x62, 0xbe, 0x3f, 0xca, 0x8f, 0xa6 } }; + + group = AGAVE_API_CONFIG->GetGroup(videoConfigGroupGUID); + if (group) + { + itemYV12 = group->GetItem(L"YV12"); + itemOverlay = group->GetItem(L"overlay"); + itemVsync = group->GetItem(L"vsync"); + itemDDraw = group->GetItem(L"ddraw"); + } +} + + +VideoConfig config_video; diff --git a/Src/Plugins/Input/in_wmvdrm/vidutils.h b/Src/Plugins/Input/in_wmvdrm/vidutils.h new file mode 100644 index 00000000..34b83a90 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/vidutils.h @@ -0,0 +1,24 @@ +#ifndef NULLSOFT_IN_WMVDRM_VID_UTILS_H +#define NULLSOFT_IN_WMVDRM_VID_UTILS_H + +#include "../Agave/Config/ifc_configgroup.h" + +class VideoConfig +{ +public: + VideoConfig(); + bool yv12(); + bool overlays(); + bool vsync(); + bool ddraw(); + +private: + void GetGroup(); + + ifc_configgroup *group; + ifc_configitem *itemYV12, *itemOverlay, *itemVsync, *itemDDraw; +}; + +extern VideoConfig config_video; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/changes.txt b/Src/Plugins/Input/in_wv/changes.txt new file mode 100644 index 00000000..4a03afc1 --- /dev/null +++ b/Src/Plugins/Input/in_wv/changes.txt @@ -0,0 +1,7 @@ +* Removed incorrect manifest file which causes issues loading the plug-in on some systems
+* Now built with VS2008 so now depends on 5.57+ due to msvcr90.dll dependency (cuts size)
+* Changed localisation system to directly use api_language
+* Added %type%, %family% (used mainly by the installer) and %gain% to complement the other replaygain options
+* Removed config dialog as not applicable now with new defaults and also if we're following Winamp's settings
+* Changed some of the stringtable resource (adds new strings, removes deprecated strings and some minor tweaks)
+* Changed some of the options for the plug-in project (including using lng_generator directly)
diff --git a/Src/Plugins/Input/in_wv/in2.h b/Src/Plugins/Input/in_wv/in2.h new file mode 100644 index 00000000..7c712f3f --- /dev/null +++ b/Src/Plugins/Input/in_wv/in2.h @@ -0,0 +1,152 @@ +#ifndef NULLSOFT_WINAMP_IN2H +#define NULLSOFT_WINAMP_IN2H +// Input plugin interface + +#include "out.h" + +// If you want your input plugin to support unicode then define the following which will then +// adjust required functions to their unicode variants. This is supported from Winamp 5.3+. +#define IN_UNICODE 0x0F000000 + +#ifdef UNICODE_INPUT_PLUGIN +#define in_char wchar_t +#define IN_VER (IN_UNICODE | 0x100) +#else +#define in_char char +#define IN_VER 0x100 +#endif + +#define IN_MODULE_FLAG_USES_OUTPUT_PLUGIN 1 + +// By default Winamp assumes your input plugin wants to use Winamp's EQ, and doesn't do replay gain +// if you handle any of these yourself (EQ, Replay Gain adjustments), then set these flags accordingly + +// Set this if you want to implement your own EQ inplace of using Winamp's native implementation. +#define IN_MODULE_FLAG_EQ 2 + +// Set this if you adjusted volume for replay gain. For tracks with no replay gain metadata then you +// should clear this flag UNLESS you handle "non_replaygain" gain adjustment yourself then keep it. +#define IN_MODULE_FLAG_REPLAYGAIN 8 + +// Use this if you queried for the replay gain preamp parameter and used it. This is new to 5.54 clients. +#define IN_MODULE_FLAG_REPLAYGAIN_PREAMP 16 +typedef struct +{ + int version; // module type (IN_VER) + char *description; // description of module, with version string + + HWND hMainWindow; // Winamp's main window (filled in by winamp - is a valid HWND on 5.1+ clients) + HINSTANCE hDllInstance; // DLL instance handle (Also filled in by winamp) + + char *FileExtensions; // "mp3\0Layer 3 MPEG\0mp2\0Layer 2 MPEG\0mpg\0Layer 1 MPEG\0" + // May be altered from Config, so the user can select what they want + + int is_seekable; // is this stream seekable? + int UsesOutputPlug; // does this plug-in use the output plug-ins? (musn't ever change, ever :) + // note that this has turned into a "flags" field see IN_MODULE_FLAG_* + + void (*Config)(HWND hwndParent); // configuration dialog + void (*About)(HWND hwndParent); // about dialog + + void (*Init)(); // called at program init + void (*Quit)(); // called at program quit + + #define GETFILEINFO_TITLE_LENGTH 2048 + // If file == NULL then the currently playing file is used (assumes you've cached it as required) + void (*GetFileInfo)(const in_char *file, in_char *title, int *length_in_ms); + + #define INFOBOX_EDITED 0 + #define INFOBOX_UNCHANGED 1 + int (*InfoBox)(const in_char *file, HWND hwndParent); + + int (*IsOurFile)(const in_char *fn); // called before extension checks, to allow detection of mms://, etc + + // playback stuff + int (*Play)(const in_char *fn); // return zero on success, -1 on file-not-found, some other value on other (stopping winamp) error + void (*Pause)(); // pause stream + void (*UnPause)(); // unpause stream + int (*IsPaused)(); // ispaused? return 1 if paused, 0 if not + void (*Stop)(); // stop (unload) stream + + // time stuff + int (*GetLength)(); // get length in ms + int (*GetOutputTime)(); // returns current output time in ms. (usually returns outMod->GetOutputTime() + void (*SetOutputTime)(int time_in_ms); // seeks to point in stream (in ms). Usually you signal your thread to seek, which seeks and calls outMod->Flush().. + + // volume stuff + void (*SetVolume)(int volume); // from 0 to 255.. usually just call outMod->SetVolume + void (*SetPan)(int pan); // from -127 to 127.. usually just call outMod->SetPan + + // in-window builtin vis stuff + void (*SAVSAInit)(int maxlatency_in_ms, int srate); // call once in Play(). maxlatency_in_ms should be the value returned from outMod->Open() + // call after opening audio device with max latency in ms and samplerate + void (*SAVSADeInit)(); // call in Stop() + + // simple vis supplying mode + void (*SAAddPCMData)(void *PCMData, int nch, int bps, int timestamp); // sets the spec data directly from PCM data quick and easy way + // to get vis working :) needs at least 576 samples :) + + // advanced vis supplying mode, only use if you're cool. Use SAAddPCMData for most stuff. + int (*SAGetMode)(); // gets csa (the current type (4=ws,2=osc,1=spec)) use when calling SAAdd() + int (*SAAdd)(void *data, int timestamp, int csa); // sets the spec data, filled in by winamp + + // vis stuff (plug-in) + // simple vis supplying mode + void (*VSAAddPCMData)(void *PCMData, int nch, int bps, int timestamp); // sets the vis data directly from PCM data quick and easy way + // to get vis working :) needs at least 576 samples :) + + // advanced vis supplying mode, only use if you're cool. Use VSAAddPCMData for most stuff. + int (*VSAGetMode)(int *specNch, int *waveNch); // use to figure out what to give to VSAAdd + int (*VSAAdd)(void *data, int timestamp); // filled in by winamp, called by plug-in + + // call this in Play() to tell the vis plug-ins the current output params. + void (*VSASetInfo)(int srate, int nch); // <-- Correct (benski, dec 2005).. old declaration had the params backwards + + // dsp plug-in processing: + // (filled in by winamp, calld by input plug) + + // returns 1 if active (which means that the number of samples returned by dsp_dosamples could be + // greater than went in.. Use it to estimate if you'll have enough room in the output buffer + int (*dsp_isactive)(); + + // returns number of samples to output. This can be as much as twice numsamples. + // be sure to allocate enough buffer for samples, then. + int (*dsp_dosamples)(short int *samples, int numsamples, int bps, int nch, int srate); + + // eq stuff + void (*EQSet)(int on, char data[10], int preamp); // 0-64 each, 31 is +0, 0 is +12, 63 is -12. Do nothing to ignore. + + // info setting (filled in by winamp) + void (*SetInfo)(int bitrate, int srate, int stereo, int synched); // if -1, changes ignored? :) + + Out_Module *outMod; // filled in by winamp, optionally used :) +} In_Module; + +// These are the return values to be used with the uninstall plugin export function: +// __declspec(dllexport) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) +// which determines if Winamp can uninstall the plugin immediately or on winamp restart. +// If this is not exported then Winamp will assume an uninstall with reboot is the only way. +// +#define IN_PLUGIN_UNINSTALL_NOW 0x1 +#define IN_PLUGIN_UNINSTALL_REBOOT 0x0 +// +// Uninstall support was added from 5.0+ and uninstall now support from 5.5+ though note +// that it is down to you to ensure that if uninstall now is returned that it will not +// cause a crash i.e. don't use if you've been subclassing the main window. +// +// The HWND passed in the calling of winampUninstallPlugin(..) is the preference page HWND. +// + +// For a input plugin to be correctly detected by Winamp you need to ensure that +// the exported winampGetInModule2(..) is exported as an undecorated function +// e.g. +// #ifdef __cplusplus +// extern "C" { +// #endif +// __declspec(dllexport) In_Module *winampGetInModule2(){ return &plugin; } +// #ifdef __cplusplus +// } +// #endif +// + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/in_wv.cpp b/Src/Plugins/Input/in_wv/in_wv.cpp new file mode 100644 index 00000000..ddfef3d6 --- /dev/null +++ b/Src/Plugins/Input/in_wv/in_wv.cpp @@ -0,0 +1,2062 @@ +/* +** .WV input plug-in for WavPack +** Copyright (c) 2000 - 2006, Conifer Software, All Rights Reserved +*/ +#include <windows.h> +#include <fcntl.h> +#include <stdio.h> +#include <mmreg.h> +#include <msacm.h> +#include <math.h> +#include <sys/stat.h> +#include <io.h> +#include <strsafe.h> + +#include "in2.h" +#include "wavpack.h" +#include "resource.h" +#include "wasabi/winamp/wa_ipc.h" +#include "wasabi/wasabi.h" +#include "wasabi/nu/autochar.h" +#include "wasabi/nu/autowide.h" + +#define fileno _fileno + +static float calculate_gain(WavpackContext *wpc, bool allowDefault=true); + +#define PLUGIN_VERSION "2.8.1" +//#define DEBUG_CONSOLE +//#define ANSI_METADATA +#define UNICODE_METADATA +//#define OLD_INFO_DIALOG + +// post this to the main window at end of file (after playback as stopped) +#define WM_WA_MPEG_EOF WM_USER+2 + +#define MAX_NCH 8 + +static struct wpcnxt { + WavpackContext *wpc; // WavPack context provided by library + float play_gain; // playback gain (for replaygain support) + //int soft_clipping; // soft clipping active for playback + int output_bits; // 16, 24, or 32 bits / sample + long sample_buffer[576*MAX_NCH*2]; // sample buffer + float error [MAX_NCH]; // error term for noise shaping + char lastfn[MAX_PATH]; // filename stored for comparisons only + wchar_t w_lastfn[MAX_PATH]; // w_filename stored for comparisons only + FILE *wv_id, *wvc_id; // file pointer when we use reader callbacks +} curr, edit, info; + +int decode_pos_ms; // current decoding position, in milliseconds +int paused; // are we paused? +int seek_needed; // if != -1, it is the point that the decode thread should seek to, in ms. + +#define ALLOW_WVC 0x1 +#define REPLAYGAIN_TRACK 0x2 +#define REPLAYGAIN_ALBUM 0x4 +#define SOFTEN_CLIPPING 0x8 +#define PREVENT_CLIPPING 0x10 + +#define ALWAYS_16BIT 0x20 // new flags added for version 2.5 +#define ALLOW_MULTICHANNEL 0x40 +#define REPLAYGAIN_24BIT 0x80 + +int config_bits = ALLOW_WVC | ALLOW_MULTICHANNEL; // all configuration goes here + +int killDecodeThread=0; // the kill switch for the decode thread +HANDLE thread_handle=INVALID_HANDLE_VALUE; // the handle to the decode thread + +DWORD WINAPI __stdcall DecodeThread(void *b); // the decode thread procedure + +static api_service* WASABI_API_SVC; +static api_language* WASABI_API_LNG; +static api_config *AGAVE_API_CONFIG; +static api_memmgr *WASABI_API_MEMMGR; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; + +static char file_extensions[128] = {"WV\0WavPack Files (*.WV)\0"}; + +// function definitions for the In_Module stucture +void about (HWND hwndParent); +void init(); +void quit(); +void getfileinfo(const char *filename, char *title, int *length_in_ms); +int infoDlg(const char *fn, HWND hwnd); +int isourfile(const char *fn); +int play(const char *fn); +void pause(); +void unpause(); +int ispaused(); +void stop(); +int getlength(); +int getoutputtime(); +void setoutputtime(int time_in_ms); +void setvolume(int volume); +void setpan(int pan); +void eq_set(int on, char data [10], int preamp); + +In_Module mod = // the output module +{ + IN_VER, + "WavPack Decoder v"PLUGIN_VERSION, + 0, // hMainWindow + 0, // hDllInstance + file_extensions, + 1, // is_seekable + 1, // uses output + about, + about, + init, + quit, + getfileinfo, + infoDlg, + isourfile, + play, + pause, + unpause, + ispaused, + stop, + getlength, + getoutputtime, + setoutputtime, + setvolume, + setpan, + 0,0,0,0,0,0,0,0,0, // vis stuff + 0,0, // dsp + eq_set, + NULL, // setinfo + 0 // out_mod +}; + +static BOOL CALLBACK WavPackDlgProc(HWND, UINT, WPARAM, LPARAM); + +extern long dump_alloc (void); + +int DoAboutMessageBox(HWND parent, wchar_t* title, wchar_t* message) +{ + MSGBOXPARAMSW msgbx = {sizeof(MSGBOXPARAMSW),0}; + msgbx.lpszText = message; + msgbx.lpszCaption = title; + msgbx.lpszIcon = MAKEINTRESOURCEW(102); + msgbx.hInstance = GetModuleHandle(0); + msgbx.dwStyle = MB_USERICON; + msgbx.hwndOwner = parent; + return MessageBoxIndirectW(&msgbx); +} + +void about(HWND hwndParent) +{ + wchar_t about_string[512]; +#ifdef DEBUG_ALLOC + sprintf (about_string, "alloc_count = %d", dump_alloc ()); +#else + StringCchPrintfW(about_string, 512, WASABI_API_LNGSTRINGW(IDS_ABOUT_MESSAGE), PLUGIN_VERSION, "1998-2010", __DATE__); +#endif + DoAboutMessageBox(hwndParent, WASABI_API_LNGSTRINGW(IDS_ABOUT), about_string); +} + +void init() /* any one-time initialization goes here (configuration reading, etc) */ +{ + if (mod.hMainWindow) + { + // load all of the required wasabi services from the winamp client + WASABI_API_SVC = (api_service *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GET_API_SERVICE); + if (WASABI_API_SVC == (api_service *)1) + WASABI_API_SVC=0; + + WASABI_API_SVC->service_register(&albumArtFactory); + } + ServiceBuild(AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceBuild(WASABI_API_LNG, languageApiGUID); + ServiceBuild(WASABI_API_MEMMGR, memMgrApiServiceGuid); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(mod.hDllInstance,InWvLangGuid); + + static char szDescription[256]; + StringCchPrintfA(szDescription,256,WASABI_API_LNGSTRING(IDS_DESCRIPTION),PLUGIN_VERSION); + mod.description = szDescription; + + // set the file extension to the localised version + char tmp [64], *tmp_ptr = tmp, *fex_ptr = file_extensions; + WASABI_API_LNGSTRING_BUF(IDS_FILETYPE, tmp, sizeof (tmp)); + + *fex_ptr++ = 'W'; + *fex_ptr++ = 'V'; + *fex_ptr++ = 0; + + while (*tmp_ptr) + *fex_ptr++ = *tmp_ptr++; + + *fex_ptr++ = 0; + *fex_ptr++ = 0; +} + +#ifdef DEBUG_CONSOLE + +HANDLE debug_console=INVALID_HANDLE_VALUE; // debug console + +void debug_write (char *str) +{ + static int cant_debug; + + if (cant_debug) + return; + + if (debug_console == INVALID_HANDLE_VALUE) { + AllocConsole (); + +#if 1 + debug_console = GetStdHandle (STD_OUTPUT_HANDLE); +#else + debug_console = CreateConsoleScreenBuffer (GENERIC_WRITE, FILE_SHARE_WRITE, + NULL, CONSOLE_TEXTMODE_BUFFER, NULL); +#endif + + if (debug_console == INVALID_HANDLE_VALUE) { + MessageBox(NULL, "Can't get a console handle", "WavPack",MB_OK); + cant_debug = 1; + return; + } + else if (!SetConsoleActiveScreenBuffer (debug_console)) { + MessageBox(NULL, "Can't activate console buffer", "WavPack",MB_OK); + cant_debug = 1; + return; + } + } + + WriteConsole (debug_console, str, strlen (str), NULL, NULL); +} + +#endif + +void quit() /* one-time deinit, such as memory freeing */ +{ +#ifdef DEBUG_CONSOLE + if (debug_console != INVALID_HANDLE_VALUE) { + FreeConsole (); + + if (debug_console != GetStdHandle (STD_OUTPUT_HANDLE)) + CloseHandle (debug_console); + + debug_console = INVALID_HANDLE_VALUE; + } +#endif + ServiceRelease(AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceRelease(WASABI_API_LNG, languageApiGUID); + ServiceRelease(WASABI_API_MEMMGR, memMgrApiServiceGuid); + WASABI_API_SVC->service_deregister(&albumArtFactory); +} + +// used for detecting URL streams.. unused here. strncmp(fn,"http://",7) to detect HTTP streams, etc +int isourfile(const char *fn) +{ + return 0; +} + +int play(const char *fn) +{ + int num_chans, sample_rate; + char error[128]; + int maxlatency; + int thread_id; + int open_flags; + +#ifdef DEBUG_CONSOLE + sprintf (error, "play (%s)\n", fn); + debug_write (error); +#endif + + open_flags = OPEN_TAGS | OPEN_NORMALIZE; + + if (config_bits & ALLOW_WVC) + open_flags |= OPEN_WVC; + + if (!(config_bits & ALLOW_MULTICHANNEL)) + open_flags |= OPEN_2CH_MAX; + + curr.wpc = WavpackOpenFileInput(fn, error, open_flags, 0); + + if (!curr.wpc) // error opening file, just return error + return -1; + + num_chans = WavpackGetReducedChannels(curr.wpc); + sample_rate = WavpackGetSampleRate(curr.wpc); + curr.output_bits = WavpackGetBitsPerSample(curr.wpc) > 16 ? 24 : 16; + + if (config_bits & ALWAYS_16BIT) + curr.output_bits = 16; + else if ((config_bits & (REPLAYGAIN_TRACK | REPLAYGAIN_ALBUM)) && + (config_bits & REPLAYGAIN_24BIT)) + curr.output_bits = 24; + + if (num_chans > MAX_NCH) // don't allow too many channels! + { + WavpackCloseFile(curr.wpc); + return -1; + } + + curr.play_gain = calculate_gain(curr.wpc); + lstrcpyn(curr.lastfn, fn, MAX_PATH); + + paused = 0; + decode_pos_ms = 0; + seek_needed = -1; + + maxlatency = mod.outMod->Open(sample_rate, num_chans, curr.output_bits, -1, -1); + + if (maxlatency < 0) // error opening device + { + curr.wpc = WavpackCloseFile(curr.wpc); + return -1; + } + + // dividing by 1000 for the first parameter of setinfo makes it + // display 'H'... for hundred.. i.e. 14H Kbps. + + mod.SetInfo(0, (sample_rate + 500) / 1000, num_chans, 1); + + // initialize vis stuff + + mod.SAVSAInit(maxlatency, sample_rate); + mod.VSASetInfo(sample_rate, num_chans); + + mod.outMod->SetVolume(-666); // set the output plug-ins default volume + + killDecodeThread=0; + + thread_handle = (HANDLE)CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE) DecodeThread, (void *) &killDecodeThread, 0, (LPDWORD)&thread_id); + + if (SetThreadPriority(thread_handle, THREAD_PRIORITY_HIGHEST) == 0) { + curr.wpc = WavpackCloseFile(curr.wpc); + return -1; + } + + return 0; +} + +void pause() +{ +#ifdef DEBUG_CONSOLE + debug_write ("pause ()\n"); +#endif + + paused = 1; + mod.outMod->Pause(1); +} + +void unpause() +{ +#ifdef DEBUG_CONSOLE + debug_write ("unpause ()\n"); +#endif + + paused = 0; + mod.outMod->Pause(0); +} + +int ispaused() +{ + return paused; +} + +void stop() +{ +#ifdef DEBUG_CONSOLE + debug_write ("stop ()\n"); +#endif + + if (thread_handle != INVALID_HANDLE_VALUE) + { + killDecodeThread = 1; + + if (WaitForSingleObject(thread_handle, INFINITE) == WAIT_TIMEOUT) + { + MessageBox(mod.hMainWindow,"error asking thread to die!\n", "error killing decode thread", 0); + TerminateThread(thread_handle,0); + } + + CloseHandle(thread_handle); + thread_handle = INVALID_HANDLE_VALUE; + } + + if (curr.wpc) + curr.wpc = WavpackCloseFile(curr.wpc); + + mod.outMod->Close(); + mod.SAVSADeInit(); +} + +int getlength() +{ + return (int)(WavpackGetNumSamples (curr.wpc) * 1000.0 / WavpackGetSampleRate (curr.wpc)); +} + +int getoutputtime() +{ + if (seek_needed == -1) + return decode_pos_ms + (mod.outMod->GetOutputTime () - mod.outMod->GetWrittenTime ()); + else + return seek_needed; +} + +void setoutputtime (int time_in_ms) +{ +#ifdef DEBUG_CONSOLE + char str [40]; + sprintf (str, "setoutputtime (%d)\n", time_in_ms); + debug_write (str); +#endif + + seek_needed = time_in_ms; +} + +void setvolume (int volume) +{ + mod.outMod->SetVolume(volume); +} + +void setpan (int pan) +{ + mod.outMod->SetPan(pan); +} + +static void generate_format_string(WavpackContext *wpc, wchar_t *string, int maxlen, int wide); +static void AnsiToUTF8(char *string, int len); +static int UTF8ToWideChar(const char *pUTF8, wchar_t *pWide); +static void UTF8ToAnsi(char *string, int len); + +int infoDlg(const char *fn, HWND hwnd) +{ +#ifdef OLD_INFO_DIALOG + char string[2048]; + wchar_t w_string[2048]; + WavpackContext *wpc; + int open_flags; + + open_flags = OPEN_TAGS | OPEN_NORMALIZE; + + if (config_bits & ALLOW_WVC) + open_flags |= OPEN_WVC; + + if (!(config_bits & ALLOW_MULTICHANNEL)) + open_flags |= OPEN_2CH_MAX; + + wpc = WavpackOpenFileInput(fn, string, open_flags, 0); + + if (wpc) + { + int mode = WavpackGetMode(wpc); + + //generate_format_string(wpc, string, sizeof (string), 1); + wchar_t *temp = (wchar_t *)malloc(sizeof(string) * sizeof(wchar_t)); + generate_format_string(wpc, temp, sizeof(string), 0); + lstrcpyn(string, AutoChar(temp, CP_UTF8), sizeof(string)); + free(temp); + + if (WavpackGetMode(wpc) & MODE_VALID_TAG) + { + char value [128]; + + if (config_bits & (REPLAYGAIN_TRACK | REPLAYGAIN_ALBUM)) + { + int local_clipping = 0; + float local_gain; + + local_gain = calculate_gain(wpc); + + if (local_gain != 1.0) + StringCchPrintf(string + strlen (string), 2048, "Gain: %+.2f dB %s\n", + log10 (local_gain) * 20.0, local_clipping ? "(w/soft clipping)" : ""); + } + + if (WavpackGetTagItem(wpc, "title", value, sizeof (value))) + { + if (!(mode & MODE_APETAG)) + AnsiToUTF8(value, sizeof (value)); + + StringCchPrintf(string + strlen (string), 2048, "\nTitle: %s", value); + } + + if (WavpackGetTagItem(wpc, "artist", value, sizeof (value))) + { + if (!(mode & MODE_APETAG)) + AnsiToUTF8(value, sizeof (value)); + + StringCchPrintf(string + strlen (string), 2048, "\nArtist: %s", value); + } + + if (WavpackGetTagItem (wpc, "album", value, sizeof (value))) + { + if (!(mode & MODE_APETAG)) + AnsiToUTF8(value, sizeof (value)); + + StringCchPrintf (string + strlen (string), 2048, "\nAlbum: %s", value); + } + + if (WavpackGetTagItem (wpc, "genre", value, sizeof (value))) + { + if (!(mode & MODE_APETAG)) + AnsiToUTF8(value, sizeof (value)); + + StringCchPrintf(string + strlen (string), 2048, "\nGenre: %s", value); + } + + if (WavpackGetTagItem (wpc, "comment", value, sizeof (value))) + { + if (!(mode & MODE_APETAG)) + AnsiToUTF8(value, sizeof (value)); + + StringCchPrintf(string + strlen (string), 2048, "\nComment: %s", value); + } + + if (WavpackGetTagItem(wpc, "year", value, sizeof (value))) + StringCchPrintf(string + strlen (string), 2048, "\nYear: %s", value); + + if (WavpackGetTagItem(wpc, "track", value, sizeof (value))) + StringCchPrintf(string + strlen (string), 2048, "\nTrack: %s", value); + + StringCchCat(string, 2048, "\n"); + } + + UTF8ToWideChar(string, w_string); + MessageBoxW(hwnd, w_string, L"WavPack File Info Box", MB_OK); + wpc = WavpackCloseFile(wpc); + } + else + MessageBox(hwnd, string, "WavPack Decoder", MB_OK); + + return 0; +#else + return 1; +#endif +} + +void getfileinfo(const char *filename, char *title, int *length_in_ms) +{ + if (!filename || !*filename) // currently playing file + { + + if (length_in_ms) + *length_in_ms = getlength (); + + if (title) + { + if (WavpackGetTagItem(curr.wpc, "title", NULL, 0)) + { + char art [128], ttl [128]; + + WavpackGetTagItem(curr.wpc, "title", ttl, sizeof (ttl)); + + if (WavpackGetMode(curr.wpc) & MODE_APETAG) + UTF8ToAnsi(ttl, sizeof (ttl)); + + if (WavpackGetTagItem(curr.wpc, "artist", art, sizeof (art))) + { + if (WavpackGetMode(curr.wpc) & MODE_APETAG) + UTF8ToAnsi(art, sizeof (art)); + + StringCchPrintf(title, GETFILEINFO_TITLE_LENGTH, "%s - %s", art, ttl); + } + else + lstrcpyn(title, ttl, GETFILEINFO_TITLE_LENGTH); + } + else + { + char *p = curr.lastfn + strlen (curr.lastfn); + + while (*p != '\\' && p >= curr.lastfn) + p--; + + lstrcpyn(title, ++p, GETFILEINFO_TITLE_LENGTH); + } + } + } + else // some other file + { + WavpackContext *wpc; + char error [128]; + int open_flags; + + if (length_in_ms) + *length_in_ms = -1000; + + if (title) + *title = 0; + + open_flags = OPEN_TAGS | OPEN_NORMALIZE; + + if (config_bits & ALLOW_WVC) + open_flags |= OPEN_WVC; + + if (!(config_bits & ALLOW_MULTICHANNEL)) + open_flags |= OPEN_2CH_MAX; + + wpc = WavpackOpenFileInput(filename, error, open_flags, 0); + + if (wpc) + { + if (length_in_ms) + *length_in_ms = (int)(WavpackGetNumSamples(wpc) * 1000.0 / WavpackGetSampleRate(wpc)); + + if (title && WavpackGetTagItem(wpc, "title", NULL, 0)) + { + char art [128], ttl [128]; + + WavpackGetTagItem(wpc, "title", ttl, sizeof (ttl)); + + if (WavpackGetMode(wpc) & MODE_APETAG) + UTF8ToAnsi(ttl, sizeof (ttl)); + + if (WavpackGetTagItem(wpc, "artist", art, sizeof (art))) + { + if (WavpackGetMode(wpc) & MODE_APETAG) + UTF8ToAnsi(art, sizeof (art)); + + StringCchPrintf(title, GETFILEINFO_TITLE_LENGTH, "%s - %s", art, ttl); + } + else + lstrcpyn(title, ttl, GETFILEINFO_TITLE_LENGTH); + } + + wpc = WavpackCloseFile(wpc); + } + + if (title && !*title) + { + char *p = (char*)filename + strlen (filename); + + while (*p != '\\' && p >= filename) p--; + lstrcpyn(title, ++p, GETFILEINFO_TITLE_LENGTH); + } + } +} + +void eq_set(int on, char data [10], int preamp) +{ + // most plug-ins can't even do an EQ anyhow.. I'm working on writing + // a generic PCM EQ, but it looks like it'll be a little too CPU + // consuming to be useful :) +} + +static int read_samples(struct wpcnxt *cnxt, int num_samples); + +DWORD WINAPI __stdcall DecodeThread(void *b) +{ + int num_chans, sample_rate; + int done = 0; + + memset(curr.error, 0, sizeof (curr.error)); + num_chans = WavpackGetReducedChannels(curr.wpc); + sample_rate = WavpackGetSampleRate(curr.wpc); + + while (!*((int *)b) ) + { + if (seek_needed != -1) + { + int seek_position = seek_needed; + int bc = 0; + + seek_needed = -1; + + if (seek_position > getlength() - 1000 && getlength() > 1000) + seek_position = getlength() - 1000; // don't seek to last second + + mod.outMod->Flush(decode_pos_ms = seek_position); + + if (WavpackSeekSample(curr.wpc, (int)(sample_rate / 1000.0 * seek_position))) { + decode_pos_ms = (int)(WavpackGetSampleIndex(curr.wpc) * 1000.0 / sample_rate); + mod.outMod->Flush(decode_pos_ms); + continue; + } + else + done = 1; + } + + if (done) { + mod.outMod->CanWrite(); + + if (!mod.outMod->IsPlaying()) { + PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return 0; + } + + Sleep(10); + } + else if (mod.outMod->CanWrite() >= ((576 * num_chans * (curr.output_bits / 8)) << (mod.dsp_isactive () ? 1 : 0))) + { + int tsamples = read_samples (&curr, 576) * num_chans; + int tbytes = tsamples * (curr.output_bits/8); + + if (tsamples) + { + mod.SAAddPCMData((char *) curr.sample_buffer, num_chans, curr.output_bits, decode_pos_ms); + mod.VSAAddPCMData((char *) curr.sample_buffer, num_chans, curr.output_bits, decode_pos_ms); + decode_pos_ms = (int)(WavpackGetSampleIndex(curr.wpc) * 1000.0 / sample_rate); + + if (mod.dsp_isactive()) + tbytes = mod.dsp_dosamples ((short *) curr.sample_buffer, + tsamples / num_chans, curr.output_bits, num_chans, sample_rate) * (num_chans * (curr.output_bits/8)); + + mod.outMod->Write ((char *) curr.sample_buffer, tbytes); + } + else + done = 1; + } + else + { + mod.SetInfo((int) ((WavpackGetInstantBitrate (curr.wpc) + 500.0) / 1000.0), -1, -1, 1); + Sleep(20); + } + } + + return 0; +} + +/********* These functions provide the "transcoding" mode of winamp. *********/ + +extern "C" __declspec (dllexport) intptr_t winampGetExtendedRead_open (const char *fn, int *size, int *bps, int *nch, int *srate) +{ + struct wpcnxt *cnxt = (struct wpcnxt *)malloc(sizeof (struct wpcnxt)); + int num_chans, sample_rate, open_flags; + char error[128]; + +#ifdef DEBUG_CONSOLE + sprintf (error, "Read_open (%s)\n", fn); + debug_write (error); +#endif + + if (!cnxt) + return 0; + + memset(cnxt, 0, sizeof (struct wpcnxt)); + open_flags = OPEN_NORMALIZE | OPEN_WVC; + + if (!(config_bits & ALLOW_MULTICHANNEL) || *nch == 2) + open_flags |= OPEN_2CH_MAX; + + if (config_bits & (REPLAYGAIN_TRACK | REPLAYGAIN_ALBUM)) + open_flags |= OPEN_TAGS; + + cnxt->wpc = WavpackOpenFileInput(fn, error, open_flags, 0); + + if (!cnxt->wpc) // error opening file, just return error + { + free (cnxt); + return 0; + } + + num_chans = WavpackGetReducedChannels(cnxt->wpc); + sample_rate = WavpackGetSampleRate(cnxt->wpc); + + if (num_chans > MAX_NCH) + { + WavpackCloseFile(cnxt->wpc); + free (cnxt); + return 0; + } + + if (*bps != 16 && *bps != 24 && *bps != 32) + { + cnxt->output_bits = WavpackGetBitsPerSample(cnxt->wpc) > 16 ? 24 : 16; + + if (config_bits & ALWAYS_16BIT) + cnxt->output_bits = 16; + else if ((config_bits & (REPLAYGAIN_TRACK | REPLAYGAIN_ALBUM)) && + (config_bits & REPLAYGAIN_24BIT)) + cnxt->output_bits = 24; + } + else + cnxt->output_bits = *bps; + + if (num_chans > MAX_NCH) // don't allow too many channels! + { + WavpackCloseFile(cnxt->wpc); + free (cnxt); + return 0; + } + + *nch = num_chans; + *srate = sample_rate; + *bps = cnxt->output_bits; + *size = WavpackGetNumSamples(cnxt->wpc) * (*bps / 8) * (*nch); + + cnxt->play_gain = calculate_gain(cnxt->wpc); + +#ifdef DEBUG_CONSOLE + sprintf (error, "Read_open success! nch=%d, srate=%d, bps=%d, size=%d\n", + *nch, *srate, *bps, *size); + debug_write (error); +#endif + + return (intptr_t) cnxt; +} + +extern "C" __declspec (dllexport) intptr_t winampGetExtendedRead_getData (intptr_t handle, char *dest, int len, int *killswitch) +{ + struct wpcnxt *cnxt = (struct wpcnxt *)handle; + int num_chans = WavpackGetReducedChannels(cnxt->wpc); + int bytes_per_sample = num_chans * cnxt->output_bits / 8; + int used = 0; + +#ifdef DEBUG_CONSOLE + char error [128]; +#endif + + while (used < len && !*killswitch) + { + int nsamples = (len - used) / bytes_per_sample, tsamples; + + if (!nsamples) + break; + else if (nsamples > 576) + nsamples = 576; + + tsamples = read_samples(cnxt, nsamples) * num_chans; + + if (tsamples) + { + int tbytes = tsamples * (cnxt->output_bits/8); + + memcpy (dest + used, cnxt->sample_buffer, tbytes); + used += tbytes; + } + else + break; + } + +#ifdef DEBUG_CONSOLE + sprintf (error, "Read_getData (%d), actualy read %d\n", len, used); + debug_write (error); +#endif + + return used; +} + +extern "C" __declspec (dllexport) int winampGetExtendedRead_setTime (intptr_t handle, int millisecs) +{ + struct wpcnxt *cnxt = (struct wpcnxt *) handle; + int sample_rate = WavpackGetSampleRate(cnxt->wpc); + + return WavpackSeekSample(cnxt->wpc, (int)(sample_rate / 1000.0 * millisecs)); +} + +extern "C" __declspec (dllexport) void winampGetExtendedRead_close (intptr_t handle) +{ + struct wpcnxt *cnxt = (struct wpcnxt *) handle; + +#ifdef DEBUG_CONSOLE + char error [128]; + + sprintf (error, "Read_close ()\n"); + debug_write (error); +#endif + + WavpackCloseFile(cnxt->wpc); + free (cnxt); +} + +/* This is a generic function to read WavPack samples and convert them to a + * form usable by winamp. It includes conversion of any WavPack format + * (including ieee float) to 16, 24, or 32-bit integers (with noise shaping + * for the 16-bit case) and replay gain implementation (with optional soft + * clipping). It is used by both the regular "play" code and the newer + * transcoding functions. + * + * The num_samples parameter is the number of "composite" samples to + * convert and is limited currently to 576 samples for legacy reasons. The + * return value is the number of samples actually converted and will be + * equal to the number requested unless an error occurs or the end-of-file + * is encountered. The converted samples are stored (interleaved) at + * cnxt->sample_buffer[]. + */ + +static int read_samples (struct wpcnxt *cnxt, int num_samples) +{ + int num_chans = WavpackGetReducedChannels(cnxt->wpc), samples, tsamples; + + samples = WavpackUnpackSamples(cnxt->wpc, (int32_t*) cnxt->sample_buffer, num_samples); + tsamples = samples * num_chans; + + if (tsamples) + { + if (!(WavpackGetMode(cnxt->wpc) & MODE_FLOAT)) + { + float scaler = (float) (1.0 / ((unsigned long) 1 << (WavpackGetBytesPerSample(cnxt->wpc) * 8 - 1))); + float *fptr = (float *) cnxt->sample_buffer; + long *lptr = cnxt->sample_buffer; + int cnt = tsamples; + + while (cnt--) + *fptr++ = *lptr++ * scaler; + } + + if (cnxt->play_gain != 1.0) + { + float *fptr = (float *) cnxt->sample_buffer; + int cnt = tsamples; + double outval; + + while (cnt--) + { + outval = *fptr * cnxt->play_gain; + + /*if (cnxt->soft_clipping) + { + if (outval > 0.75) + outval = 1.0 - (0.0625 / (outval - 0.5)); + else if (outval < -0.75) + outval = -1.0 - (0.0625 / (outval + 0.5)); + }*/ + + *fptr++ = (float) outval; + } + } + + if (cnxt->output_bits == 16) + { + float *fptr = (float *) cnxt->sample_buffer; + short *sptr = (short *) cnxt->sample_buffer; + int cnt = samples, ch; + + while (cnt--) + for (ch = 0; ch < num_chans; ++ch) + { + int dst; + + *fptr -= cnxt->error [ch]; + + if (*fptr >= 1.0) + dst = 32767; + else if (*fptr <= -1.0) + dst = -32768; + else + dst = (int) floor (*fptr * 32768.0); + + cnxt->error [ch] = (float)(dst / 32768.0 - *fptr++); + *sptr++ = dst; + } + } + else if (cnxt->output_bits == 24) + { + unsigned char *cptr = (unsigned char *) cnxt->sample_buffer; + float *fptr = (float *) cnxt->sample_buffer; + int cnt = tsamples; + long outval; + + while (cnt--) { + if (*fptr >= 1.0) + outval = 8388607; + else if (*fptr <= -1.0) + outval = -8388608; + else + outval = (int) floor (*fptr * 8388608.0); + + *cptr++ = (unsigned char) outval; + *cptr++ = (unsigned char) (outval >> 8); + *cptr++ = (unsigned char) (outval >> 16); + fptr++; + } + } + else if (cnxt->output_bits == 32) + { + float *fptr = (float *) cnxt->sample_buffer; + long *sptr = (long *) cnxt->sample_buffer; + int cnt = tsamples; + + while (cnt--) + { + if (*fptr >= 1.0) + *sptr++ = 8388607 << 8; + else if (*fptr <= -1.0) + *sptr++ = -8388608 << 8; + else + *sptr++ = ((int) floor (*fptr * 8388608.0)) << 8; + + fptr++; + } + } + } + + return samples; +} + +extern "C" __declspec (dllexport) In_Module * winampGetInModule2() +{ + return &mod; +} + +// This code provides an interface between the reader callback mechanism that +// WavPack uses internally and the standard fstream C library. + +static int32_t read_bytes(void *id, void *data, int32_t bcount) +{ + FILE *file = id ? *(FILE**)id : NULL; + + if (file) + return (int32_t) fread(data, 1, bcount, file); + else + return 0; +} + +static uint32_t get_pos(void *id) +{ + FILE *file = id ? *(FILE**)id : NULL; + + if (file) + return ftell(file); + else + return -1; +} + +static int set_pos_abs(void *id, uint32_t pos) +{ + FILE *file = id ? *(FILE**)id : NULL; + + if (file) + return fseek(file, pos, SEEK_SET); + else + return 0; +} + +static int set_pos_rel(void *id, int32_t delta, int mode) +{ + FILE *file = id ? *(FILE**)id : NULL; + + if (file) + return fseek(file, delta, mode); + else + return -1; +} + +static int push_back_byte(void *id, int c) +{ + FILE *file = id ? *(FILE**)id : NULL; + + if (file) + return ungetc(c, file); + else + return EOF; +} + +static uint32_t get_length(void *id) +{ + FILE *file = id ? *(FILE**)id : NULL; + struct stat statbuf; + + if (!file || fstat (fileno (file), &statbuf) || !(statbuf.st_mode & S_IFREG)) + return 0; + else + return statbuf.st_size; +} + +static int can_seek(void *id) +{ + FILE *file = id ? *(FILE**)id : NULL; + struct stat statbuf; + + return file && !fstat (fileno (file), &statbuf) && (statbuf.st_mode & S_IFREG); +} + +static int32_t write_bytes(void *id, void *data, int32_t bcount) +{ + FILE *file = id ? *(FILE**)id : NULL; + + if (file) + return (int32_t) fwrite (data, 1, bcount, file); + else + return 0; +} + +static WavpackStreamReader freader = { + read_bytes, get_pos, set_pos_abs, + set_pos_rel, push_back_byte, + get_length, can_seek, write_bytes +}; + +/* These functions provide UNICODE support for the winamp media library */ + +static int metadata_we_can_write(const char *metadata); + +static void close_context(struct wpcnxt *cxt) +{ + if (cxt->wpc) + WavpackCloseFile(cxt->wpc); + + if (cxt->wv_id) + fclose(cxt->wv_id); + + if (cxt->wvc_id) + fclose(cxt->wvc_id); + + memset(cxt, 0, sizeof (*cxt)); +} + +#ifdef ANSI_METADATA + +extern "C" __declspec (dllexport) int winampGetExtendedFileInfo(char *filename, char *metadata, char *ret, int retlen) +{ + int open_flags = OPEN_TAGS; + char error[128]; + int retval = 0; + +#ifdef DEBUG_CONSOLE + sprintf (error, "winampGetExtendedFileInfo (%s)\n", metadata); + debug_write (error); +#endif + + if (!_stricmp(metadata, "type")) + { + ret[0] = '0'; + ret[1] = 0; + return 1; + } + else if (!_stricmp(metadata, "family")) + { + int len; + const char *p; + if (!filename || !filename[0]) return 0; + len = lstrlen(filename); + if (len < 3 || '.' != filename[len - 3]) return 0; + p = &filename[len - 2]; + if (!_stricmp(p, "wv")) { WASABI_API_LNGSTRING_BUF(IDS_FAMILY_STRING, ret, retlen); return 1; } + return 0; + } + + if (!filename || !*filename) + return retval; + + if (!_stricmp(metadata, "length")) { /* even if no file, return a 1 and write "0" */ + StringCchPrintf(ret, retlen, "%d", 0); + retval = 1; + } + + if (!info.wpc || strcmp(filename, info.lastfn) || !_stricmp(metadata, "formatinformation")) + { + close_context(&info); + + if (!(info.wv_id = fopen(filename, "rb"))) + return retval; + + if (config_bits & ALLOW_WVC) + { + int length = strlen(filename) + 10; + char *wvc_name = (char *)malloc(length); + + if (wvc_name) + { + lstrcpyn(wvc_name, filename, length); + StringCchCat(wvc_name, length, "c"); + info.wvc_id = fopen(wvc_name, "rb"); + free(wvc_name); + } + } + + info.wpc = WavpackOpenFileInputEx(&freader, &info.wv_id, info.wvc_id ? &info.wvc_id : NULL, error, open_flags, 0); + + if (!info.wpc) + { + close_context(&info); + return retval; + } + + lstrcpyn(info.lastfn, filename, MAX_PATH); + info.w_lastfn [0] = 0; + } + + if (!_stricmp(metadata, "formatinformation")) + { + wchar_t *temp = (wchar_t *)malloc(retlen * sizeof(wchar_t)); + generate_format_string(info.wpc, temp, retlen, 0); + lstrcpyn(ret, AutoChar(temp), retlen); + free(temp); + retval = 1; + } + else if (!_stricmp(metadata, "length")) + { + StringCchPrintf(ret, retlen, "%d", (int)(WavpackGetNumSamples(info.wpc) * 1000.0 / WavpackGetSampleRate(info.wpc))); + retval = 1; + } + else if (!_stricmp(metadata, "lossless")) + { + StringCchPrintf (ret, retlen, "%d", (WavpackGetMode(info.wpc) & MODE_LOSSLESS) ? 1 : 0); + retval = 1; + } + else if (!_stricmp(metadata, "numsamples")) + { + StringCchPrintf(ret, retlen, "%d", WavpackGetNumSamples(info.wpc)); + retval = 1; + } + else if (!_stricmp(metadata, "mime")) + { + lstrcpyn(ret, L"audio/x-wavpack", retlen); + retval = 1; + } + else if (!_stricmp(metadata, "gain")) + { + StringCchPrintf(ret, retlen, "%-+.2f dB", calculate_gain(info.wpc, false)); + retval = 1; + } + else if (WavpackGetTagItem(info.wpc, metadata, ret, retlen)) + { + if (!_stricmp(metadata, "rating")) + { + int rating = atoi(ret); + // appears to be generally 0-5 or 0-100 + if (rating > 10) { + rating /= 20; + } + // or maybe we're dealing with a 1-10 range + else if (rating > 5) { + rating /= 2; + } + // otherwise it is hopefully in the 0-5 range + + StringCchPrintf(ret, retlen, "%u", rating); + } + else + { + if (WavpackGetMode(info.wpc) & MODE_APETAG) + { + UTF8ToAnsi(ret, retlen); + } + } + + retval = 1; + } + else if (metadata_we_can_write(metadata)) + { + if (retlen) + *ret = 0; + + retval = 1; + } + + // This is a little ugly, but since the WavPack library has read the tags off the + // files, we can close the files (but not the WavPack context) now so that we don't + // leave handles open. We may access the file again for the "formatinformation" + // field, so we reopen the file if we get that one. + + if (info.wv_id) + { + fclose(info.wv_id); + info.wv_id = NULL; + } + + if (info.wvc_id) + { + fclose(info.wvc_id); + info.wvc_id = NULL; + } + + return retval; +} + +#endif + +#ifdef UNICODE_METADATA + +extern "C" __declspec (dllexport) int winampGetExtendedFileInfoW (wchar_t *filename, char *metadata, wchar_t *ret, int retlen) +{ + char error[128], res[256]; + int open_flags = OPEN_TAGS; + int retval = 0; + +#ifdef DEBUG_CONSOLE + sprintf (error, "winampGetExtendedFileInfoW (%s)\n", metadata); + debug_write (error); +#endif + + if (!_stricmp(metadata, "type")) + { + ret[0] = '0'; + ret[1] = 0; + return 1; + } + else if (!_stricmp(metadata, "family")) + { + int len; + const wchar_t *p; + if (!filename || !filename[0]) return 0; + len = lstrlenW(filename); + if (len < 3 || L'.' != filename[len - 3]) return 0; + p = &filename[len - 2]; + if (!_wcsicmp(p, L"wv")) { WASABI_API_LNGSTRINGW_BUF(IDS_FAMILY_STRING, ret, retlen); return 1; } + return 0; + } + + if (!filename || !*filename) + return retval; + + if (!_stricmp(metadata, "length")) /* even if no file, return a 1 and write "0" */ + { + StringCchPrintfW(ret, retlen, L"%d", 0); + retval = 1; + } + + if (!info.wpc || wcscmp(filename, info.w_lastfn) || !_stricmp(metadata, "formatinformation")) + { + close_context(&info); + + if (!(info.wv_id = _wfopen(filename, L"rb"))) + return retval; + + if (config_bits & ALLOW_WVC) { + int length = wcslen(filename) + 10; + wchar_t *wvc_name = (wchar_t *)malloc(length * sizeof(wchar_t)); + + if (wvc_name) { + lstrcpynW(wvc_name, filename, length); + StringCchCatW(wvc_name, length, L"c"); + info.wvc_id = _wfopen(wvc_name, L"rb"); + free(wvc_name); + } + } + + info.wpc = WavpackOpenFileInputEx(&freader, &info.wv_id, info.wvc_id ? &info.wvc_id : NULL, error, open_flags, 0); + + if (!info.wpc) + { + close_context(&info); + return retval; + } + + lstrcpynW(info.w_lastfn, filename, MAX_PATH); + info.lastfn[0] = 0; + } + + if (!_stricmp(metadata, "formatinformation")) + { + generate_format_string(info.wpc, ret, retlen, 0); + retval = 1; + } + else if (!_stricmp (metadata, "length")) + { + StringCchPrintfW(ret, retlen, L"%d", (int)(WavpackGetNumSamples(info.wpc) * 1000.0 / WavpackGetSampleRate(info.wpc))); + retval = 1; + } + else if (!_stricmp(metadata, "lossless")) + { + StringCchPrintfW(ret, retlen, L"%d", (WavpackGetMode(info.wpc) & MODE_LOSSLESS) ? 1 : 0); + retval = 1; + } + else if (!_stricmp(metadata, "gain")) + { + StringCchPrintfW(ret, retlen, L"%-+.2f dB", calculate_gain(info.wpc, false)); + retval = 1; + } + else if (!_stricmp(metadata, "numsamples")) + { + StringCchPrintfW(ret, retlen, L"%d", WavpackGetNumSamples(info.wpc)); + retval = 1; + } + else if (!_stricmp(metadata, "mime")) + { + lstrcpynW(ret, L"audio/x-wavpack", retlen); + retval = 1; + } + else if (WavpackGetTagItem(info.wpc, metadata, res, sizeof (res))) + { + if (!_stricmp(metadata, "rating")) + { + int rating = atoi(res); + // appears to be generally 0-5 or 0-100 + if (rating > 10) { + rating /= 20; + } + // or maybe we're dealing with a 1-10 range + else if (rating > 5) { + rating /= 2; + } + // otherwise it is hopefully in the 0-5 range + + StringCchPrintfW(ret, retlen, L"%u", rating); + } + else + { + if (!(WavpackGetMode(info.wpc) & MODE_APETAG)) + lstrcpynW(ret, AutoWide(res), retlen); + else + lstrcpynW(ret, AutoWide(res, CP_UTF8), retlen); + } + + retval = 1; + } + else if (metadata_we_can_write (metadata)) + { + if (retlen) + *ret = 0; + + retval = 1; + } + + // This is a little ugly, but since the WavPack library has read the tags off the + // files, we can close the files (but not the WavPack context) now so that we don't + // leave handles open. We may access the file again for the "formatinformation" + // field, so we reopen the file if we get that one. + + if (info.wv_id) + { + fclose (info.wv_id); + info.wv_id = NULL; + } + + if (info.wvc_id) + { + fclose (info.wvc_id); + info.wvc_id = NULL; + } + + return retval; +} + +#endif + +#ifdef ANSI_METADATA + +extern "C" int __declspec (dllexport) winampSetExtendedFileInfo(const char *filename, const char *metadata, char *val) +{ + char error[128]; + +#ifdef DEBUG_CONSOLE + sprintf (error, "winampSetExtendedFileInfo (%s=%s)\n", metadata, val); + debug_write (error); +#endif + + if (!filename || !*filename || !metadata_we_can_write(metadata)) + return 0; + + if (!edit.wpc || strcmp(filename, edit.lastfn)) + { + if (edit.wpc) + WavpackCloseFile(edit.wpc); + + edit.wpc = WavpackOpenFileInput(filename, error, OPEN_TAGS | OPEN_EDIT_TAGS, 0); + + if (!edit.wpc) + return 0; + + lstrcpyn(edit.lastfn, filename, MAX_PATH); + edit.w_lastfn [0] = 0; + } + + if (strlen(val)) + return WavpackAppendTagItem(edit.wpc, metadata, val, strlen (val)); + else + return WavpackDeleteTagItem(edit.wpc, metadata); +} + +#endif + +#ifdef UNICODE_METADATA + +extern "C" int __declspec (dllexport) winampSetExtendedFileInfoW(const wchar_t *filename, const char *metadata, wchar_t *val) +{ + char error[128], utf8_val[256]; + + lstrcpyn(utf8_val,AutoChar(val, CP_UTF8),sizeof(utf8_val) - 1); + +#ifdef DEBUG_CONSOLE + sprintf (error, "winampSetExtendedFileInfoW (%s=%s)\n", metadata, utf8_val); + debug_write (error); +#endif + + if (!filename || !*filename || !metadata_we_can_write(metadata)) + return 0; + + if (!edit.wpc || wcscmp(filename, edit.w_lastfn)) + { + if (edit.wpc) + { + WavpackCloseFile(edit.wpc); + edit.wpc = NULL; + } + + if (edit.wv_id) + fclose(edit.wv_id); + + if (!(edit.wv_id = _wfopen(filename, L"r+b"))) + return 0; + + edit.wpc = WavpackOpenFileInputEx(&freader, &edit.wv_id, NULL, error, OPEN_TAGS | OPEN_EDIT_TAGS, 0); + + if (!edit.wpc) + { + fclose(edit.wv_id); + return 0; + } + + lstrcpynW(edit.w_lastfn, filename, MAX_PATH); + edit.lastfn [0] = 0; + } + + if (strlen(utf8_val)) + return WavpackAppendTagItem(edit.wpc, metadata, utf8_val, strlen (utf8_val)); + else + return WavpackDeleteTagItem(edit.wpc, metadata); +} + +#endif + +extern "C" int __declspec (dllexport) winampWriteExtendedFileInfo(void) +{ +#ifdef DEBUG_CONSOLE + debug_write ("winampWriteExtendedFileInfo ()\n"); +#endif + + if (edit.wpc) + { + WavpackWriteTag(edit.wpc); + WavpackCloseFile(edit.wpc); + edit.wpc = NULL; + } + + if (edit.wv_id) + { + fclose(edit.wv_id); + edit.wv_id = NULL; + } + + close_context(&info); // make sure we re-read info on any open files + return 1; +} + +// return 1 if you want winamp to show it's own file info dialogue, 0 if you want to show your own (via In_Module.InfoBox) +// if returning 1, remember to implement winampGetExtendedFileInfo("formatinformation")! +extern "C" __declspec(dllexport) int winampUseUnifiedFileInfoDlg(const wchar_t * fn) +{ + return 1; +} + +static const char *writable_metadata [] = { + "track", "genre", "year", "comment", "artist", + "album", "title", "albumartist", "composer", + "publisher", "disc", "tool", "encoder", "bpm", + "category", "rating", + "replaygain_track_gain", "replaygain_track_peak", + "replaygain_album_gain", "replaygain_album_peak" +}; + +#define NUM_KNOWN_METADATA (sizeof (writable_metadata) / sizeof (writable_metadata [0])) + +static int metadata_we_can_write(const char *metadata) +{ + if (!metadata || !*metadata) + return 0; + + for (int i = 0; i < NUM_KNOWN_METADATA; ++i) + if (!_stricmp(metadata, writable_metadata[i])) + return 1; + + return 0; +} + +static void generate_format_string(WavpackContext *wpc, wchar_t *string, int maxlen, int wide) +{ + int mode = WavpackGetMode(wpc); + unsigned char md5_sum[16]; + wchar_t modes[256]; + wchar_t fmt[256]; + + WASABI_API_LNGSTRINGW_BUF(IDS_ENCODER_VERSION, fmt, sizeof(fmt)); + StringCchPrintfW(string, maxlen, fmt, WavpackGetVersion(wpc)); + while (*string && string++ && maxlen--); + + WASABI_API_LNGSTRINGW_BUF(IDS_SOURCE, fmt, sizeof (fmt)); + StringCchPrintfW(string, maxlen, fmt, WavpackGetBitsPerSample(wpc), + WASABI_API_LNGSTRINGW((WavpackGetMode(wpc) & MODE_FLOAT) ? IDS_FLOATS : IDS_INTS), + WavpackGetSampleRate(wpc)); + while (*string && string++ && maxlen--); + + if (WavpackGetNumChannels(wpc) > 2) + { + WASABI_API_LNGSTRINGW_BUF(IDS_MULTICHANNEL, fmt, sizeof (fmt)); + StringCchPrintfW(string, maxlen, fmt, WavpackGetNumChannels(wpc)); + while (*string && string++ && maxlen--); + } + else if (WavpackGetNumChannels(wpc) == 2) + { + WASABI_API_LNGSTRINGW_BUF(IDS_STEREO, fmt, sizeof (fmt)); + StringCchPrintfW(string, maxlen, fmt); + while (*string && string++ && maxlen--); + } + else + { + WASABI_API_LNGSTRINGW_BUF(IDS_MONO, fmt, sizeof (fmt)); + StringCchPrintfW(string, maxlen, fmt); + while (*string && string++ && maxlen--); + } + + modes [0] = 0; + + if (WavpackGetMode(wpc) & MODE_HYBRID) + { + StringCchCatW(modes, 256, WASABI_API_LNGSTRINGW(IDS_HYBRID)); + StringCchCatW(modes, 256, L" "); + } + + StringCchCatW(modes, 256, WASABI_API_LNGSTRINGW((WavpackGetMode(wpc) & MODE_LOSSLESS) ? IDS_LOSSLESS : IDS_LOSSY)); + + if (WavpackGetMode(wpc) & MODE_FAST) + StringCchCatW(modes, 256, WASABI_API_LNGSTRINGW(IDS_FAST)); + else if (WavpackGetMode(wpc) & MODE_VERY_HIGH) + StringCchCatW(modes, 256, WASABI_API_LNGSTRINGW(IDS_VHIGH)); + else if (WavpackGetMode(wpc) & MODE_HIGH) + StringCchCatW(modes, 256, WASABI_API_LNGSTRINGW(IDS_HIGH)); + + if (WavpackGetMode(wpc) & MODE_EXTRA) + StringCchCatW(modes, 256, WASABI_API_LNGSTRINGW(IDS_EXTRA)); + + StringCchPrintfW(string, maxlen, L"%s:%s %s\n", + WASABI_API_LNGSTRINGW(IDS_MODES), + (wide || lstrlenW(modes) < 24) ? L"" : L"\n", modes); + while (*string && string++ && maxlen--); + + if (WavpackGetRatio(wpc) != 0.0) + { + wchar_t str_kbps[32]; + StringCchPrintfW(string, maxlen, L"%s: %d %s \n", + WASABI_API_LNGSTRINGW(IDS_BITRATE), + (int)((WavpackGetAverageBitrate(wpc, TRUE) + 500.0) / 1000.0), + WASABI_API_LNGSTRINGW_BUF(IDS_KBPS, str_kbps, sizeof(str_kbps))); + while (*string && string++ && maxlen--); + StringCchPrintfW(string, maxlen, L"%s: %.2f : 1 \n", + WASABI_API_LNGSTRINGW(IDS_RATIO), 1.0 / WavpackGetRatio(wpc)); + while (*string && string++ && maxlen--); + } + + if (WavpackGetMD5Sum(wpc, md5_sum)) + { + wchar_t md5s1 [17], md5s2 [17]; + int i; + + for (i = 0; i < 8; ++i) + { + StringCchPrintfW(md5s1 + i * 2, sizeof(md5s1), L"%02x", md5_sum [i]); + StringCchPrintfW(md5s2 + i * 2, sizeof(md5s2), L"%02x", md5_sum [i+8]); + } + + StringCchPrintfW(string, maxlen, (wide ? L"%s: %s%s\n" : L"%s:\n %s\n %s\n"), + WASABI_API_LNGSTRINGW(IDS_MD5), md5s1, md5s2); + } +} + +///////////////////// native "C" functions required for AlbumArt support /////////////////////////// + +#ifdef DEBUG_CONSOLE + +static char temp_buff [256]; + +static char *wide2char (const wchar_t *src) +{ + char *dst = temp_buff; + + while (*src) + *dst++ = (char) *src++; + + *dst = 0; + return temp_buff; +} + +#endif + +int WavPack_HandlesExtension(const wchar_t *extension) +{ + return !_wcsicmp(extension, L"wv"); +} + +int WavPack_GetAlbumArt(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mime_type) +{ + char *buffer; + char error[128]; + int tag_size, i; + int retval = 1; + +#ifdef DEBUG_CONSOLE + sprintf (error, "WavPack_GetAlbumArt (%s)\n", wide2char (type)); + debug_write (error); +#endif + + if (!filename || !*filename || _wcsicmp(type, L"cover")) + return retval; + + if (!info.wpc || wcscmp(filename, info.w_lastfn)) + { + close_context(&info); + + if (!(info.wv_id = _wfopen(filename, L"rb"))) + return retval; + + if (config_bits & ALLOW_WVC) + { + int length = wcslen(filename) + 10; + wchar_t *wvc_name = (wchar_t *)malloc(length * sizeof(wchar_t)); + + if (wvc_name) + { + lstrcpynW(wvc_name, filename, length); + StringCchCatW(wvc_name, length, L"c"); + info.wvc_id = _wfopen(wvc_name, L"rb"); + free(wvc_name); + } + } + + info.wpc = WavpackOpenFileInputEx(&freader, &info.wv_id, info.wvc_id ? &info.wvc_id : NULL, error, OPEN_TAGS, 0); + + if (!info.wpc) + { + close_context(&info); + return retval; + } + + lstrcpynW(info.w_lastfn, filename, MAX_PATH); + info.lastfn[0] = 0; + } + + tag_size = WavpackGetBinaryTagItem(info.wpc, "Cover Art (Front)", NULL, 0); + + if (!tag_size) + return retval; + + buffer = (char*)WASABI_API_MEMMGR->sysMalloc(tag_size); + WavpackGetBinaryTagItem(info.wpc, "Cover Art (Front)", buffer, tag_size); + + for (i = 0; i < tag_size - 1; ++i) + if (!buffer[i] && strrchr(buffer, '.')) { + char *ext = strrchr(buffer, '.') + 1; + wchar_t *wcptr; + + wcptr = *mime_type = (wchar_t*)WASABI_API_MEMMGR->sysMalloc(strlen(ext) * 2 + 2); + + while (*ext) + *wcptr++ = *ext++; + + *wcptr = 0; + *bits = buffer; + *len = tag_size - i - 1; + memmove(buffer, buffer + i + 1, *len); + retval = 0; +#ifdef DEBUG_CONSOLE + sprintf (error, "WavPack_GetAlbumArt (\"%s\", %d) success!\n", wide2char (*mime_type), *len); + debug_write (error); +#endif + } + + if (retval) + WASABI_API_MEMMGR->sysFree(buffer); + + // This is a little ugly, but since the WavPack library has read the tags off the + // files, we can close the files (but not the WavPack context) now so that we don't + // leave handles open. We may access the file again for the "formatinformation" + // field, so we reopen the file if we get that one. + + if (info.wv_id) + { + fclose(info.wv_id); + info.wv_id = NULL; + } + + if (info.wvc_id) + { + fclose(info.wvc_id); + info.wvc_id = NULL; + } + + return retval; +} + +int WavPack_SetAlbumArt(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mime_type) +{ +#if 1 + return 2; // return 2 to indicate "read-only" cover art for now +#else + char error [128], name [50], *cp; + int tag_size, retval = 0; + unsigned char *buffer; + +#ifdef DEBUG_CONSOLE + sprintf (error, "WavPack_SetAlbumArt (%s)\n", wide2char (mime_type)); + debug_write (error); +#endif + + if (!filename || !*filename || _wcsicmp (type, L"cover") || wcslen (mime_type) > 16) + return 1; + + strcpy (name, "Cover Art (Front)"); + cp = name + strlen (name); + *cp++ = '.'; + + while (*mime_type) + *cp++ = (char) *mime_type++; + + *cp = 0; + tag_size = strlen (name) + 1 + len; + buffer = malloc (tag_size); + strcpy (buffer, name); + memcpy (buffer + strlen (buffer) + 1, bits, len); + + if (!edit.wpc || wcscmp (filename, edit.w_lastfn)) { + if (edit.wpc) { + WavpackCloseFile (edit.wpc); + edit.wpc = NULL; + } + + if (edit.wv_id) + fclose (edit.wv_id); + + if (!(edit.wv_id = _wfopen (filename, L"r+b"))) { + free (buffer); + return 1; + } + + edit.wpc = WavpackOpenFileInputEx (&freader, &edit.wv_id, NULL, error, OPEN_TAGS | OPEN_EDIT_TAGS, 0); + + if (!edit.wpc) { + fclose (edit.wv_id); + free (buffer); + return 1; + } + + wcscpy (edit.w_lastfn, filename); + edit.lastfn [0] = 0; + } + + retval = WavpackAppendTagItem (edit.wpc, "Cover Art (Front)", buffer, tag_size); + free (buffer); + + if (retval) { + winampWriteExtendedFileInfo (); + return 0; + } + else { + close_context (&edit); + return 1; + } +#endif +} + +int WavPack_DeleteAlbumArt(const wchar_t *filename, const wchar_t *type) +{ +#if 1 + return 2; // return 2 to indicate "read-only" cover art for now +#else + char error [128]; + +#ifdef DEBUG_CONSOLE + sprintf (error, "WavPack_DeleteAlbumArt ()\n"); + debug_write (error); +#endif + + if (!filename || !*filename || _wcsicmp (type, L"cover")) + return 0; + + if (!edit.wpc || wcscmp (filename, edit.w_lastfn)) { + if (edit.wpc) { + WavpackCloseFile (edit.wpc); + edit.wpc = NULL; + } + + if (edit.wv_id) + fclose (edit.wv_id); + + if (!(edit.wv_id = _wfopen (filename, L"r+b"))) + return 1; + + edit.wpc = WavpackOpenFileInputEx (&freader, &edit.wv_id, NULL, error, OPEN_TAGS | OPEN_EDIT_TAGS, 0); + + if (!edit.wpc) { + fclose (edit.wv_id); + return 1; + } + + wcscpy (edit.w_lastfn, filename); + edit.lastfn [0] = 0; + } + + if (WavpackDeleteTagItem (edit.wpc, "Cover Art (Front)")) { + winampWriteExtendedFileInfo (); + return 0; + } + else { + close_context (&edit); + return 1; + } +#endif +} + +////////////////////////////////////////////////////////////////////////////// +// This function uses the ReplayGain mode selected by the user and the info // +// stored in the specified tag to determine the gain value used to play the // +// file and whether "soft clipping" is required. Note that the gain is in // +// voltage scaling (not dB), so a value of 1.0 (not 0.0) is unity gain. // +////////////////////////////////////////////////////////////////////////////// + +static float calculate_gain(WavpackContext *wpc, bool allowDefault) +{ + if (AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false)) + { + float dB = 0, peak = 1.0f; + char gain[128] = "", peakVal[128] = ""; + _locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale(); + + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_source", 0)) + { + case 0: // track + if ((!WavpackGetTagItem(wpc, "replaygain_track_gain", gain, sizeof(gain)) || !gain[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + WavpackGetTagItem(wpc, "replaygain_album_gain", gain, sizeof(gain)); + + if ((!WavpackGetTagItem(wpc, "replaygain_track_peak", peakVal, sizeof(peakVal)) || !peakVal[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + WavpackGetTagItem(wpc, "replaygain_album_peak", peakVal, sizeof(peakVal)); + break; + case 1: + if ((!WavpackGetTagItem(wpc, "replaygain_album_gain", gain, sizeof(gain)) || !gain[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + WavpackGetTagItem(wpc, "replaygain_track_gain", gain, sizeof(gain)); + + if ((!WavpackGetTagItem(wpc, "replaygain_album_peak", peakVal, sizeof(peakVal)) || !peakVal[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + WavpackGetTagItem(wpc, "replaygain_track_peak", peakVal, sizeof(peakVal)); + break; + } + + if (gain[0]) + { + if (gain[0] == L'+') + dB = (float)_atof_l(&gain[1],C_locale); + else + dB = (float)_atof_l(gain,C_locale); + } + else if (allowDefault) + { + dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0); + return powf(10.0f, dB / 20.0f); + } + + if (peakVal[0]) + { + peak = (float)_atof_l(peakVal,C_locale); + } + + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_mode", 1)) + { + case 0: // apply gain + return powf(10.0f, dB / 20.0f); + case 1: // apply gain, but don't clip + return min(powf(10.0f, dB / 20.0f), 1.0f / peak); + case 2: // normalize + return 1.0f / peak; + case 3: // prevent clipping + if (peak > 1.0f) + return 1.0f / peak; + else + return 1.0f; + } + } + + return 1.0f; // no gain +} + +// Convert a Ansi string into its Unicode UTF-8 format equivalent. The +// conversion is done in-place so the maximum length of the string buffer must +// be specified because the string may become longer or shorter. If the +// resulting string will not fit in the specified buffer size then it is +// truncated. +#ifdef OLD_INFO_DIALOG +static void AnsiToUTF8(char *string, int len) +{ + int max_chars = (int) strlen(string); + wchar_t *temp = (wchar_t *) malloc((max_chars + 1) * 2); + + MultiByteToWideChar(CP_ACP, 0, string, -1, temp, max_chars + 1); + lstrcpyn(string, AutoChar(temp, CP_UTF8), len); + free(temp); +} +#endif + +// Convert Unicode UTF-8 string to wide format. UTF-8 string must be NULL +// terminated. Resulting wide string must be able to fit in provided space +// and will also be NULL terminated. The number of characters converted will +// be returned (not counting terminator). + +static int UTF8ToWideChar(const char *pUTF8, wchar_t *pWide) +{ + int trail_bytes = 0; + int chrcnt = 0; + + while (*pUTF8) + { + if (*pUTF8 & 0x80) + { + if (*pUTF8 & 0x40) + { + if (trail_bytes) + { + trail_bytes = 0; + chrcnt++; + } + else + { + char temp = *pUTF8; + + while (temp & 0x80) + { + trail_bytes++; + temp <<= 1; + } + + pWide [chrcnt] = temp >> trail_bytes--; + } + } + else if (trail_bytes) + { + pWide [chrcnt] = (pWide [chrcnt] << 6) | (*pUTF8 & 0x3f); + + if (!--trail_bytes) + chrcnt++; + } + } + else + pWide [chrcnt++] = *pUTF8; + + pUTF8++; + } + + pWide [chrcnt] = 0; + return chrcnt; +} + +// Convert a Unicode UTF-8 format string into its Ansi equivalent. The +// conversion is done in-place so the maximum length of the string buffer must +// be specified because the string may become longer or shorter. If the +// resulting string will not fit in the specified buffer size then it is +// truncated. + +static void UTF8ToAnsi(char *string, int len) +{ + int max_chars = (int)strlen(string); + wchar_t *temp = (wchar_t *)malloc((max_chars + 1) * 2); + int act_chars = UTF8ToWideChar(string, temp); + + while (act_chars) + { + memset (string, 0, len); + + if (WideCharToMultiByte(CP_ACP, 0, temp, act_chars, string, len - 1, NULL, NULL)) + break; + else + act_chars--; + } + + if (!act_chars) + *string = 0; + + free (temp); +} + +extern "C" __declspec(dllexport) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) +{ + // as we're not hooking anything and have no settings we can support an on-the-fly uninstall action + return IN_PLUGIN_UNINSTALL_NOW; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/resource.h b/Src/Plugins/Input/in_wv/resource.h new file mode 100644 index 00000000..dfeadd62 --- /dev/null +++ b/Src/Plugins/Input/in_wv/resource.h @@ -0,0 +1,49 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by wavpack.rc +// +#define IDS_DISABLED 106 +#define IDS_USE_TRACK 107 +#define IDS_USE_ALBUM 108 +#define IDS_JUST_CLIP 109 +#define IDS_SOFT_CLIP 110 +#define IDS_PREVENT_CLIP 111 +#define IDS_ABOUT 112 +#define IDS_FORMAT 113 +#define IDS_ENCODER_VERSION 114 +#define IDS_SOURCE 115 +#define IDS_MULTICHANNEL 116 +#define IDS_MONO 117 +#define IDS_STEREO 118 +#define IDS_HYBRID 119 +#define IDS_LOSSLESS 120 +#define IDS_LOSSY 121 +#define IDS_INTS 122 +#define IDS_FLOATS 123 +#define IDS_MODES 124 +#define IDS_FAST 125 +#define IDS_HIGH 126 +#define IDS_VHIGH 127 +#define IDS_EXTRA 128 +#define IDS_BITRATE 129 +#define IDS_RATIO 130 +#define IDS_KBPS 131 +#define IDS_MD5 132 +#define IDS_DESCRIPTION 133 +#define IDS_STRING134 134 +#define IDS_FILETYPE 134 +#define IDS_ABOUT_MESSAGE 135 +#define IDS_FAMILY_STRING 136 +#define IDS_GUID 65535 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 136 +#define _APS_NEXT_COMMAND_VALUE 40002 +#define _APS_NEXT_CONTROL_VALUE 1027 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_wv/wasabi/Agave/AlbumArt/svc_albumArtProvider.h b/Src/Plugins/Input/in_wv/wasabi/Agave/AlbumArt/svc_albumArtProvider.h new file mode 100644 index 00000000..563b7689 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Agave/AlbumArt/svc_albumArtProvider.h @@ -0,0 +1,73 @@ +#ifndef NULLSOFT_AGAVE_SVC_ALBUMARTPROVIDER_H +#define NULLSOFT_AGAVE_SVC_ALBUMARTPROVIDER_H + +#include "../../bfc/dispatch.h" +#include "../../bfc/std_mkncc.h" // for MKnCC() + +enum +{ + ALBUMARTPROVIDER_SUCCESS = 0, + ALBUMARTPROVIDER_FAILURE = 1, + ALBUMARTPROVIDER_READONLY = 2, + + ALBUMARTPROVIDER_TYPE_EMBEDDED = 0, // contained within another file (e.g. inside id3v2 tag) + ALBUMARTPROVIDER_TYPE_DATABASE = 1, // cached in a database somewhere (e.g. ipod artwork DB) + ALBUMARTPROVIDER_TYPE_FOLDER = 2, // sitting on a folder somewhere (e.g. folder.jpg) +}; +class svc_albumArtProvider : public Dispatchable +{ +protected: + svc_albumArtProvider() {} + ~svc_albumArtProvider() {} +public: + + static FOURCC getServiceType() { return svc_albumArtProvider::SERVICETYPE; } + bool IsMine(const wchar_t *filename); + int ProviderType(); + // implementation note: use WASABI_API_MEMMGR to alloc bits and mimetype, so that the recipient can free through that + int GetAlbumArtData(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType); + int SetAlbumArtData(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType); + int DeleteAlbumArt(const wchar_t *filename, const wchar_t *type); + + DISPATCH_CODES + { + SVC_ALBUMARTPROVIDER_PROVIDERTYPE = 0, + SVC_ALBUMARTPROVIDER_GETALBUMARTDATA = 10, + SVC_ALBUMARTPROVIDER_ISMINE = 20, + SVC_ALBUMARTPROVIDER_SETALBUMARTDATA = 30, + SVC_ALBUMARTPROVIDER_DELETEALBUMART = 40, + }; + + enum + { + SERVICETYPE = MK3CC('a','a','p') + }; + +}; + +inline bool svc_albumArtProvider::IsMine(const wchar_t *filename) +{ + return _call(SVC_ALBUMARTPROVIDER_ISMINE, false, filename); +} + +inline int svc_albumArtProvider::ProviderType() +{ + return _call(SVC_ALBUMARTPROVIDER_PROVIDERTYPE, (int)ALBUMARTPROVIDER_TYPE_EMBEDDED); +} + +inline int svc_albumArtProvider::GetAlbumArtData(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType) +{ + return _call(SVC_ALBUMARTPROVIDER_GETALBUMARTDATA, (int)ALBUMARTPROVIDER_FAILURE, filename, type, bits, len, mimeType); +} + +inline int svc_albumArtProvider::SetAlbumArtData(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType) +{ + return _call(SVC_ALBUMARTPROVIDER_SETALBUMARTDATA, (int)ALBUMARTPROVIDER_FAILURE, filename, type, bits, len, mimeType); +} + +inline int svc_albumArtProvider::DeleteAlbumArt(const wchar_t *filename, const wchar_t *type) +{ + return _call(SVC_ALBUMARTPROVIDER_DELETEALBUMART, (int)ALBUMARTPROVIDER_FAILURE, filename, type); +} + +#endif diff --git a/Src/Plugins/Input/in_wv/wasabi/Agave/Config/api_config.h b/Src/Plugins/Input/in_wv/wasabi/Agave/Config/api_config.h new file mode 100644 index 00000000..934671ae --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Agave/Config/api_config.h @@ -0,0 +1,123 @@ +#ifndef NULLSOFT_AGAVE_API_CONFIG_H +#define NULLSOFT_AGAVE_API_CONFIG_H + +#include "../../bfc/dispatch.h" +#include "ifc_configgroup.h" + +enum +{ + CONFIG_SUCCESS = 0, + CONFIG_FAILURE = 1, + CONFIG_GROUPNOTFOUND = 2, + CONFIG_ITEMNOTFOUND = 3, +}; + +class api_config : public Dispatchable +{ +protected: + api_config() {} + ~api_config() {} +public: + ifc_configgroup *GetGroup(GUID groupGUID); + void RegisterGroup(ifc_configgroup *newGroup); + + /* Shortcut methods */ + bool GetBool(GUID groupGUID, const wchar_t *configItem, bool defaultValue); + uintptr_t GetUnsigned(GUID groupGUID, const wchar_t *configItem, uintptr_t defaultValue); + intptr_t GetInt(GUID groupGUID, const wchar_t *configItem, intptr_t defaultValue); + float GetFloat(GUID groupGUID, const wchar_t *configItem, float defaultValue); + const wchar_t *GetString(GUID groupGUID, const wchar_t *configItem, const wchar_t *defaultValue); + ifc_configitem *GetItem(GUID groupGUID, const wchar_t *configItem); +public: + DISPATCH_CODES + { + API_CONFIG_GETGROUP = 10, + API_CONFIG_REGISTERGROUP = 20, + }; +}; + +inline ifc_configgroup *api_config::GetGroup(GUID groupGUID) +{ + return _call(API_CONFIG_GETGROUP, (ifc_configgroup *)0, groupGUID); +} + +inline void api_config::RegisterGroup(ifc_configgroup *newGroup) +{ + _voidcall(API_CONFIG_REGISTERGROUP, newGroup); +} + +inline bool api_config::GetBool(GUID groupGUID, const wchar_t *configItem, bool defaultValue) +{ + ifc_configgroup *group = GetGroup(groupGUID); + if (group) + { + ifc_configitem *item = group->GetItem(configItem); + if (item) + return item->GetBool(); + } + return defaultValue; +} + +inline uintptr_t api_config::GetUnsigned(GUID groupGUID, const wchar_t *configItem, uintptr_t defaultValue) +{ + ifc_configgroup *group = GetGroup(groupGUID); + if (group) + { + ifc_configitem *item = group->GetItem(configItem); + if (item) + return item->GetUnsigned(); + } + return defaultValue; +} + +inline intptr_t api_config::GetInt(GUID groupGUID, const wchar_t *configItem, intptr_t defaultValue) +{ + ifc_configgroup *group = GetGroup(groupGUID); + if (group) + { + ifc_configitem *item = group->GetItem(configItem); + if (item) + return item->GetInt(); + } + return defaultValue; +} + +inline float api_config::GetFloat(GUID groupGUID, const wchar_t *configItem, float defaultValue) +{ + ifc_configgroup *group = GetGroup(groupGUID); + if (group) + { + ifc_configitem *item = group->GetItem(configItem); + if (item) + return item->GetFloat(); + } + return defaultValue; +} + +inline const wchar_t *api_config::GetString(GUID groupGUID, const wchar_t *configItem, const wchar_t *defaultValue) +{ + ifc_configgroup *group = GetGroup(groupGUID); + if (group) + { + ifc_configitem *item = group->GetItem(configItem); + if (item) + return item->GetString(); + } + return defaultValue; +} + +inline ifc_configitem *api_config::GetItem(GUID groupGUID, const wchar_t *configItem) +{ + ifc_configgroup *group = GetGroup(groupGUID); + if (group) + { + return group->GetItem(configItem); + } + return 0; +} + +// {AEFBF8BE-E0AA-4318-8CC1-4353410B64DC} +static const GUID AgaveConfigGUID = +{ 0xaefbf8be, 0xe0aa, 0x4318, { 0x8c, 0xc1, 0x43, 0x53, 0x41, 0xb, 0x64, 0xdc } }; + +#endif diff --git a/Src/Plugins/Input/in_wv/wasabi/Agave/Config/ifc_configgroup.h b/Src/Plugins/Input/in_wv/wasabi/Agave/Config/ifc_configgroup.h new file mode 100644 index 00000000..d6f21ec8 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Agave/Config/ifc_configgroup.h @@ -0,0 +1,35 @@ +#ifndef NULLSOFT_AGAVE_IFC_CONFIGGROUP_H +#define NULLSOFT_AGAVE_IFC_CONFIGGROUP_H + +#include "../../bfc/dispatch.h" +#include "../../bfc/platform/types.h" +#include "../../bfc/platform/guid.h" +#include "ifc_configitem.h" + +class ifc_configgroup : public Dispatchable +{ +protected: + ifc_configgroup() {} + ~ifc_configgroup() {} +public: + ifc_configitem *GetItem(const wchar_t *name); + GUID GetGUID(); +public: + DISPATCH_CODES + { + IFC_CONFIGGROUP_GETITEM = 10, + IFC_CONFIGGROUP_GETGUID = 20, + }; + +}; + +inline ifc_configitem *ifc_configgroup::GetItem(const wchar_t *name) +{ + return _call(IFC_CONFIGGROUP_GETITEM, (ifc_configitem *)0, name); +} + +inline GUID ifc_configgroup::GetGUID() +{ + return _call(IFC_CONFIGGROUP_GETGUID, (GUID)INVALID_GUID); +} +#endif diff --git a/Src/Plugins/Input/in_wv/wasabi/Agave/Config/ifc_configitem.h b/Src/Plugins/Input/in_wv/wasabi/Agave/Config/ifc_configitem.h new file mode 100644 index 00000000..6f5a8ae3 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Agave/Config/ifc_configitem.h @@ -0,0 +1,200 @@ +#ifndef NULLSOFT_AGAVE_IFC_CONFIGITEM_H +#define NULLSOFT_AGAVE_IFC_CONFIGITEM_H + +#include "../../bfc/dispatch.h" +#include <stddef.h> +/* +notes: +The Set() functions are "public-facing", meaning that they can be called by anyone. If you want to make your config item read-only, +then simply don't implement these. You can always make "private" Set functions in your implementation. + +SetStringInternal and GetStringInternal are written for use with classes to load and save from INI files (or XML files or whatever). +It's up to you to figure out a clever way to encode yourself. + +*/ + +enum +{ + CONFIG_ITEM_TYPE_STRING = 0, + CONFIG_ITEM_TYPE_INT = 1, + CONFIG_ITEM_TYPE_UNSIGNED =2, + CONFIG_ITEM_TYPE_BOOL =3, + CONFIG_ITEM_TYPE_BINARY =4, + CONFIG_ITEM_TYPE_INT_ARRAY = 5, +}; + +class ifc_configitem : public Dispatchable +{ +protected: + ifc_configitem() {} + ~ifc_configitem() {} +public: + const wchar_t *GetName(); + int GetType(); + + const wchar_t *GetString(); + void SetString(const wchar_t *stringValue); + + intptr_t GetInt(); + void SetInt(intptr_t intValue); + + uintptr_t GetUnsigned(); + void SetUnsigned(uintptr_t unsignedValue); + + bool GetBool(); + void SetBool(bool boolValue); + + float GetFloat(); + void SetFloat(float floatValue); + + size_t GetBinarySize(); + size_t GetBinaryData(void *data, size_t bytes); // returns bytes written + void SetBinaryData(void *data, size_t bytes); + + size_t GetIntArrayElements(); + size_t GetIntArray(intptr_t *array, size_t elements); // returns elements written + void SetIntArray(intptr_t *array, size_t elements); + + const wchar_t *GetStringInternal(); // gets a string suitable for saving in an INI file or XML + void SetStringInternal(const wchar_t *internalString); + +public: + DISPATCH_CODES + { + IFC_CONFIGITEM_GETNAME = 10, + IFC_CONFIGITEM_GETTYPE = 20, + + IFC_CONFIGITEM_GETSTRING= 30, + IFC_CONFIGITEM_SETSTRING= 40, + + IFC_CONFIGITEM_GETINT= 50, + IFC_CONFIGITEM_SETINT= 60, + + IFC_CONFIGITEM_GETUNSIGNED= 70, + IFC_CONFIGITEM_SETUNSIGNED= 80, + + IFC_CONFIGITEM_GETBOOL= 90, + IFC_CONFIGITEM_SETBOOL= 100, + + IFC_CONFIGITEM_GETBINARYSIZE= 110, + IFC_CONFIGITEM_GETBINARYDATA= 120, + IFC_CONFIGITEM_SETBINARYDATA= 130, + + IFC_CONFIGITEM_GETINTARRAYELEMENTS= 140, + IFC_CONFIGITEM_GETINTARRAY= 150, + IFC_CONFIGITEM_SETINTARRAY= 160, + + IFC_CONFIGITEM_GETSTRINGINTERNAL= 170, + IFC_CONFIGITEM_SETSTRINGINTERNAL= 180, + + IFC_CONFIGITEM_GETFLOAT= 190, + IFC_CONFIGITEM_SETFLOAT= 200, + }; +}; + + + +inline const wchar_t *ifc_configitem::GetName() +{ + return _call(IFC_CONFIGITEM_GETNAME, (const wchar_t *)0); +} + +inline int ifc_configitem::GetType() +{ + return _call(IFC_CONFIGITEM_GETTYPE, (int)0); +} + +inline const wchar_t *ifc_configitem::GetString() +{ + return _call(IFC_CONFIGITEM_GETSTRING, (const wchar_t *)0); +} + +inline void ifc_configitem::SetString(const wchar_t *stringValue) +{ + _voidcall(IFC_CONFIGITEM_SETSTRING, stringValue); +} + + +inline intptr_t ifc_configitem::GetInt() +{ + return _call(IFC_CONFIGITEM_GETINT, (intptr_t)0); +} +#pragma warning(push) +#pragma warning(disable: 4244) +inline void ifc_configitem::SetInt(intptr_t intValue) +{ + _voidcall(IFC_CONFIGITEM_SETINT, intValue); +} +#pragma warning(pop) + +inline uintptr_t ifc_configitem::GetUnsigned() +{ + return _call(IFC_CONFIGITEM_GETUNSIGNED, (uintptr_t)0); +} + +inline void ifc_configitem::SetUnsigned(uintptr_t unsignedValue) +{ + _voidcall(IFC_CONFIGITEM_SETUNSIGNED, unsignedValue); +} + + +inline bool ifc_configitem::GetBool() +{ + return _call(IFC_CONFIGITEM_GETBOOL, (bool)false); +} + +inline void ifc_configitem::SetBool(bool boolValue) +{ + _voidcall(IFC_CONFIGITEM_SETBOOL, boolValue); +} + +inline size_t ifc_configitem::GetBinarySize() +{ + return _call(IFC_CONFIGITEM_GETBINARYSIZE, (size_t)0); +} + +inline size_t ifc_configitem::GetBinaryData(void *data, size_t bytes) +{ + return _call(IFC_CONFIGITEM_GETBINARYDATA, (size_t)0, data, bytes); +} + +inline void ifc_configitem::SetBinaryData(void *data, size_t bytes) +{ + _voidcall(IFC_CONFIGITEM_SETBINARYDATA, data, bytes); +} + +inline size_t ifc_configitem::GetIntArrayElements() +{ + return _call(IFC_CONFIGITEM_GETINTARRAYELEMENTS, (size_t)0); +} + +inline size_t ifc_configitem::GetIntArray(intptr_t *array, size_t elements) +{ + return _call(IFC_CONFIGITEM_GETINTARRAY, (size_t)0, array, elements); +} +inline void ifc_configitem::SetIntArray(intptr_t *array, size_t elements) +{ + _voidcall(IFC_CONFIGITEM_SETINTARRAY, array, elements); +} + +inline const wchar_t *ifc_configitem::GetStringInternal() +{ + return _call(IFC_CONFIGITEM_GETSTRINGINTERNAL, (const wchar_t *)0); +} +inline void ifc_configitem::SetStringInternal(const wchar_t *internalString) +{ + _voidcall(IFC_CONFIGITEM_SETSTRINGINTERNAL, internalString); +} + +inline float ifc_configitem::GetFloat() +{ + return _call(IFC_CONFIGITEM_GETFLOAT, (float)0); +} + +inline void ifc_configitem::SetFloat(float floatValue) +{ + _voidcall(IFC_CONFIGITEM_SETFLOAT, floatValue); +} + + +#endif diff --git a/Src/Plugins/Input/in_wv/wasabi/Agave/Language/api_language.h b/Src/Plugins/Input/in_wv/wasabi/Agave/Language/api_language.h new file mode 100644 index 00000000..426a1c3c --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Agave/Language/api_language.h @@ -0,0 +1,315 @@ +#ifndef NULLSOFT_API_LANGUAGE_H +#define NULLSOFT_API_LANGUAGE_H + +#include "../../bfc/dispatch.h" +#include "lang.h" +#include <locale.h> + +#if (_MSC_VER <= 1200) + struct threadlocaleinfostruct; + struct threadmbcinfostruct; + typedef struct threadlocaleinfostruct * pthreadlocinfo; + typedef struct threadmbcinfostruct * pthreadmbcinfo; + + typedef struct localeinfo_struct + { + pthreadlocinfo locinfo; + pthreadmbcinfo mbcinfo; + } _locale_tstruct, *_locale_t; +#endif + +class api_language : public Dispatchable +{ +protected: + api_language() {} + ~api_language() {} +public: + char *GetString(HINSTANCE hinst, HINSTANCE owner, UINT uID, char *str=NULL, size_t maxlen=0); + wchar_t *GetStringW(HINSTANCE hinst, HINSTANCE owner, UINT uID, wchar_t *str=NULL, size_t maxlen=0); + + char *GetStringFromGUID(const GUID guid, HINSTANCE owner, UINT uID, char *str=NULL, size_t maxlen=0); + wchar_t *GetStringFromGUIDW(const GUID guid, HINSTANCE owner, UINT uID, wchar_t *str=NULL, size_t maxlen=0); + + HINSTANCE FindDllHandleByGUID(GUID guid); + HINSTANCE FindDllHandleByString(const char* str); + HINSTANCE FindDllHandleByStringW(const wchar_t* str); + HINSTANCE StartLanguageSupport(HINSTANCE hinstance, const GUID guid); + + const wchar_t *GetLanguageFolder(); + + #define LANG_IDENT_STR 0 + #define LANG_LANG_CODE 1 + #define LANG_COUNTRY_CODE 2 + const wchar_t *GetLanguageIdentifier(int mode); + + HWND CreateLDialogParam(HINSTANCE localised, HINSTANCE original, UINT id, HWND parent, DLGPROC proc, LPARAM param); + INT_PTR LDialogBoxParam(HINSTANCE localised, HINSTANCE original, UINT id, HWND parent, DLGPROC proc, LPARAM param); + HMENU LoadLMenu(HINSTANCE localised, HINSTANCE original, UINT id); + + HWND CreateLDialogParamW(HINSTANCE localised, HINSTANCE original, UINT id, HWND parent, DLGPROC proc, LPARAM param); + INT_PTR LDialogBoxParamW(HINSTANCE localised, HINSTANCE original, UINT id, HWND parent, DLGPROC proc, LPARAM param); + HMENU LoadLMenuW(HINSTANCE localised, HINSTANCE original, UINT id); + + void* LoadResourceFromFile(HINSTANCE hinst, HINSTANCE owner, LPCTSTR lpType, LPCTSTR lpName, DWORD* size); + void* LoadResourceFromFileW(HINSTANCE hinst, HINSTANCE owner, LPCWSTR lpType, LPCWSTR lpName, DWORD* size); + + HACCEL LoadAcceleratorsA(HINSTANCE hinst, HINSTANCE owner, LPCSTR lpTableName); + HACCEL LoadAcceleratorsW(HINSTANCE hinst, HINSTANCE owner, LPCWSTR lpTableName); + + // Implemented in 5.58+ + // When called this will attempt to set the locale used for numeric representation + // to that of the user running the current Winamp instance as long as the language + // and country identifiers match those reported within the language pack (if used) + // + // If you're running under a different thread then this will need to be called as + // the locale is set on a per thread basis which generally means anything under the + // Winamp process will be handled correctly unless a UI aspect is running under a + // different thread. Internally this is called within winamp.exe and vis_milk2.dll + BOOL UseUserNumericLocale(); + + // Get_C_NumericLocale() is a wrapper for _create_locale(LC_NUMERIC, "C") which can + // then be used in _atof_l(..), _sscanf_l(..) or other locale based functions when + // you need to process numbers without localisation handling ie the "C" locale. + // This function is provided for convenience unless you want to do it all manually. + _locale_t Get_C_NumericLocale(); + +public: + DISPATCH_CODES + { + API_LANGUAGE_GETSTRING = 10, + API_LANGUAGE_GETSTRINGW = 11, + + API_LANGUAGE_GETSTRINGFROMGUID = 12, + API_LANGUAGE_GETSTRINGFROMGUIDW = 13, + + API_LANGUAGE_GETHINSTANCEBYGUID = 20, + API_LANGUAGE_GETHINSTANCEBYNAME = 21, + API_LANGUAGE_GETHINSTANCEBYNAMEW = 22, + + API_LANGUAGE_STARTUP = 30, + API_LANGUAGE_SHUTDOWN = 31, + + API_LANGUAGE_GETLANGUAGEFOLDER=40, + + API_LANGUAGE_CREATELDIALOGPARAM=50, + API_LANGUAGE_LDIALOGBOXPARAM=51, + API_LANGUAGE_LOADLMENU=52, + API_LANGUAGE_CREATELDIALOGPARAMW=53, + API_LANGUAGE_LDIALOGBOXPARAMW=54, + API_LANGUAGE_LOADLMENUW=55, + + API_LANGUAGE_GETLANGUAGEIDENTIFIER=60, + + API_LANGUAGE_LOADRESOURCEFROMFILE=70, + API_LANGUAGE_LOADRESOURCEFROMFILEW=71, + + API_LANGUAGE_LOADACCELERATORSA=80, + API_LANGUAGE_LOADACCELERATORSW=81, + + // Implemented in 5.58+ + // See UseUserNumericLocale notes + API_LANGUAGE_USEUSERNUMERICLOCALE=90, + API_LANGUAGE_GET_C_NUMERICLOCALE=91, + }; +}; + +inline char *api_language::GetString(HINSTANCE hinst, HINSTANCE owner, UINT uID, char *str, size_t maxlen) +{ + return _call(API_LANGUAGE_GETSTRING, (char * )0, hinst, owner, uID, str, maxlen); +} + +inline wchar_t *api_language::GetStringW(HINSTANCE hinst, HINSTANCE owner, UINT uID, wchar_t *str, size_t maxlen) +{ + return _call(API_LANGUAGE_GETSTRINGW, (wchar_t * )0, hinst, owner, uID, str, maxlen); +} + +inline char *api_language::GetStringFromGUID(const GUID guid, HINSTANCE owner, UINT uID, char *str, size_t maxlen) +{ + return _call(API_LANGUAGE_GETSTRINGFROMGUID, (char * )0, guid, owner, uID, str, maxlen); +} + +inline wchar_t *api_language::GetStringFromGUIDW(const GUID guid, HINSTANCE owner, UINT uID, wchar_t *str, size_t maxlen) +{ + return _call(API_LANGUAGE_GETSTRINGFROMGUIDW, (wchar_t * )0, guid, owner, uID, str, maxlen); +} + +inline HINSTANCE api_language::FindDllHandleByGUID(const GUID guid) +{ + return _call(API_LANGUAGE_GETHINSTANCEBYGUID, (HINSTANCE )0, guid); +} + +inline HINSTANCE api_language::FindDllHandleByString(const char* str) +{ + return _call(API_LANGUAGE_GETHINSTANCEBYNAME, (HINSTANCE )0, str); +} + +inline HINSTANCE api_language::FindDllHandleByStringW(const wchar_t* str) +{ + return _call(API_LANGUAGE_GETHINSTANCEBYNAMEW, (HINSTANCE )0, str); +} + +inline HINSTANCE api_language::StartLanguageSupport(HINSTANCE hinstance, const GUID guid) +{ + return _call(API_LANGUAGE_STARTUP, (HINSTANCE )0, hinstance, guid); +} + +inline const wchar_t *api_language::GetLanguageFolder() +{ + return _call(API_LANGUAGE_GETLANGUAGEFOLDER, (const wchar_t *)0); +} + +inline HWND api_language::CreateLDialogParam(HINSTANCE localised, HINSTANCE original, UINT id, HWND parent, DLGPROC proc, LPARAM param) +{ + return _call(API_LANGUAGE_CREATELDIALOGPARAM, (HWND)0, localised, original, id, parent, proc, param); +} + +inline INT_PTR api_language::LDialogBoxParam(HINSTANCE localised, HINSTANCE original, UINT id, HWND parent, DLGPROC proc, LPARAM param) +{ + return _call(API_LANGUAGE_LDIALOGBOXPARAM, (INT_PTR)0, localised, original, id, parent, proc, param); +} + +inline HMENU api_language::LoadLMenu(HINSTANCE localised, HINSTANCE original, UINT id) +{ + return _call(API_LANGUAGE_LOADLMENU, (HMENU)0, localised, original, id); +} + +inline HWND api_language::CreateLDialogParamW(HINSTANCE localised, HINSTANCE original, UINT id, HWND parent, DLGPROC proc, LPARAM param) +{ + return _call(API_LANGUAGE_CREATELDIALOGPARAMW, (HWND)0, localised, original, id, parent, proc, param); +} + +inline INT_PTR api_language::LDialogBoxParamW(HINSTANCE localised, HINSTANCE original, UINT id, HWND parent, DLGPROC proc, LPARAM param) +{ + return _call(API_LANGUAGE_LDIALOGBOXPARAMW, (INT_PTR)0, localised, original, id, parent, proc, param); +} + +inline HMENU api_language::LoadLMenuW(HINSTANCE localised, HINSTANCE original, UINT id) +{ + return _call(API_LANGUAGE_LOADLMENUW, (HMENU)0, localised, original, id); +} + +inline const wchar_t *api_language::GetLanguageIdentifier(int mode) +{ + return _call(API_LANGUAGE_GETLANGUAGEIDENTIFIER, (const wchar_t *)0, mode); +} + +inline void *api_language::LoadResourceFromFile(HINSTANCE hinst, HINSTANCE owner, LPCTSTR lpType, LPCTSTR lpName, DWORD* size) +{ + return _call(API_LANGUAGE_LOADRESOURCEFROMFILE, (void*)0, hinst, owner, lpType, lpName, size); +} + +inline void *api_language::LoadResourceFromFileW(HINSTANCE hinst, HINSTANCE owner, LPCWSTR lpType, LPCWSTR lpName, DWORD* size) +{ + return _call(API_LANGUAGE_LOADRESOURCEFROMFILEW, (void*)0, hinst, owner, lpType, lpName, size); +} + +inline HACCEL api_language::LoadAcceleratorsA(HINSTANCE hinst, HINSTANCE owner, LPCSTR lpTableName) +{ + return _call(API_LANGUAGE_LOADACCELERATORSA, (HACCEL)NULL, hinst, owner, lpTableName); +} + +inline HACCEL api_language::LoadAcceleratorsW(HINSTANCE hinst, HINSTANCE owner, LPCWSTR lpTableName) +{ + return _call(API_LANGUAGE_LOADACCELERATORSA, (HACCEL)NULL, hinst, owner, lpTableName); +} + +inline BOOL api_language::UseUserNumericLocale() +{ + return _call(API_LANGUAGE_USEUSERNUMERICLOCALE, (BOOL)0); +} + +inline _locale_t api_language::Get_C_NumericLocale() +{ + return _call(API_LANGUAGE_GET_C_NUMERICLOCALE, (_locale_t)0); +} + + +// utility macros and relevant predefined variables for use with the service + macros +extern api_language *languageManager; +#define WASABI_API_LNG languageManager + +extern HINSTANCE api_localised_hinstance; +#define WASABI_API_LNG_HINST api_localised_hinstance + +extern HINSTANCE api_orig_hinstance; +#define WASABI_API_ORIG_HINST api_orig_hinstance + +#define WASABI_API_LNGSTR WASABI_API_LNG->GetString +// use this is you want a temp copy of the string +#define WASABI_API_LNGSTRING(uID) \ + WASABI_API_LNGSTR(WASABI_API_LNG_HINST,WASABI_API_ORIG_HINST,uID) +// use this is you want a temp copy of the string but need it to fallback to a different module +#define WASABI_API_LNGSTRING_HINST(hinst,uID) \ + WASABI_API_LNGSTR(WASABI_API_LNG_HINST,hinst,uID) +// use this is you want a copy of the string +#define WASABI_API_LNGSTRING_BUF(uID,buf,len) \ + WASABI_API_LNGSTR(WASABI_API_LNG_HINST,WASABI_API_ORIG_HINST,uID,buf,len) +// use this is you want a copy of the string but need it to fallback to a different module +#define WASABI_API_LNGSTRING_HINST_BUF(hinst,uID,buf,len) \ + WASABI_API_LNGSTR(WASABI_API_LNG_HINST,hinst,uID,buf,len) + +// unicode versions of the above macros +#define WASABI_API_LNGSTRW WASABI_API_LNG->GetStringW +#define WASABI_API_LNGSTRINGW(uID) \ + WASABI_API_LNGSTRW(WASABI_API_LNG_HINST,WASABI_API_ORIG_HINST,uID) +#define WASABI_API_LNGSTRINGW_HINST(hinst,uID) \ + WASABI_API_LNGSTRW(WASABI_API_LNG_HINST,hinst,uID) +#define WASABI_API_LNGSTRINGW_BUF(uID,buf,len) \ + WASABI_API_LNGSTRW(WASABI_API_LNG_HINST,WASABI_API_ORIG_HINST,uID,buf,len) +#define WASABI_API_LNGSTRINGW_BUF_HINST(hinst,uID,buf,len) \ + WASABI_API_LNGSTRW(WASABI_API_LNG_HINST,hinst,uID,buf,len) + +// Dialog handling functions (will revert back to the non-localised version if not valid/present) +#define WASABI_API_CREATEDIALOGPARAM(id, parent, proc, param) \ + WASABI_API_LNG->CreateLDialogParam(WASABI_API_LNG_HINST, WASABI_API_ORIG_HINST, id, parent, (DLGPROC)proc, param) +#define WASABI_API_CREATEDIALOG(id, parent, proc) \ + WASABI_API_LNG->CreateLDialogParam(WASABI_API_LNG_HINST, WASABI_API_ORIG_HINST, id, parent, (DLGPROC)proc, 0) + +#define WASABI_API_CREATEDIALOGPARAMW(id, parent, proc, param) \ + WASABI_API_LNG->CreateLDialogParamW(WASABI_API_LNG_HINST, WASABI_API_ORIG_HINST, id, parent, (DLGPROC)proc, param) +#define WASABI_API_CREATEDIALOGW(id, parent, proc) \ + WASABI_API_LNG->CreateLDialogParamW(WASABI_API_LNG_HINST, WASABI_API_ORIG_HINST, id, parent, (DLGPROC)proc, 0) + +#define WASABI_API_DIALOGBOXPARAM(id, parent, proc, param) \ + WASABI_API_LNG->LDialogBoxParam(WASABI_API_LNG_HINST, WASABI_API_ORIG_HINST, id, parent, (DLGPROC)proc, param) +#define WASABI_API_DIALOGBOX(id, parent, proc) \ + WASABI_API_LNG->LDialogBoxParam(WASABI_API_LNG_HINST, WASABI_API_ORIG_HINST, id, parent, (DLGPROC)proc, 0) + +#define WASABI_API_DIALOGBOXPARAMW(id, parent, proc, param) \ + WASABI_API_LNG->LDialogBoxParamW(WASABI_API_LNG_HINST, WASABI_API_ORIG_HINST, id, parent, (DLGPROC)proc, param) +#define WASABI_API_DIALOGBOXW(id, parent, proc) \ + WASABI_API_LNG->LDialogBoxParamW(WASABI_API_LNG_HINST, WASABI_API_ORIG_HINST, id, parent, (DLGPROC)proc, 0) + +#define WASABI_API_LOADMENU(id) \ + WASABI_API_LNG->LoadLMenu(WASABI_API_LNG_HINST, WASABI_API_ORIG_HINST, id) +#define WASABI_API_LOADMENUW(id) \ + WASABI_API_LNG->LoadLMenuW(WASABI_API_LNG_HINST, WASABI_API_ORIG_HINST, id) + + +#define WASABI_API_LOADACCELERATORSA(__id) \ + WASABI_API_LNG->LoadAcceleratorsA(WASABI_API_LNG_HINST, WASABI_API_ORIG_HINST, MAKEINTRESOURCEA(__id)) +#define WASABI_API_LOADACCELERATORSW(__id) \ + WASABI_API_LNG->LoadAcceleratorsW(WASABI_API_LNG_HINST, WASABI_API_ORIG_HINST, MAKEINTRESOURCEW(__id)) + +#ifdef UNICODE +#define WASABI_API_LOADACCELERATORS WASABI_API_LOADACCELERATORSW +#else +#define WASABI_API_LOADACCELERATORS WASABI_API_LOADACCELERATORSA +#endif + +#define WASABI_API_START_LANG(orig_hinst, guid) \ +{ \ + WASABI_API_ORIG_HINST = orig_hinst; \ + WASABI_API_LNG_HINST = WASABI_API_LNG->StartLanguageSupport(WASABI_API_ORIG_HINST,guid); \ +} + +#define WASABI_API_LOADRESFROMFILE(lpType, lpName, size) \ + WASABI_API_LNG->LoadResourceFromFile(WASABI_API_LNG_HINST, WASABI_API_ORIG_HINST, lpType, lpName, size) +#define WASABI_API_LOADRESFROMFILEW(lpType, lpName, size) \ + WASABI_API_LNG->LoadResourceFromFileW(WASABI_API_LNG_HINST, WASABI_API_ORIG_HINST, lpType, lpName, size) + +// {30AED4E5-EF10-4277-8D49-27AB5570E891} +static const GUID languageApiGUID = +{ 0x30aed4e5, 0xef10, 0x4277, { 0x8d, 0x49, 0x27, 0xab, 0x55, 0x70, 0xe8, 0x91 } }; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/wasabi/Agave/Language/lang.h b/Src/Plugins/Input/in_wv/wasabi/Agave/Language/lang.h new file mode 100644 index 00000000..59cd684a --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Agave/Language/lang.h @@ -0,0 +1,595 @@ +#ifndef _LANG_GUIDS_H_ +#define _LANG_GUIDS_H_ +#ifdef __cplusplus +extern "C" { +#endif + +// this is just the stringtable id and the +// stringtable block where the guid is stored +#define LANG_DLL_GUID_STRING_ID 65535 +#define LANG_DLL_GUID_BLOCK_ID 4096 + +// this is the stringtable id in winamp.exe's lng file where +// the language of the lang pack is declared. +// the actual string is defined as language_code-country_code +// e.g. +// en-US - US English +// en-GB - UK English +#define LANG_PACK_LANG_ID 65534 + +// this is the indentifiers required as of 5.57+ for the localisation +// 'about page' shown in supporting language packs in the 'about' box +#define LANG_DLL_AUTHOR_HOMEPAGE 65533 +#define LANG_DLL_AUTHOR_HOMEPAGE2 65532 +#define LANG_DLL_ABOUT_TRANSLATION_DLG_ID 1378 + +// this holds all of the guids that will be used in modules lng files +// using these will allow you to make use of the language resources +// between plugins / for 3rd party plugins for compatability support + +// {A0099AA7-F980-45cf-818D-64EAA9F4EF4B} +static const GUID WinampLangGUID = +{ 0xa0099aa7, 0xf980, 0x45cf, { 0x81, 0x8d, 0x64, 0xea, 0xa9, 0xf4, 0xef, 0x4b } }; + +// {0E844B2A-70E8-4007-A73A-E9C05DB3F06D} +static const GUID WinampALangGUID = +{ 0xe844b2a, 0x70e8, 0x4007, { 0xa7, 0x3a, 0xe9, 0xc0, 0x5d, 0xb3, 0xf0, 0x6d } }; + +// {250FAA3C-20CD-49db-A932-67B1C0191B0E} +static const GUID GenHotkeysLangGUID = +{ 0x250faa3c, 0x20cd, 0x49db, { 0xa9, 0x32, 0x67, 0xb1, 0xc0, 0x19, 0x1b, 0xe } }; + +// {25B50046-5B31-418b-B77E-1B0D140D64ED} +static const GUID GenTrayLangGUID = +{ 0x25b50046, 0x5b31, 0x418b, { 0xb7, 0x7e, 0x1b, 0xd, 0x14, 0xd, 0x64, 0xed } }; + +// {3D968813-F245-40ad-8589-5599C754B924} +static const GUID GenMlLangGUID = +{ 0x3d968813, 0xf245, 0x40ad, { 0x85, 0x89, 0x55, 0x99, 0xc7, 0x54, 0xb9, 0x24 } }; + +// {A3A1E7C0-761B-4391-A08A-F0D7AF38931C} +static const GUID MlBookmarkLangGUID = +{ 0xa3a1e7c0, 0x761b, 0x4391, { 0xa0, 0x8a, 0xf0, 0xd7, 0xaf, 0x38, 0x93, 0x1c } }; + +// {40450D34-E85A-428c-A01C-B2546BF23CE0} +static const GUID MlDashboardLangGUID = +{ 0x40450d34, 0xe85a, 0x428c, { 0xa0, 0x1c, 0xb2, 0x54, 0x6b, 0xf2, 0x3c, 0xe0 } }; + +// {168BA411-7E26-4749-98F0-FF02810D9B51} +static const GUID MlADDONSLangGUID = +{ 0x168ba411, 0x7e26, 0x4749, { 0x98, 0xf0, 0xff, 0x2, 0x81, 0xd, 0x9b, 0x51 } }; + +// {7F31F590-6602-45c9-B3F8-F61AE05BD1D3} +static const GUID MlNowPlayingLangGUID = +{ 0x7f31f590, 0x6602, 0x45c9, { 0xb3, 0xf8, 0xf6, 0x1a, 0xe0, 0x5b, 0xd1, 0xd3 } }; + +// {F8756C00-11D2-4857-8C50-163AE4A57783} +static const GUID MlHistoryLangGUID = +{ 0xf8756c00, 0x11d2, 0x4857, { 0x8c, 0x50, 0x16, 0x3a, 0xe4, 0xa5, 0x77, 0x83 } }; + +// {5E766B4F-818E-4f14-9C42-0902B2C571DC} +static const GUID MlPlaylistsLangGUID = +{ 0x5e766b4f, 0x818e, 0x4f14, { 0x9c, 0x42, 0x9, 0x2, 0xb2, 0xc5, 0x71, 0xdc } }; + +// {D006C700-557E-43c7-A580-B4C50C56957A} +static const GUID MlOnlineLangGUID = +{ 0xd006c700, 0x557e, 0x43c7, { 0xa5, 0x80, 0xb4, 0xc5, 0xc, 0x56, 0x95, 0x7a } }; + +// {5F633543-148D-48cc-B683-DA82F592CF28} +static const GUID MlReplayGainLangGUID = +{ 0x5f633543, 0x148d, 0x48cc, { 0xb6, 0x83, 0xda, 0x82, 0xf5, 0x92, 0xcf, 0x28 } }; + +// {699B8BA5-B292-4aba-8047-D46B0DF4E1D6} +static const GUID MlTranscodeLangGUID = +{ 0x699b8ba5, 0xb292, 0x4aba, { 0x80, 0x47, 0xd4, 0x6b, 0xd, 0xf4, 0xe1, 0xd6 } }; + +// {34DF1A2D-7EAD-41ab-B1A7-9AFA6DE2AFF1} +static const GUID EncWavLangGUID = +{ 0x34df1a2d, 0x7ead, 0x41ab, { 0xb1, 0xa7, 0x9a, 0xfa, 0x6d, 0xe2, 0xaf, 0xf1 } }; + +// {33BC12FD-E7F7-42ec-8FE3-2D8BD3A977C2} +static const GUID EncWMALangGUID = +{ 0x33bc12fd, 0xe7f7, 0x42ec, { 0x8f, 0xe3, 0x2d, 0x8b, 0xd3, 0xa9, 0x77, 0xc2 } }; + +// {8FBADBBB-B4D5-47d9-A723-5C8C8E88EE73} +static const GUID EncAACLangGUID = +{ 0x8fbadbbb, 0xb4d5, 0x47d9, { 0xa7, 0x23, 0x5c, 0x8c, 0x8e, 0x88, 0xee, 0x73 } }; + +// {F1534ECA-6E64-42c2-9781-812E61154515} +static const GUID EncLameLangGUID = +{ 0xf1534eca, 0x6e64, 0x42c2, { 0x97, 0x81, 0x81, 0x2e, 0x61, 0x15, 0x45, 0x15 } }; + +// deprecated enc_flac.dll based on FLAKE +// {5C0BA1EE-5A59-47cc-BC28-5B9F0C5EA1B7} +static const GUID EncFlakeLangGUID = +{ 0x5c0ba1ee, 0x5a59, 0x47cc, { 0xbc, 0x28, 0x5b, 0x9f, 0xc, 0x5e, 0xa1, 0xb7 } }; + +// {A23C2B70-C66B-475e-8A67-E0F33FD5BD12} +static const GUID EncVorbisLangGUID = +{ 0xa23c2b70, 0xc66b, 0x475e, { 0x8a, 0x67, 0xe0, 0xf3, 0x3f, 0xd5, 0xbd, 0x12 } }; + +// {D40620FB-E44B-47b3-98EE-8E5A089C0C94} +static const GUID tagzLangGUID = +{ 0xd40620fb, 0xe44b, 0x47b3, { 0x98, 0xee, 0x8e, 0x5a, 0x8, 0x9c, 0xc, 0x94 } }; + +// {06A3F81D-043D-4b5c-B341-590ED7053492} +static const GUID MlLocalLangGUID = +{ 0x6a3f81d, 0x43d, 0x4b5c, { 0xb3, 0x41, 0x59, 0xe, 0xd7, 0x5, 0x34, 0x92 } }; + +// {706549D3-D813-45dd-9A0B-E3793A1B63A8} +static const GUID MlDownloadsLangGUID = +{ 0x706549d3, 0xd813, 0x45dd, { 0x9a, 0xb, 0xe3, 0x79, 0x3a, 0x1b, 0x63, 0xa8 } }; + +// {1FF327B2-A41D-4c67-A58A-EB09BA1470D3} +static const GUID MlWireLangGUID = +{ 0x1ff327b2, 0xa41d, 0x4c67, { 0xa5, 0x8a, 0xeb, 0x9, 0xba, 0x14, 0x70, 0xd3 } }; + +// {04C986EE-9CE3-4369-820D-A64394C63D60} +static const GUID MlPMPLangGUID = +{ 0x4c986ee, 0x9ce3, 0x4369, { 0x82, 0xd, 0xa6, 0x43, 0x94, 0xc6, 0x3d, 0x60 } }; + +// {E553C1A4-5DE2-4838-8000-FDF8DC377DD4} +static const GUID PmpUSBLangGUID = +{ 0xe553c1a4, 0x5de2, 0x4838, { 0x80, 0x0, 0xfd, 0xf8, 0xdc, 0x37, 0x7d, 0xd4 } }; + +// {01C3E74C-261E-45e2-AA30-ED4039DCD3A2} +static const GUID PmpP4SLangGUID = +{ 0x1c3e74c, 0x261e, 0x45e2, { 0xaa, 0x30, 0xed, 0x40, 0x39, 0xdc, 0xd3, 0xa2 } }; + +// {4F5B2300-19D1-4390-BE04-89019441100B} +static const GUID PmpNJBLangGUID = +{ 0x4f5b2300, 0x19d1, 0x4390, { 0xbe, 0x4, 0x89, 0x1, 0x94, 0x41, 0x10, 0xb } }; + +// {B81F32B8-4AA4-4eba-8798-95F13812F638} +static const GUID PmpACTIVESYNCLangGUID = +{ 0xb81f32b8, 0x4aa4, 0x4eba, { 0x87, 0x98, 0x95, 0xf1, 0x38, 0x12, 0xf6, 0x38 } }; + +// {C2EE3DA5-B29B-42a0-AB5E-B202393435D6} +static const GUID PmpIPODLangGUID = +{ 0xc2ee3da5, 0xb29b, 0x42a0, { 0xab, 0x5e, 0xb2, 0x2, 0x39, 0x34, 0x35, 0xd6 } }; + +// {2C913A2F-CD49-40a1-8F1A-8EF7C2A22229} +static const GUID MlDiscLangGUID = +{ 0x2c913a2f, 0xcd49, 0x40a1, { 0x8f, 0x1a, 0x8e, 0xf7, 0xc2, 0xa2, 0x22, 0x29 } }; + +// {1A710E67-5180-49ac-8102-105856ED0A2F} +static const GUID OutDiskLangGUID = +{ 0x1a710e67, 0x5180, 0x49ac, { 0x81, 0x2, 0x10, 0x58, 0x56, 0xed, 0xa, 0x2f } }; + +// {858FBF71-9878-4d86-BFDD-8FEA8361238C} +static const GUID VisNFSFLangGUID = +{ 0x858fbf71, 0x9878, 0x4d86, { 0xbf, 0xdd, 0x8f, 0xea, 0x83, 0x61, 0x23, 0x8c } }; + +// {BE608673-B723-4a59-9EBA-52DC77109E10} +static const GUID VisAVSLangGUID = +{ 0xbe608673, 0xb723, 0x4a59, { 0x9e, 0xba, 0x52, 0xdc, 0x77, 0x10, 0x9e, 0x10 } }; + +// {226275F6-3318-4d4b-A6B3-5B1B5B077BE8} +static const GUID VisMilkdropLangGUID = +{ 0x226275f6, 0x3318, 0x4d4b, { 0xa6, 0xb3, 0x5b, 0x1b, 0x5b, 0x7, 0x7b, 0xe8 } }; + +// {C5D175F1-E4E4-47ee-B85C-4EDC6B026A35} +static const GUID VisMilk2LangGUID = +{ 0xc5d175f1, 0xe4e4, 0x47ee, { 0xb8, 0x5c, 0x4e, 0xdc, 0x6b, 0x2, 0x6a, 0x35 } }; + +// {87DCEEC2-1EC3-4c59-BED4-E8F42232C7D8} +static const GUID InCDDALangGUID = +{ 0x87dceec2, 0x1ec3, 0x4c59, { 0xbe, 0xd4, 0xe8, 0xf4, 0x22, 0x32, 0xc7, 0xd8 } }; + +// {20395FD0-AC67-446d-B8D0-D88BFD3174FC} +static const GUID IndshowLangGUID = +{ 0x20395fd0, 0xac67, 0x446d, { 0xb8, 0xd0, 0xd8, 0x8b, 0xfd, 0x31, 0x74, 0xfc } }; + +// {9475116B-F8C4-4dff-BC19-9601B238557D} +static const GUID InFlacLangGUID = +{ 0x9475116b, 0xf8c4, 0x4dff, { 0xbc, 0x19, 0x96, 0x1, 0xb2, 0x38, 0x55, 0x7d } }; + +// {EA1C197A-D227-474c-A9FD-1C79DE722BDD} +static const GUID InLineInLangGUID = +{ 0xea1c197a, 0xd227, 0x474c, { 0xa9, 0xfd, 0x1c, 0x79, 0xde, 0x72, 0x2b, 0xdd } }; + +// {96374982-0142-41a5-AEDE-244505C45D30} +static const GUID InWavLangGUID = +{ 0x96374982, 0x142, 0x41a5, { 0xae, 0xde, 0x24, 0x45, 0x5, 0xc4, 0x5d, 0x30 } }; + +// {5C5BCA4E-279E-4867-8E24-58C8B186959A} +static const GUID InVorbisLangGUID = +{ 0x5c5bca4e, 0x279e, 0x4867, { 0x8e, 0x24, 0x58, 0xc8, 0xb1, 0x86, 0x95, 0x9a } }; + +// {A786C0B0-69DE-49e2-9461-4F592808B0B3} +static const GUID ndeLangGUID = +{ 0xa786c0b0, 0x69de, 0x49e2, { 0x94, 0x61, 0x4f, 0x59, 0x28, 0x8, 0xb0, 0xb3 } }; + +// {11B847DB-29A7-47ac-B386-43B40385B817} +static const GUID InNSVLangGUID = +{ 0x11b847db, 0x29a7, 0x47ac, { 0xb3, 0x86, 0x43, 0xb4, 0x3, 0x85, 0xb8, 0x17 } }; + +// {5F24DF00-C163-4eaa-AB9D-22F106588C25} +static const GUID MLOrbLangGUID = +{ 0x5f24df00, 0xc163, 0x4eaa, { 0xab, 0x9d, 0x22, 0xf1, 0x6, 0x58, 0x8c, 0x25 } }; + +// {0FED0FEE-C995-4499-AB47-E2482336C046} +static const GUID InMidiLangGUID = +{ 0xfed0fee, 0xc995, 0x4499, { 0xab, 0x47, 0xe2, 0x48, 0x23, 0x36, 0xc0, 0x46 } }; + +// {F30C75C1-D284-4cd5-9CED-2BD9E7869438} +static const GUID InMp4LangGUID = +{ 0xf30c75c1, 0xd284, 0x4cd5, { 0x9c, 0xed, 0x2b, 0xd9, 0xe7, 0x86, 0x94, 0x38 } }; + +// {CD3EEF98-011C-4213-BC16-3F91C937B9B8} +static const GUID InMp3LangGUID = +{ 0xcd3eef98, 0x11c, 0x4213, { 0xbc, 0x16, 0x3f, 0x91, 0xc9, 0x37, 0xb9, 0xb8 } }; + +// {A1A39D49-671A-4c2f-AE42-BEA134EAF6A9} +static const GUID InModLangGUID = +{ 0xa1a39d49, 0x671a, 0x4c2f, { 0xae, 0x42, 0xbe, 0xa1, 0x34, 0xea, 0xf6, 0xa9 } }; + +// {4B567AEB-89CE-4881-9D7D-B31D7B65979A} +static const GUID DspSpsLangGUID = +{ 0x4b567aeb, 0x89ce, 0x4881, { 0x9d, 0x7d, 0xb3, 0x1d, 0x7b, 0x65, 0x97, 0x9a } }; + +// {004A91D9-CCD6-44e5-973A-4B7045C4662B} +static const GUID OutWaveLangGUID = +{ 0x4a91d9, 0xccd6, 0x44e5, { 0x97, 0x3a, 0x4b, 0x70, 0x45, 0xc4, 0x66, 0x2b } }; + +// {A812F3D3-633B-4af6-8749-3BA75290BAC0} +static const GUID OutDSLangGUID = +{ 0xa812f3d3, 0x633b, 0x4af6, { 0x87, 0x49, 0x3b, 0xa7, 0x52, 0x90, 0xba, 0xc0 } }; + +// {C5B78F09-3222-4a64-AA98-F1ABC5A9E355} +static const GUID InWmLangGUID = +{ 0xc5b78f09, 0x3222, 0x4a64, { 0xaa, 0x98, 0xf1, 0xab, 0xc5, 0xa9, 0xe3, 0x55 } }; + +// {C14FAE1D-B410-459f-B008-1A8BE3633000} +static const GUID burnlibLangGUID = +{ 0xc14fae1d, 0xb410, 0x459f, { 0xb0, 0x8, 0x1a, 0x8b, 0xe3, 0x63, 0x30, 0x0 } }; + +// {ACD05A75-030B-4943-A100-540DAD98FB00} +static const GUID GenFFLangGUID = +{ 0xacd05a75, 0x30b, 0x4943, { 0xa1, 0x0, 0x54, 0xd, 0xad, 0x98, 0xfb, 0x0 } }; + +// {0CE0174D-8334-479e-B322-9D80D48FC74D} +static const GUID MlPlgLangGUID = +{ 0xce0174d, 0x8334, 0x479e, { 0xb3, 0x22, 0x9d, 0x80, 0xd4, 0x8f, 0xc7, 0x4d } }; + +// {9E398E5F-EDEC-4dd8-A40D-E29B385A88C0} +static const GUID playlistLangGUID = +{ 0x9e398e5f, 0xedec, 0x4dd8, { 0xa4, 0xd, 0xe2, 0x9b, 0x38, 0x5a, 0x88, 0xc0 } }; + +// {092A97EF-7DC0-41a7-80D1-90DEEB18F12D} +static const GUID GenCrasherLangGUID = +{ 0x92a97ef, 0x7dc0, 0x41a7, { 0x80, 0xd1, 0x90, 0xde, 0xeb, 0x18, 0xf1, 0x2d } }; + +// {D8DBA660-90BD-431d-8F4E-189D6ACB407E} +static const GUID MlAutoTagLangGUID = +{ 0xd8dba660, 0x90bd, 0x431d, { 0x8f, 0x4e, 0x18, 0x9d, 0x6a, 0xcb, 0x40, 0x7e } }; + +// {EC959D43-9122-4807-B928-7B46207AFA49} +static const GUID InFlvLangGUID = +{ 0xec959d43, 0x9122, 0x4807, { 0xb9, 0x28, 0x7b, 0x46, 0x20, 0x7a, 0xfa, 0x49 } }; + +// {2430A7AC-317D-4d64-B33C-E1452A6384A2} +static const GUID InSwfLangGUID = +{ 0x2430a7ac, 0x317d, 0x4d64, { 0xb3, 0x3c, 0xe1, 0x45, 0x2a, 0x63, 0x84, 0xa2 } }; + +// {22661553-8D22-4012-8D3B-0FF8FE57A9ED} +static const GUID MlImpexLangGUID = +{ 0x22661553, 0x8d22, 0x4012, { 0x8d, 0x3b, 0xf, 0xf8, 0xfe, 0x57, 0xa9, 0xed } }; + +// {73760073-560C-433b-BC59-3FCC94CDEA4A} +static const GUID EncFlacLangGUID = +{ 0x73760073, 0x560c, 0x433b, { 0xbc, 0x59, 0x3f, 0xcc, 0x94, 0xcd, 0xea, 0x4a } }; + +// {95C65BA3-3C34-40ec-AE74-8D2C60AAE3C8} +static const GUID authLangGUID = +{ 0x95c65ba3, 0x3c34, 0x40ec, { 0xae, 0x74, 0x8d, 0x2c, 0x60, 0xaa, 0xe3, 0xc8 } }; + +// {CA36E14A-3742-4edc-A40F-2BC87F26B347} +static const GUID InAviLangGUID = +{ 0xca36e14a, 0x3742, 0x4edc, { 0xa4, 0xf, 0x2b, 0xc8, 0x7f, 0x26, 0xb3, 0x47 } }; + +// {5BDA8055-292D-4fcd-8404-884C2A34A8F9} +static const GUID InMkvLangGUID = +{ 0x5bda8055, 0x292d, 0x4fcd, { 0x84, 0x4, 0x88, 0x4c, 0x2a, 0x34, 0xa8, 0xf9 } }; + +// {0233DC7B-7060-43e5-8354-D2F2C7C7611D} +static const GUID GenMudLangGUID = +{ 0x233dc7b, 0x7060, 0x43e5, { 0x83, 0x54, 0xd2, 0xf2, 0xc7, 0xc7, 0x61, 0x1d } }; + +// {DCCF5A41-D16B-452b-8B7A-CFCA3360D8E8} +static const GUID omBrowserLangGUID = +{ 0xdccf5a41, 0xd16b, 0x452b, { 0x8b, 0x7a, 0xcf, 0xca, 0x33, 0x60, 0xd8, 0xe8 } }; + +// Winamp Android plugin (pmp_android.dll) +// {EBFF6E00-39D8-45e6-B3EC-E3B07A45E6B0} +static const GUID PmpAndroidLangGUID = +{ 0xebff6e00, 0x39d8, 0x45e6, { 0xb3, 0xec, 0xe3, 0xb0, 0x7a, 0x45, 0xe6, 0xb0 } }; + +// Winamp Wifi plugin (pmp_wifi.dll) +// {3066887B-CA40-4683-897F-4416FE349D7E} +static const GUID PmpWifiLangGUID = +{ 0x3066887b, 0xca40, 0x4683, { 0x89, 0x7f, 0x44, 0x16, 0xfe, 0x34, 0x9d, 0x7e } }; + + +/* +** These are guids for known 3rd party lng files +*/ + +// WavPack Input plugin (in_wv.dll) +// {6DE2E465-690E-4df1-B6E2-2A9B33ED3DBB} +static const GUID InWvLangGuid = +{ 0x6de2e465, 0x690e, 0x4df1, { 0xb6, 0xe2, 0x2a, 0x9b, 0x33, 0xed, 0x3d, 0xbb } }; + +// Nullsoft Waveform Wrapper plugin (in_wav.dll) +// {1CED00E8-4B1B-4e10-A188-9A7C6BBEB421} +static const GUID InWavLangGuid = +{ 0x1ced00e8, 0x4b1b, 0x4e10, { 0xa1, 0x88, 0x9a, 0x7c, 0x6b, 0xbe, 0xb4, 0x21 } }; + +// Jump To File Extra (JTFE) plugin (gen_jumpex.dll) +// Note: this used to be {243355FE-8B16-48d2-89C3-FD80B3902875} but was changed with +// v1.1 (the build in 5.58) due to mass of changes to the file to ensure that +// this will work correctly if an old / partial file is present in the langpack +// {4693FA7D-2055-4b36-A239-0AD998B5A884} +static const GUID GenJTFELangGUID = +{ 0x4693fa7d, 0x2055, 0x4b36, { 0xa2, 0x39, 0xa, 0xd9, 0x98, 0xb5, 0xa8, 0x84 } }; + + +// Time Restore & Autoplay (TRAP) plugin (gen_timerestore.dll) +// {75854C46-1F1A-4fae-B3FA-EEA6B253490E} +static const GUID GenTRAPLangGUID = +{ 0x75854c46, 0x1f1a, 0x4fae, { 0xb3, 0xfa, 0xee, 0xa6, 0xb2, 0x53, 0x49, 0xe } }; + +// Playlist File Remover (PLFR) plugin (gen_play_remove.dll) +// {58D8276F-12DD-44a7-A930-AA336BC8BA9A} +static const GUID GenPLFRLangGUID = +{ 0x58d8276f, 0x12dd, 0x44a7, { 0xa9, 0x30, 0xaa, 0x33, 0x6b, 0xc8, 0xba, 0x9a } }; + +// Skin Manager plugin (gen_skinmanager.dll) +// {D877C116-0201-44b2-A003-335C0600BF7A} +static const GUID GenSkinManagerGUID = +{ 0xd877c116, 0x201, 0x44b2, { 0xa0, 0x3, 0x33, 0x5c, 0x6, 0x0, 0xbf, 0x7a } }; + +// Playlist Undo plugin (gen_undo.dll) +// {3050F3A7-DADB-459f-900A-A8A224B7F32D} +static const GUID GenUndoLangGUID = +{ 0x3050f3a7, 0xdadb, 0x459f, { 0x90, 0xa, 0xa8, 0xa2, 0x24, 0xb7, 0xf3, 0x2d } }; + +// Playlist Separator plugin (in_text.dll) +// {505CAF53-D00E-4580-AA67-B31DEA6FE946} +static const GUID InTextLangGUID = +{ 0x505caf53, 0xd00e, 0x4580, { 0xaa, 0x67, 0xb3, 0x1d, 0xea, 0x6f, 0xe9, 0x46 } }; + +// One for Nunz plugin (gen_nunzio.dll) +// {CB659857-7468-40ef-BC51-844449253780} +static const GUID GenOne4NunzLangGUID = +{ 0xcb659857, 0x7468, 0x40ef, { 0xbc, 0x51, 0x84, 0x44, 0x49, 0x25, 0x37, 0x80 } }; + +// Save File As plugin (gen_saveas.dll) +// {71174948-4965-4f61-90F5-E53FF30E6578} +static const GUID GenSaveAsLangGUID = +{ 0x71174948, 0x4965, 0x4f61, { 0x90, 0xf5, 0xe5, 0x3f, 0xf3, 0xe, 0x65, 0x78 } }; + +// Yar-matey! Playlist Copier plugin (gen_yar.dll) +// {9725C8BF-B577-4d72-93EF-5FB41D88FFC2} +static const GUID GenYarLangGUID = +{ 0x9725c8bf, 0xb577, 0x4d72, { 0x93, 0xef, 0x5f, 0xb4, 0x1d, 0x88, 0xff, 0xc2 } }; + +// Album Art plugin (gen_classicart.dll) +// {EAD1E933-6D75-4c2c-B9C4-B4D7F06B7D8D} +static const GUID GenClasicArtGUID = +{ 0xead1e933, 0x6d75, 0x4c2c, { 0xb9, 0xc4, 0xb4, 0xd7, 0xf0, 0x6b, 0x7d, 0x8d } }; + +// Windows 7 Taskbar Integration plugin (gen_win7shell.dll) +// {7204A532-5D37-415d-B431-272C953B7459} +static const GUID GenWin7ShellLangGUID = +{ 0x7204a532, 0x5d37, 0x415d, { 0xb4, 0x31, 0x27, 0x2c, 0x95, 0x3b, 0x74, 0x59 } }; + +// Find File on Disk plugin (gen_find_on_disk.dll) +// {8CCF206C-1EA0-484e-88A3-943B4C4AF272} +static const GUID GenFFODLangGUID = +{ 0x8ccf206c, 0x1ea0, 0x484e, { 0x88, 0xa3, 0x94, 0x3b, 0x4c, 0x4a, 0xf2, 0x72 } }; + +// ML Bookmark Categoriser plugin (ml_bkmk.dll) +// {C3BC5F81-B400-4c64-BCC5-3B758D6BE2E1} +static const GUID MlBkCatLangGUID = +{ 0xc3bc5f81, 0xb400, 0x4c64, { 0xbc, 0xc5, 0x3b, 0x75, 0x8d, 0x6b, 0xe2, 0xe1 } }; + +// Lite'n Winamp Preferences plugin (gen_nopro.dll) +// {E6C98DDD-FC99-4ccc-B845-79A81B8C1959} +static const GUID GenNoProLangGUID = +{ 0xe6c98ddd, 0xfc99, 0x4ccc, { 0xb8, 0x45, 0x79, 0xa8, 0x1b, 0x8c, 0x19, 0x59 } }; + +// ML Enqueue & Play plugin (ml_enqplay.dll) +// {0DF6B872-74C3-4236-BE78-E1EAE665C62D} +static const GUID MlEnqPlayLangGUID = +{ 0xdf6b872, 0x74c3, 0x4236, { 0xbe, 0x78, 0xe1, 0xea, 0xe6, 0x65, 0xc6, 0x2d } }; + +// YMAMP (in_ym.dll) +// {C5F9EFFA-4727-4075-9017-A6BAE72B848C} +static const GUID InYMAMPLangGUID = +{ 0xc5f9effa, 0x4727, 0x4075, { 0x90, 0x17, 0xa6, 0xba, 0xe7, 0x2b, 0x84, 0x8c } }; + +// SNESAmp wrapper (in_snes.dll + in_snes.trb + in_snes_trb.lng) +// {7B2084F6-B7A7-449b-A133-12F1916F188E} +static const GUID InSNESWrapperLangGUID = +{ 0x7b2084f6, 0xb7a7, 0x449b, { 0xa1, 0x33, 0x12, 0xf1, 0x91, 0x6f, 0x18, 0x8e } }; + +// View Current File Information Hotkey (gen_wolfgang) plugin +// {E16E2C50-71AB-4188-9193-B9D5FB127F97} +static const GUID GenWolfgangLangGUID = +{ 0xe16e2c50, 0x71ab, 0x4188, { 0x91, 0x93, 0xb9, 0xd5, 0xfb, 0x12, 0x7f, 0x97 } }; + +// Mute Hotkey (gen_mute) plugin +// {E87B8C7F-51DA-442c-BB2A-D5F941318853} +static const GUID GenMuteLangGUID = +{ 0xe87b8c7f, 0x51da, 0x442c, { 0xbb, 0x2a, 0xd5, 0xf9, 0x41, 0x31, 0x88, 0x53 } }; + +// Play Random Song Hotkey (gen_prs) plugin +// {1112230B-6928-4f20-BD0E-F559FE6AD66E} +static const GUID GenPRSLangGUID = +{ 0x1112230b, 0x6928, 0x4f20, { 0xbd, 0xe, 0xf5, 0x59, 0xfe, 0x6a, 0xd6, 0x6e } }; + +// Randomise Playlist Hotkey (gen_grp) plugin +// {554151CC-ADEC-4bdc-8A96-7812BF69058D} +static const GUID GenRandPLLangGUID = +{ 0x554151cc, 0xadec, 0x4bdc, { 0x8a, 0x96, 0x78, 0x12, 0xbf, 0x69, 0x5, 0x8d } }; + +// Clear Current Playlist Hotkey (gen_gcp) plugin +// {1C71FF32-D2E1-403b-B39C-897AF7F4B4AE} +static const GUID GenCleardPLLangGUID = +{ 0x1c71ff32, 0xd2e1, 0x403b, { 0xb3, 0x9c, 0x89, 0x7a, 0xf7, 0xf4, 0xb4, 0xae } }; + +// EQ Hotkeys (gen_eq_hotkeys) plugin +// {4EA319B6-955A-4519-807E-A36EEDDC6224} +static const GUID GenEQGHKLangGUID = +{ 0x4ea319b6, 0x955a, 0x4519, { 0x80, 0x7e, 0xa3, 0x6e, 0xed, 0xdc, 0x62, 0x24 } }; + +// Auto EQ (gen_autoeq) plugin +// {5A2E5855-239A-44a6-A49B-1F495BBFD0D6} +static const GUID GenAutoEQLangGUID = +{ 0x5a2e5855, 0x239a, 0x44a6, { 0xa4, 0x9b, 0x1f, 0x49, 0x5b, 0xbf, 0xd0, 0xd6 } }; + +// CD Menu Tweaker (gen_cd_menu.dll) +// {A609C17B-44F3-47d8-9B76-C660FF5D3739} +static const GUID GenCDMenuTweakLangGUID = +{ 0xa609c17b, 0x44f3, 0x47d8, { 0x9b, 0x76, 0xc6, 0x60, 0xff, 0x5d, 0x37, 0x39 } }; + +// Three Mode Repeat (gen_3mode.dll) +// {81EE2A10-80E9-4d22-B363-AEA820AE988F} +static const GUID Gen3ModeLangGUID = +{ 0x81ee2a10, 0x80e9, 0x4d22, { 0xb3, 0x63, 0xae, 0xa8, 0x20, 0xae, 0x98, 0x8f } }; + +// Taskbar Text Mod (gen_ttm.dll) +// {BBFD3662-DBDF-417a-AAAC-23914D55F24B} +static const GUID GenTTMLangGUID = +{ 0xbbfd3662, 0xdbdf, 0x417a, { 0xaa, 0xac, 0x23, 0x91, 0x4d, 0x55, 0xf2, 0x4b } }; + +// OS Pos Restorer (gen_os_diag.dll) +// {B5A3AD19-2180-45d7-AFFE-80D2B7575CD1} +static const GUID GenOSDiagLangGUID = +{ 0xb5a3ad19, 0x2180, 0x45d7, { 0xaf, 0xfe, 0x80, 0xd2, 0xb7, 0x57, 0x5c, 0xd1 } }; + +// Shuffle Restorer (gen_shuffle_restorer.dll) +// {B13ED906-B8E9-4753-B03F-351B05A6E250} +static const GUID GenShuffleRestorerLangGUID = +{ 0xb13ed906, 0xb8e9, 0x4753, { 0xb0, 0x3f, 0x35, 0x1b, 0x5, 0xa6, 0xe2, 0x50 } }; + +// Shuffle Change Blocker (gen_shufblock.dll) +// {FCCFABF2-6EF3-4651-A43C-F7CA38176889} +static const GUID GenShuffleBlockerLangGUID = +{ 0xfccfabf2, 0x6ef3, 0x4651, { 0xa4, 0x3c, 0xf7, 0xca, 0x38, 0x17, 0x68, 0x89 } }; + +// Single Click 'n' Play (gen_singleclick.dll) +// {AF67A1E2-8827-4fa3-9F9F-3A3DE2886022} +static const GUID GenSingleClickLangGUID = +{ 0xaf67a1e2, 0x8827, 0x4fa3, { 0x9f, 0x9f, 0x3a, 0x3d, 0xe2, 0x88, 0x60, 0x22 } }; + +// No Minimise (gen_no_min.dll) +// {8BCF7C51-6F88-455f-88FD-0B6911650997} +static const GUID GenNoMinimiseLangGUID = +{ 0x8bcf7c51, 0x6f88, 0x455f, { 0x88, 0xfd, 0xb, 0x69, 0x11, 0x65, 0x9, 0x97 } }; + +// Repeater (gen_repeater.dll) +// {1C4C8774-8BBC-4f11-851E-936BF5C85E96} +static const GUID GenRepeaterLangGUID = +{ 0x1c4c8774, 0x8bbc, 0x4f11, { 0x85, 0x1e, 0x93, 0x6b, 0xf5, 0xc8, 0x5e, 0x96 } }; + +// Alt Close (gen_alt_close.dll) +// {0FD70024-FA0E-4f4f-A0D4-CD560913C146} +static const GUID GenAltCloseLangGUID = +{ 0xfd70024, 0xfa0e, 0x4f4f, { 0xa0, 0xd4, 0xcd, 0x56, 0x9, 0x13, 0xc1, 0x46 } }; + +// ML Exporter (ml_exporter.dll) +// {3B441F40-E8E9-46bf-B399-556FB6CD4295} +static const GUID MLExporterLangGUID = +{ 0x3b441f40, 0xe8e9, 0x46bf, { 0xb3, 0x99, 0x55, 0x6f, 0xb6, 0xcd, 0x42, 0x95 } }; + +// Silence Detector DSP (dsp_silence_detect.dll) +// {15CCEBE6-C1F5-4246-A7B0-A6E66025C01C} +static const GUID DspSilenceDetectLangGUID = +{ 0x15ccebe6, 0xc1f5, 0x4246, { 0xa7, 0xb0, 0xa6, 0xe6, 0x60, 0x25, 0xc0, 0x1c } }; + +// Skinned Preferences (gen_prefs_skin.dll) +// {AE99B23F-0E51-4a99-9AB0-21AEA7B4B3CA} +static const GUID GenSkinPrefsLangGUID = +{ 0xae99b23f, 0xe51, 0x4a99, { 0x9a, 0xb0, 0x21, 0xae, 0xa7, 0xb4, 0xb3, 0xca } }; + +// Jump to Track (gen_jtt.dll) +// {D7D804A3-0794-4761-B43B-4873E5B41873} +static const GUID GenJTTLangGUID = +{ 0xd7d804a3, 0x794, 0x4761, { 0xb4, 0x3b, 0x48, 0x73, 0xe5, 0xb4, 0x18, 0x73 } }; + +// Jump to Time Extra (gen_jumptotime.dll) +// {9B5DC220-F06A-44cc-909E-D2157513F280} +static const GUID GenJumpToTimeLangGUID = +{ 0x9b5dc220, 0xf06a, 0x44cc, { 0x90, 0x9e, 0xd2, 0x15, 0x75, 0x13, 0xf2, 0x80 } }; + +// Jumper (gen_jumper.dll) +// {5D793BF9-0903-4bc9-A78D-D10AB92C7EE5} +static const GUID GenJumperLangGUID = +{ 0x5d793bf9, 0x903, 0x4bc9, { 0xa7, 0x8d, 0xd1, 0xa, 0xb9, 0x2c, 0x7e, 0xe5 } }; + +// One Click Show and Hide (gen_one_click.dll) +// {8F3FCFB3-1F5A-43c6-A71E-891026479301} +static const GUID GenOneClickLangGUID = +{ 0x8f3fcfb3, 0x1f5a, 0x43c6, { 0xa7, 0x1e, 0x89, 0x10, 0x26, 0x47, 0x93, 0x1 } }; + +// Playback Excluder (gen_exclude.dll) +// {15C44197-EBC5-4cc7-B935-EDE40C9C1AF6} +static const GUID GenPlaybackExluderLangGUID = +{ 0x15c44197, 0xebc5, 0x4cc7, { 0xb9, 0x35, 0xed, 0xe4, 0xc, 0x9c, 0x1a, 0xf6 } }; + +// Close to Notification Area (gen_d3x7r0.dll) +// {2A3BC93A-99FF-469a-A94B-576218CF6265} +static const GUID GenCloseToNotAreaLangGUID = +{ 0x2a3bc93a, 0x99ff, 0x469a, { 0xa9, 0x4b, 0x57, 0x62, 0x18, 0xcf, 0x62, 0x65 } }; + +// Crop Die (gen_crop_die.dll) +// {9E79066C-58C5-41b9-9361-C1951DA989CD} +static const GUID GenCropDieLangGUID = +{ 0x9e79066c, 0x58c5, 0x41b9, { 0x93, 0x61, 0xc1, 0x95, 0x1d, 0xa9, 0x89, 0xcd } }; + +// Play Selected Song Hotkey (gen_gpss.dll) +// {94E8B2B6-685F-484b-9938-EC929F6874EC} +static const GUID GenPlaySelGHKLangGUID = +{ 0x94e8b2b6, 0x685f, 0x484b, { 0x99, 0x38, 0xec, 0x92, 0x9f, 0x68, 0x74, 0xec } }; + +// Close After Current (gen_cac.dll) +// {8B6A33FB-A6C5-49a0-A52A-0A0F14913BB2} +static const GUID GenCACLangGUID = +{ 0x8b6a33fb, 0xa6c5, 0x49a0, { 0xa5, 0x2a, 0xa, 0xf, 0x14, 0x91, 0x3b, 0xb2 } }; + +// Enhancer Wrapper DSP (dsp_enhancer.dll) +// {78842EF6-CCA2-410c-9E23-C498ABB24373} +static const GUID DspEnhancerLangGUID = +{ 0x78842ef6, 0xcca2, 0x410c, { 0x9e, 0x23, 0xc4, 0x98, 0xab, 0xb2, 0x43, 0x73 } }; + +// Shutdown on Close (gen_soc.dll) +// {CAE88304-4A0B-46e5-8B50-BEDFAE00FA6A} +static const GUID GenSOCLangGUID = +{ 0xcae88304, 0x4a0b, 0x46e5, { 0x8b, 0x50, 0xbe, 0xdf, 0xae, 0x0, 0xfa, 0x6a } }; + +// Mouse Wheel Blocker (gen_mwblock.dll) +// {C9D6697C-4C7B-4aec-A4C7-45395F0771EA} +static const GUID GenMouseWheelBlockLangGUID = +{ 0xc9d6697c, 0x4c7b, 0x4aec, { 0xa4, 0xc7, 0x45, 0x39, 0x5f, 0x7, 0x71, 0xea } }; + +// ML Icon Control plugin (ml_icon_control.dll) +// {4A55AE4D-B3CB-42df-A94E-53588FD761BA} +static const GUID MlIconControlLangGUID= +{ 0x4a55ae4d, 0xb3cb, 0x42df, { 0xa9, 0x4e, 0x53, 0x58, 0x8f, 0xd7, 0x61, 0xba } }; + +// File Copier plug-in (gen_copy.dll) +// {A2121FC9-6FC3-4a56-88F2-A36FF64D10EA} +static const GUID GenFileCopierLangGUID = +{ 0xa2121fc9, 0x6fc3, 0x4a56, { 0x88, 0xf2, 0xa3, 0x6f, 0xf6, 0x4d, 0x10, 0xea } }; + +// Shoutcast Source DSP plug-in +// {FD4D4A01-C337-4144-85D7-00678B3B2D2D} +static const GUID DspShoutcastLangGUID = +{ 0xfd4d4a01, 0xc337, 0x4144, { 0x85, 0xd7, 0x0, 0x67, 0x8b, 0x3b, 0x2d, 0x2d } }; + +#ifdef __cplusplus +} // extern "C" +#endif +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/wasabi/Wasabi.cpp b/Src/Plugins/Input/in_wv/wasabi/Wasabi.cpp new file mode 100644 index 00000000..12d0eca3 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Wasabi.cpp @@ -0,0 +1,161 @@ +#include "Winamp/in2.h" +#include "Winamp/wa_ipc.h" +#include "Wasabi.h" + +AlbumArtFactory albumArtFactory; + +class WavPack_AlbumArtProvider : public svc_albumArtProvider +{ +public: + bool IsMine(const wchar_t *filename); + int ProviderType(); + // implementation note: use WASABI_API_MEMMGR to alloc bits and mimetype, so that the recipient can free through that + int GetAlbumArtData(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType); + int SetAlbumArtData(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType); + int DeleteAlbumArt(const wchar_t *filename, const wchar_t *type); +protected: + RECVS_DISPATCH; +}; + +static const wchar_t *GetLastCharactercW(const wchar_t *string) +{ + if (!string || !*string) + return string; + + return CharPrevW(string, string+lstrlenW(string)); +} + +static const wchar_t *scanstr_backW(const wchar_t *str, const wchar_t *toscan, const wchar_t *defval) +{ + const wchar_t *s = GetLastCharactercW(str); + if (!str[0]) return defval; + if (!toscan || !toscan[0]) return defval; + while (1) + { + const wchar_t *t = toscan; + while (*t) + { + if (*t == *s) return s; + t = CharNextW(t); + } + t = CharPrevW(str, s); + if (t == s) + return defval; + s = t; + } +} + +static const wchar_t *extensionW(const wchar_t *fn) +{ + const wchar_t *end = scanstr_backW(fn, L"./\\", 0); + if (!end) + return (fn+lstrlenW(fn)); + + if (*end == L'.') + return end+1; + + return (fn+lstrlenW(fn)); +} + +int WavPack_HandlesExtension(const wchar_t *extension); +bool WavPack_AlbumArtProvider::IsMine(const wchar_t *filename) +{ + const wchar_t *extension = extensionW(filename); + if (extension && *extension) + { + return WavPack_HandlesExtension(extension) != 0; + } + return false; +} + +int WavPack_AlbumArtProvider::ProviderType() +{ + return ALBUMARTPROVIDER_TYPE_EMBEDDED; +} + +int WavPack_GetAlbumArt(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mime_type); +int WavPack_AlbumArtProvider::GetAlbumArtData(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mime_type) +{ + return WavPack_GetAlbumArt(filename, type, bits, len, mime_type); +} + +int WavPack_SetAlbumArt(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mime_type); +int WavPack_AlbumArtProvider::SetAlbumArtData(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mime_type) +{ + return WavPack_SetAlbumArt(filename, type, bits, len, mime_type); +} + +int WavPack_DeleteAlbumArt(const wchar_t *filename, const wchar_t *type); +int WavPack_AlbumArtProvider::DeleteAlbumArt(const wchar_t *filename, const wchar_t *type) +{ + return WavPack_DeleteAlbumArt(filename, type); +} + +#define CBCLASS WavPack_AlbumArtProvider +START_DISPATCH; +CB(SVC_ALBUMARTPROVIDER_PROVIDERTYPE, ProviderType); +CB(SVC_ALBUMARTPROVIDER_GETALBUMARTDATA, GetAlbumArtData); +CB(SVC_ALBUMARTPROVIDER_ISMINE, IsMine); +CB(SVC_ALBUMARTPROVIDER_SETALBUMARTDATA, SetAlbumArtData); +CB(SVC_ALBUMARTPROVIDER_DELETEALBUMART, DeleteAlbumArt); +END_DISPATCH; +#undef CBCLASS + +static WavPack_AlbumArtProvider albumArtProvider; + +// {A558560E-4334-446a-9219-E1F34F518ADE} +static const GUID wavpack_albumartproviderGUID = +{ 0xa558560e, 0x4334, 0x446a, { 0x92, 0x19, 0xe1, 0xf3, 0x4f, 0x51, 0x8a, 0xde } }; + +FOURCC AlbumArtFactory::GetServiceType() +{ + return svc_albumArtProvider::SERVICETYPE; +} + +const char *AlbumArtFactory::GetServiceName() +{ + return "WavPack Album Art Provider"; +} + +GUID AlbumArtFactory::GetGUID() +{ + return wavpack_albumartproviderGUID; +} + +void *AlbumArtFactory::GetInterface(int global_lock) +{ + return &albumArtProvider; +} + +int AlbumArtFactory::SupportNonLockingInterface() +{ + return 1; +} + +int AlbumArtFactory::ReleaseInterface(void *ifc) +{ + return 1; +} + +const char *AlbumArtFactory::GetTestString() +{ + return 0; +} + +int AlbumArtFactory::ServiceNotify(int msg, int param1, int param2) +{ + return 1; +} + +#define CBCLASS AlbumArtFactory +START_DISPATCH; +CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType) +CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName) +CB(WASERVICEFACTORY_GETGUID, GetGUID) +CB(WASERVICEFACTORY_GETINTERFACE, GetInterface) +CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface) +CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface) +CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString) +CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/wasabi/Wasabi.h b/Src/Plugins/Input/in_wv/wasabi/Wasabi.h new file mode 100644 index 00000000..c606fac7 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Wasabi.h @@ -0,0 +1,66 @@ +#ifndef NULLSOFT_API_H +#define NULLSOFT_API_H + +#include "api/service/api_service.h" +extern api_service *serviceManager; +#define WASABI_API_SVC serviceManager + +#include "api/service/waservicefactory.h" + +#include "Agave/Config/api_config.h" +extern api_config *configApi; +#define AGAVE_API_CONFIG configApi + +#include "api/memmgr/api_memmgr.h" +extern api_memmgr *memmgr; +#define WASABI_API_MEMMGR memmgr + +#include "Agave/Language/api_language.h" + +#include "Agave/AlbumArt/svc_albumArtProvider.h" + +template <class api_T> +static void ServiceBuild(api_T *&api_t, GUID factoryGUID_t) +{ + if (WASABI_API_SVC) + { + waServiceFactory *factory = WASABI_API_SVC->service_getServiceByGuid(factoryGUID_t); + if (factory) + api_t = (api_T *)factory->getInterface(); + } +} + +template <class api_T> +static void ServiceRelease(api_T *api_t, GUID factoryGUID_t) +{ + if (WASABI_API_SVC) + { + waServiceFactory *factory = WASABI_API_SVC->service_getServiceByGuid(factoryGUID_t); + if (factory) + factory->releaseInterface(api_t); + } +} + +class AlbumArtFactory : public waServiceFactory +{ +public: + FOURCC GetServiceType(); + const char *GetServiceName(); + GUID GetGUID(); + void *GetInterface(int global_lock); + int SupportNonLockingInterface(); + int ReleaseInterface(void *ifc); + const char *GetTestString(); + int ServiceNotify(int msg, int param1, int param2); + +protected: + RECVS_DISPATCH; +}; + +extern AlbumArtFactory albumArtFactory; + +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = +{ 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } }; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/wasabi/Winamp/DSP.H b/Src/Plugins/Input/in_wv/wasabi/Winamp/DSP.H new file mode 100644 index 00000000..465fb0a1 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Winamp/DSP.H @@ -0,0 +1,65 @@ +#ifndef NULLSOFT_WINAMP_DSP_H +#define NULLSOFT_WINAMP_DSP_H +// DSP plugin interface + +// notes: +// any window that remains in foreground should optimally pass unused +// keystrokes to the parent (winamp's) window, so that the user +// can still control it. As for storing configuration, +// Configuration data should be stored in <dll directory>\plugin.ini +// (look at the vis plugin for configuration code) + +typedef struct winampDSPModule { + char *description; // description + HWND hwndParent; // parent window (filled in by calling app) + HINSTANCE hDllInstance; // instance handle to this DLL (filled in by calling app) + + void (*Config)(struct winampDSPModule *this_mod); // configuration dialog (if needed) + int (*Init)(struct winampDSPModule *this_mod); // 0 on success, creates window, etc (if needed) + + // modify waveform samples: returns number of samples to actually write + // (typically numsamples, but no more than twice numsamples, and no less than half numsamples) + // numsamples should always be at least 128. should, but I'm not sure + int (*ModifySamples)(struct winampDSPModule *this_mod, short int *samples, int numsamples, int bps, int nch, int srate); + + void (*Quit)(struct winampDSPModule *this_mod); // called when unloading + + void *userData; // user data, optional +} winampDSPModule; + +typedef struct { + int version; // DSP_HDRVER + char *description; // description of library + winampDSPModule* (*getModule)(int); // module retrieval function + int (*sf)(int key); // DSP_HDRVER == 0x21 +} winampDSPHeader; + +// exported symbols +#ifdef USE_DSP_HDR_HWND +typedef winampDSPHeader* (*winampDSPGetHeaderType)(HWND); +#define DSP_HDRVER 0x22 + +#else + +typedef winampDSPHeader* (*winampDSPGetHeaderType)(HWND); +// header version: 0x20 == 0.20 == winamp 2.0 +#define DSP_HDRVER 0x20 +#endif + +// return values from the winampUninstallPlugin(HINSTANCE hdll, HWND parent, int param) +// which determine if we can uninstall the plugin immediately or on winamp restart +#define DSP_PLUGIN_UNINSTALL_NOW 0x0 +#define DSP_PLUGIN_UNINSTALL_REBOOT 0x1 +// +// uninstall support was added from 5.0+ and uninstall now support from 5.5+ +// it is down to you to ensure that if uninstall now is returned that it will not cause a crash +// (ie don't use if you've been subclassing the main window) + +// Version note: +// +// Added passing of Winamp's main hwnd in the call to the exported winampDSPHeader() +// which allows for primarily the use of localisation features with the bundled plugins. +// If you want to use the new version then either you can edit you version of dsp.h or +// you can add USE_DSP_HDR_HWND to your project's defined list or before use of dsp.h +// +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/wasabi/Winamp/GEN.H b/Src/Plugins/Input/in_wv/wasabi/Winamp/GEN.H new file mode 100644 index 00000000..b010c100 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Winamp/GEN.H @@ -0,0 +1,37 @@ +#ifndef NULLSOFT_WINAMP_GEN_H +#define NULLSOFT_WINAMP_GEN_H + +#include <windows.h> + +#define GEN_INIT_SUCCESS 0 + +// return values from the winampUninstallPlugin(HINSTANCE hdll, HWND parent, int param) +// which determine if we can uninstall the plugin immediately or on winamp restart +// +// uninstall support was added from 5.0+ and uninstall now support from 5.5+ +// it is down to you to ensure that if uninstall now is returned that it will not cause a crash +// (ie don't use if you've been subclassing the main window) +#define GEN_PLUGIN_UNINSTALL_NOW 0x1 +#define GEN_PLUGIN_UNINSTALL_REBOOT 0x0 + +typedef struct { + int version; + char *description; + int (*init)(); + void (*config)(); + void (*quit)(); + HWND hwndParent; + HINSTANCE hDllInstance; +} winampGeneralPurposePlugin; + +#define GPPHDR_VER 0x10 +#ifdef __cplusplus +extern "C" { +#endif +//extern winampGeneralPurposePlugin *gen_plugins[256]; +typedef winampGeneralPurposePlugin * (*winampGeneralPurposePluginGetter)(); +#ifdef __cplusplus +} +#endif + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/wasabi/Winamp/IN2.H b/Src/Plugins/Input/in_wv/wasabi/Winamp/IN2.H new file mode 100644 index 00000000..67e1a2e4 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Winamp/IN2.H @@ -0,0 +1,138 @@ + +#ifndef NULLSOFT_WINAMP_IN2H +#define NULLSOFT_WINAMP_IN2H +#include "out.h" + +// note: exported symbol is now winampGetInModule2. + +#define IN_UNICODE 0x0F000000 + +#ifdef UNICODE_INPUT_PLUGIN +#define in_char wchar_t +#define IN_VER (IN_UNICODE | 0x100) +#else +#define in_char char +#define IN_VER 0x100 +#endif + +#define IN_MODULE_FLAG_USES_OUTPUT_PLUGIN 1 +// By default, Winamp assumes that your input plugin wants to use Winamp's EQ, and doesn't do replay gain +// if you handle any of these yourself (EQ, Replay Gain adjustments), then set these flags accordingly +#define IN_MODULE_FLAG_EQ 2 // set this if you do your own EQ +#define IN_MODULE_FLAG_REPLAYGAIN 8 // set this if you adjusted volume for replay gain + // for tracks with no replay gain metadata, you should clear this flag + // UNLESS you handle "non_replaygain" gain adjustment yourself +#define IN_MODULE_FLAG_REPLAYGAIN_PREAMP 16 // use this if you queried for the replay gain preamp parameter and used it + // this parameter is new to 5.54 +typedef struct +{ + int version; // module type (IN_VER) + char *description; // description of module, with version string + + HWND hMainWindow; // winamp's main window (filled in by winamp) + HINSTANCE hDllInstance; // DLL instance handle (Also filled in by winamp) + + char *FileExtensions; // "mp3\0Layer 3 MPEG\0mp2\0Layer 2 MPEG\0mpg\0Layer 1 MPEG\0" + // May be altered from Config, so the user can select what they want + + int is_seekable; // is this stream seekable? + int UsesOutputPlug; // does this plug-in use the output plug-ins? (musn't ever change, ever :) + // note that this has turned into a "flags" field + // see IN_MODULE_FLAG_* + + void (*Config)(HWND hwndParent); // configuration dialog + void (*About)(HWND hwndParent); // about dialog + + void (*Init)(); // called at program init + void (*Quit)(); // called at program quit + +#define GETFILEINFO_TITLE_LENGTH 2048 + void (*GetFileInfo)(const in_char *file, in_char *title, int *length_in_ms); // if file == NULL, current playing is used + +#define INFOBOX_EDITED 0 +#define INFOBOX_UNCHANGED 1 + int (*InfoBox)(const in_char *file, HWND hwndParent); + + int (*IsOurFile)(const in_char *fn); // called before extension checks, to allow detection of mms://, etc + // playback stuff + int (*Play)(const in_char *fn); // return zero on success, -1 on file-not-found, some other value on other (stopping winamp) error + void (*Pause)(); // pause stream + void (*UnPause)(); // unpause stream + int (*IsPaused)(); // ispaused? return 1 if paused, 0 if not + void (*Stop)(); // stop (unload) stream + + // time stuff + int (*GetLength)(); // get length in ms + int (*GetOutputTime)(); // returns current output time in ms. (usually returns outMod->GetOutputTime() + void (*SetOutputTime)(int time_in_ms); // seeks to point in stream (in ms). Usually you signal your thread to seek, which seeks and calls outMod->Flush().. + + // volume stuff + void (*SetVolume)(int volume); // from 0 to 255.. usually just call outMod->SetVolume + void (*SetPan)(int pan); // from -127 to 127.. usually just call outMod->SetPan + + // in-window builtin vis stuff + + void (*SAVSAInit)(int maxlatency_in_ms, int srate); // call once in Play(). maxlatency_in_ms should be the value returned from outMod->Open() + // call after opening audio device with max latency in ms and samplerate + void (*SAVSADeInit)(); // call in Stop() + + + // simple vis supplying mode + void (*SAAddPCMData)(void *PCMData, int nch, int bps, int timestamp); + // sets the spec data directly from PCM data + // quick and easy way to get vis working :) + // needs at least 576 samples :) + + // advanced vis supplying mode, only use if you're cool. Use SAAddPCMData for most stuff. + int (*SAGetMode)(); // gets csa (the current type (4=ws,2=osc,1=spec)) + // use when calling SAAdd() + int (*SAAdd)(void *data, int timestamp, int csa); // sets the spec data, filled in by winamp + + + // vis stuff (plug-in) + // simple vis supplying mode + void (*VSAAddPCMData)(void *PCMData, int nch, int bps, int timestamp); // sets the vis data directly from PCM data + // quick and easy way to get vis working :) + // needs at least 576 samples :) + + // advanced vis supplying mode, only use if you're cool. Use VSAAddPCMData for most stuff. + int (*VSAGetMode)(int *specNch, int *waveNch); // use to figure out what to give to VSAAdd + int (*VSAAdd)(void *data, int timestamp); // filled in by winamp, called by plug-in + + + // call this in Play() to tell the vis plug-ins the current output params. + void (*VSASetInfo)(int srate, int nch); // <-- Correct (benski, dec 2005).. old declaration had the params backwards + + + // dsp plug-in processing: + // (filled in by winamp, calld by input plug) + + // returns 1 if active (which means that the number of samples returned by dsp_dosamples + // could be greater than went in.. Use it to estimate if you'll have enough room in the + // output buffer + int (*dsp_isactive)(); + + // returns number of samples to output. This can be as much as twice numsamples. + // be sure to allocate enough buffer for samples, then. + int (*dsp_dosamples)(short int *samples, int numsamples, int bps, int nch, int srate); + + + // eq stuff + void (*EQSet)(int on, char data[10], int preamp); // 0-64 each, 31 is +0, 0 is +12, 63 is -12. Do nothing to ignore. + + // info setting (filled in by winamp) + void (*SetInfo)(int bitrate, int srate, int stereo, int synched); // if -1, changes ignored? :) + + Out_Module *outMod; // filled in by winamp, optionally used :) +} In_Module; + +// return values from the winampUninstallPlugin(HINSTANCE hdll, HWND parent, int param) +// which determine if we can uninstall the plugin immediately or on winamp restart +// +// uninstall support was added from 5.0+ and uninstall now support from 5.5+ +// it is down to you to ensure that if uninstall now is returned that it will not cause a crash +// (ie don't use if you've been subclassing the main window) +#define IN_PLUGIN_UNINSTALL_NOW 0x1 +#define IN_PLUGIN_UNINSTALL_REBOOT 0x0 + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/wasabi/Winamp/api_audiostream.h b/Src/Plugins/Input/in_wv/wasabi/Winamp/api_audiostream.h new file mode 100644 index 00000000..7a8c8289 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Winamp/api_audiostream.h @@ -0,0 +1,65 @@ +#ifndef NULLSOFT_API_AUDIOSTREAM_H +#define NULLSOFT_API_AUDIOSTREAM_H + +#include <bfc/dispatch.h> + +class api_audiostream : public Dispatchable +{ +protected: + api_audiostream() {} + ~api_audiostream() {} +public: + /* returns number of bytes written to buffer. + * a return value of 0 means EOF + */ + size_t ReadAudio(void *buffer, size_t sizeBytes); // TODO: killswitch and error code + + size_t ReadAudio(void *buffer, size_t, int *killswitch, int *errorCode); + /* Seeks to a point in the stream in milliseconds + * returns TRUE if successful, FALSE otherwise + */ + int SeekToTimeMs(int millisecs); + + /* returns 1 if this stream is seekable using SeekToTime, 0 otherwise + */ + int CanSeek(); +public: + DISPATCH_CODES + { + API_AUDIOSTREAM_READAUDIO = 10, + API_AUDIOSTREAM_READAUDIO2 = 11, + API_AUDIOSTREAM_SEEKTOTIMEMS = 20, + API_AUDIOSTREAM_CANSEEK = 30, + }; +}; + +inline size_t api_audiostream::ReadAudio(void *buffer, size_t sizeBytes) +{ + return _call(API_AUDIOSTREAM_READAUDIO, (size_t)0, buffer, sizeBytes); +} + +inline size_t api_audiostream::ReadAudio(void *buffer, size_t sizeBytes, int *killswitch, int *errorCode) +{ + void *params[4] = { &buffer, &sizeBytes, &killswitch, &errorCode}; + size_t retval; + + if (_dispatch(API_AUDIOSTREAM_READAUDIO2, &retval, params, 4)) + return retval; + else + { + *errorCode=0; + return ReadAudio(buffer, sizeBytes); + } +} + +inline int api_audiostream::SeekToTimeMs(int millisecs) +{ + return _call(API_AUDIOSTREAM_SEEKTOTIMEMS, (int)0, millisecs); +} + +inline int api_audiostream::CanSeek() +{ + return _call(API_AUDIOSTREAM_CANSEEK, (int)0); +} + +#endif diff --git a/Src/Plugins/Input/in_wv/wasabi/Winamp/api_decodefile.h b/Src/Plugins/Input/in_wv/wasabi/Winamp/api_decodefile.h new file mode 100644 index 00000000..9c4d366e --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Winamp/api_decodefile.h @@ -0,0 +1,99 @@ +#ifndef NULLSOFT_API_DECODEFILE_H +#define NULLSOFT_API_DECODEFILE_H + +#include <bfc/dispatch.h> +#include <bfc/platform/types.h> +#include "api_audiostream.h" + +enum +{ + API_DECODEFILE_SUCCESS = 0, + API_DECODEFILE_FAILURE = 1, + + API_DECODEFILE_UNSUPPORTED = 2, // type is unsupported + API_DECODEFILE_NO_INTERFACE = 3, // type is supported, but plugin does provide any interfaces for direct decoding + API_DECODEFILE_WINAMP_PRO = 4, // user has to pay $$$ to do this + API_DECODEFILE_NO_RIGHTS = 5, // user is not allowed to decode this file (e.g. DRM) + API_DECODEFILE_BAD_RESAMPLE = 6, // Winamp is unable to resample this file to CDDA format (stereo 16bit 44.1kHz) +}; + +enum +{ + AUDIOPARAMETERS_FLOAT = 1, +}; +struct AudioParameters +{ +public: + AudioParameters() : bitsPerSample(0), channels(0), sampleRate(0), sampleRateReal(0.f), sizeBytes((size_t) - 1), errorCode(API_DECODEFILE_SUCCESS), flags(0) + {} + size_t bitsPerSample; + size_t channels; + size_t sampleRate; + float sampleRateReal; // yes this is duplicate. + int flags; + size_t sizeBytes; // total size of decoded file, (size_t)-1 means don't know + int errorCode; +}; + +class api_decodefile : public Dispatchable +{ +public: + /* OpenAudioBackground gives you back an api_audiostream that you can use to get decompressed bits + * if it returns 0, check parameters->errorCode for the failure reason + * fill parameters with desired values (0 if you don't care) + * the decoder will _do its best_ to satisfy your passed-in audio parameters + * but this API does not guarantee them, so be sure to check the parameters struct after the function returns + * it's **UP TO YOU** to do any necessary conversion (sample rate, channels, bits-per-sample) if the decoder can't do it + */ + api_audiostream *OpenAudioBackground(const wchar_t *filename, AudioParameters *parameters); + /* OpenAudio is the same as OpenAudioBackground + * but, it will use the input plugin system to decode if necessary + * so it's best to use this in a separate winamp.exe + * to be honest, it was designed for internal use in the CD burner + * so it's best not to use this one at all + */ + api_audiostream *OpenAudio(const wchar_t *filename, AudioParameters *parameters); + + void CloseAudio(api_audiostream *audioStream); + /* verifies that a decoder exists to decompress this filename. + this is not a guarantee tgat the file is openable, just that it can be matched to a decoder */ + bool DecoderExists(const wchar_t *filename); +public: + DISPATCH_CODES + { + API_DECODEFILE_OPENAUDIO = 10, + API_DECODEFILE_OPENAUDIO2 = 11, + API_DECODEFILE_CLOSEAUDIO = 20, + API_DECODEFILE_DECODEREXISTS = 30, + }; +}; + +inline api_audiostream *api_decodefile::OpenAudio(const wchar_t *filename, AudioParameters *parameters) +{ + return _call(API_DECODEFILE_OPENAUDIO, (api_audiostream *)0, filename, parameters); +} + +inline api_audiostream *api_decodefile::OpenAudioBackground(const wchar_t *filename, AudioParameters *parameters) +{ + return _call(API_DECODEFILE_OPENAUDIO2, (api_audiostream *)0, filename, parameters); +} + + +inline void api_decodefile::CloseAudio(api_audiostream *audioStream) +{ + _voidcall(API_DECODEFILE_CLOSEAUDIO, audioStream); +} + +inline bool api_decodefile::DecoderExists(const wchar_t *filename) +{ + return _call(API_DECODEFILE_DECODEREXISTS, (bool)true, filename); // we default to true so that an old implementation doesn't break completely +} + +// {9B4188F5-4295-48ab-B50C-F2B0BB56D242} +static const GUID decodeFileGUID = + { + 0x9b4188f5, 0x4295, 0x48ab, { 0xb5, 0xc, 0xf2, 0xb0, 0xbb, 0x56, 0xd2, 0x42 } + }; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/wasabi/Winamp/api_random.h b/Src/Plugins/Input/in_wv/wasabi/Winamp/api_random.h new file mode 100644 index 00000000..d0618f16 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Winamp/api_random.h @@ -0,0 +1,78 @@ +#ifndef NULLSOFT_API_RANDOM_H +#define NULLSOFT_API_RANDOM_H + +#include <bfc/dispatch.h> +#include <bfc/platform/types.h> + +typedef int (*RandomGenerator)(void); +typedef unsigned long (*UnsignedRandomGenerator)(void); + +class api_random : public Dispatchable +{ +protected: + api_random() {} + ~api_random() {} +public: + RandomGenerator GetFunction(); + UnsignedRandomGenerator GetUnsignedFunction(); + int GetNumber(); + int GetPositiveNumber(); + float GetFloat(); // [0-1] + float GetFloat_LessThanOne(); // [0-1) + float GetFloat_LessThanOne_NotZero(); // (0-1) + double GetDouble(); // [0-1) +public: + DISPATCH_CODES + { + API_RANDOM_GETFUNCTION = 10, + API_RANDOM_GETFUNCTION_UNSIGNED = 11, + API_RANDOM_GETNUMBER = 20, + API_RANDOM_GETPOSITIVENUMBER = 30, + API_RANDOM_GETFLOAT = 40, + API_RANDOM_GETFLOAT2 = 41, + API_RANDOM_GETFLOAT3 = 42, + API_RANDOM_GETDOUBLE = 50, + }; +}; + +inline RandomGenerator api_random::GetFunction() +{ + return _call(API_RANDOM_GETFUNCTION, (RandomGenerator )0); +} +inline UnsignedRandomGenerator api_random::GetUnsignedFunction() +{ + return _call(API_RANDOM_GETFUNCTION_UNSIGNED, (UnsignedRandomGenerator )0); +} + +inline int api_random::GetNumber() +{ + return _call(API_RANDOM_GETNUMBER, 0); +} +inline int api_random::GetPositiveNumber() +{ + return _call(API_RANDOM_GETPOSITIVENUMBER, 0); +} +inline float api_random::GetFloat() +{ + return _call(API_RANDOM_GETFLOAT, 0.f); +} +inline float api_random::GetFloat_LessThanOne() +{ + return _call(API_RANDOM_GETFLOAT2, 0.f); +} +inline float api_random::GetFloat_LessThanOne_NotZero() +{ + return _call(API_RANDOM_GETFLOAT3, 0.f); +} +inline double api_random::GetDouble() +{ + return _call(API_RANDOM_GETDOUBLE, 0.); +} + +// {CB401CAB-CC10-48f7-ADB7-9D1D24B40E0C} +static const GUID randomApiGUID = +{ 0xcb401cab, 0xcc10, 0x48f7, { 0xad, 0xb7, 0x9d, 0x1d, 0x24, 0xb4, 0xe, 0xc } }; + + +#endif + diff --git a/Src/Plugins/Input/in_wv/wasabi/Winamp/api_wa5component.h b/Src/Plugins/Input/in_wv/wasabi/Winamp/api_wa5component.h new file mode 100644 index 00000000..da43396b --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Winamp/api_wa5component.h @@ -0,0 +1,34 @@ +#ifndef __WASABI_API_WA5COMPONENT_H_ +#define __WASABI_API_WA5COMPONENT_H_ + +#include <bfc/dispatch.h> +class api_service; +#ifdef WIN32 +#include <windows.h> +#endif + +class NOVTABLE api_wa5component : public Dispatchable +{ +public: + DISPATCH_CODES + { + API_WA5COMPONENT_REGISTERSERVICES = 10, + API_WA5COMPONENT_DEREEGISTERSERVICES = 20, + }; + + void RegisterServices(api_service *service); + void DeregisterServices(api_service *service); +#ifdef WIN32 // this is a kind of a hack (might be better to create a function that winamp calls to pass it) + HMODULE hModule; +#endif +}; +inline void api_wa5component::RegisterServices(api_service *service) +{ + _voidcall(API_WA5COMPONENT_REGISTERSERVICES, service); +} + +inline void api_wa5component::DeregisterServices(api_service *service) +{ + _voidcall(API_WA5COMPONENT_DEREEGISTERSERVICES, service); +} +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/wasabi/Winamp/ipc_pe.h b/Src/Plugins/Input/in_wv/wasabi/Winamp/ipc_pe.h new file mode 100644 index 00000000..2d013a9f --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Winamp/ipc_pe.h @@ -0,0 +1,56 @@ +#ifndef __IPC_PE_H +#define __IPC_PE_H + +#define IPC_PE_GETCURINDEX 100 // returns current idx +#define IPC_PE_GETINDEXTOTAL 101 // returns number of items +#define IPC_PE_GETINDEXINFO 102 // (copydata) lpData is of type callbackinfo, callback is called with copydata/fileinfo structure and msg IPC_PE_GETINDEXINFORESULT +#define IPC_PE_GETINDEXINFORESULT 103 // callback message for IPC_PE_GETINDEXINFO +#define IPC_PE_DELETEINDEX 104 // lParam = index +#define IPC_PE_SWAPINDEX 105 // (lParam & 0xFFFF0000) >> 16 = from, (lParam & 0xFFFF) = to +#define IPC_PE_INSERTFILENAME 106 // (copydata) lpData is of type fileinfo +#define IPC_PE_GETDIRTY 107 // returns 1 if the playlist changed since the last IPC_PE_SETCLEAN +#define IPC_PE_SETCLEAN 108 // resets the dirty flag until next modification +#define IPC_PE_GETIDXFROMPOINT 109 // pass a point parm, return a playlist index +#define IPC_PE_SAVEEND 110 // pass index to save from +#define IPC_PE_RESTOREEND 111 // no parm +#define IPC_PE_GETNEXTSELECTED 112 // same as IPC_PLAYLIST_GET_NEXT_SELECTED for the main window +#define IPC_PE_GETSELECTEDCOUNT 113 +#define IPC_PE_INSERTFILENAMEW 114 // (copydata) lpData is of type fileinfoW +#define IPC_PE_GETINDEXINFO_TITLE 115 // like IPC_PE_GETINDEXINFO, but writes the title to char file[MAX_PATH] instead of filename +#define IPC_PE_GETINDEXINFORESULT_TITLE 116 // callback message for IPC_PE_GETINDEXINFO +typedef struct { + char file[MAX_PATH]; + int index; + } fileinfo; + +typedef struct { + wchar_t file[MAX_PATH]; + int index; + } fileinfoW; + +typedef struct { + HWND callback; + int index; + } callbackinfo; + +// the following messages are in_process ONLY + +#define IPC_PE_GETINDEXTITLE 200 // lParam = pointer to fileinfo2 struct +#define IPC_PE_GETINDEXTITLEW 201 // lParam = pointer to fileinfo2W struct +#define IPC_PE_GETINDEXINFO_INPROC 202 // lParam = pointer to fileinfo struct +#define IPC_PE_GETINDEXINFOW_INPROC 203 // lParam = pointer to fileinfoW struct + +typedef struct { + int fileindex; + char filetitle[256]; + char filelength[16]; + } fileinfo2; + +typedef struct +{ + int fileindex; + wchar_t filetitle[256]; + wchar_t filelength[16]; + } fileinfo2W; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/wasabi/Winamp/wa_dlg.h b/Src/Plugins/Input/in_wv/wasabi/Winamp/wa_dlg.h new file mode 100644 index 00000000..e6edd49d --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Winamp/wa_dlg.h @@ -0,0 +1,436 @@ +/* +** Copyright (C) 2003-2008 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +*/ + +#ifndef _WA_DLG_H_ +#define _WA_DLG_H_ + +#include "wa_ipc.h" +#ifdef __cplusplus +extern "C" { +#endif +/* + 1) gen.bmp has a generic window frame for plugins to use. + its format is similar to the minibrowser's. + In addition gen.bmp includes a font for the titlebar, in both + highlight and no-highlight modes. The font is variable width, + and it uses the first color before the letter A as the delimiter. + The no-highlight form of letter must be the same width as the + highlight form. + 2) genex.bmp has button and scrollbar images, as well as some individual + pixels that describe the colors for the dialog. The button and + scrollbar images should be self explanatory (note that the buttons + have 4 pixel sized edges that are not stretched, and the center is + stretched), and the scrollbars do something similar. + The colors start at (48,0) and run every other pixel. The meaning + of each pixel is: + x=48: item background (background to edits, listviews etc) + x=50: item foreground (text color of edit/listview, etc) + x=52: window background (used to set the bg color for the dialog) + x=54: button text color + x=56: window text color + x=58: color of dividers and sunken borders + x=60: selection color for playlists + x=62: listview header background color + x=64: listview header text color + x=66: listview header frame top color + x=68: listview header frame middle color + x=70: listview header frame bottom color + x=72: listview header empty color + x=74: scrollbar foreground color + x=76: scrollbar background color + x=78: inverse scrollbar foreground color + x=80: inverse scrollbar background color + x=82: scrollbar dead area color + x=84: listview/treeview selection bar text color (active) + x=86: listview/treeview selection bar back color (active) + x=88: listview/treeview selection bar text color (inactive) + x=90: listview/treeview selection bar back color (inactive) + x=92: alternate item background + x=94: alternate item foreground +*/ + +#define DCW_SUNKENBORDER 0x00010000 +#define DCW_DIVIDER 0x00020000 + +enum +{ + WADLG_ITEMBG, + WADLG_ITEMFG, + WADLG_WNDBG, + WADLG_BUTTONFG, + WADLG_WNDFG, + WADLG_HILITE, + WADLG_SELCOLOR, + WADLG_LISTHEADER_BGCOLOR, + WADLG_LISTHEADER_FONTCOLOR, + WADLG_LISTHEADER_FRAME_TOPCOLOR, + WADLG_LISTHEADER_FRAME_MIDDLECOLOR, + WADLG_LISTHEADER_FRAME_BOTTOMCOLOR, + WADLG_LISTHEADER_EMPTY_BGCOLOR, + WADLG_SCROLLBAR_FGCOLOR, + WADLG_SCROLLBAR_BGCOLOR, + WADLG_SCROLLBAR_INV_FGCOLOR, + WADLG_SCROLLBAR_INV_BGCOLOR, + WADLG_SCROLLBAR_DEADAREA_COLOR, + WADLG_SELBAR_FGCOLOR, + WADLG_SELBAR_BGCOLOR, + WADLG_INACT_SELBAR_FGCOLOR, + WADLG_INACT_SELBAR_BGCOLOR, + WADLG_ITEMBG2, + WADLG_ITEMFG2, + WADLG_NUM_COLORS +}; + +typedef enum _WACURSOR // used in IPC_GETSKINCURSORS +{ + WACURSOR_VOLUME = 0, // volume & balane + WACURSOR_POSITION = 1, // position + WACURSOR_BTN_WINSHADE = 2, // winshade + WACURSOR_BTN_MINIMIZE = 3, // minimize + WACURSOR_BTN_CLOSE = 4, // close + WACURSOR_MENU = 5, // main menu + WACURSOR_TITLEBAR = 6, // title bar + WACURSOR_SONGNAME = 7, + WACURSOR_NORMAL = 8, + WACURSOR_WINSHADE_BTN_WINSHADE = 9, + WACURSOR_WINSHADE_BTN_MINIMIZE = 10, + WACURSOR_WINSHADE_POSITION = 11, + WACURSOR_WINSHADE_BTN_CLOSE = 12, + WACURSOR_WINSHADE_MENU = 13, + WACURSOR_WINSHADE_NORMAL = 14, + WACURSOR_PL_BTN_WINSHADE = 15, + WACURSOR_PL_BTN_CLOSE = 16, + WACURSOR_PL_TITLEBAR = 17, + WACURSOR_PL_VSCROLL = 18, + WACURSOR_PL_RESIZE = 19, + WACURSOR_PL_NORMAL = 20, + WACURSOR_PL_WINSHADE_BTN_WINSHADE = 21, + WACURSOR_PL_WINSHADE_BTN_CLOSE = 22, + WACURSOR_PL_WINSHADE_HSIZE = 23, + WACURSOR_PL_WINSHADE_NORMAL = 24, + WACURSOR_EQ_SLIDER = 25, + WACURSOR_EQ_BTN_CLOSE = 26, + WACURSOR_EQ_TITLEBAR = 27, + WACURSOR_EQ_NORMAL = 28, +} WACURSOR; + +void WADlg_init(HWND hwndWinamp); // call this on init, or on WM_DISPLAYCHANGE +void WADlg_close(); +int WADlg_getColor(int idx); +int WADlg_initted(); + +LRESULT WADlg_handleDialogMsgs(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); // +void WADlg_DrawChildWindowBorders(HWND hwndDlg, int *tab, int tabsize); // each entry in tab would be the id | DCW_* + +HBITMAP WADlg_getBitmap(); + +/// define WA_DLG_IMPLEMENT in one of your source files before including this .h +// if you are making a media library plugin, you dont need to do this, look at view_ex for +// an example of how to get the function *'s via an IPC message. +#ifdef __cplusplus +} +#endif + +#ifdef WA_DLG_IMPLEMENT + +static HBRUSH wadlg_lastbrush=0; +static HBITMAP wadlg_bitmap=0; // load this manually +static int wadlg_colors[WADLG_NUM_COLORS]; +static int wadlg_defcolors[WADLG_NUM_COLORS]= +{ + RGB(0,0,0), + RGB(0,255,0), + RGB(36,36,60), + RGB(57,56,66), + RGB(255,255,255), + RGB(132,148,165), + RGB(0,0,198), + RGB(36*2,36*2,60*2), + RGB(255,255,255), + RGB(36*3,36*3,60*3), + RGB(36,36,60), + RGB(36*0.5,36*0.5,60*0.5), + RGB(36,36,60), + RGB(36*1,36*1,60*1), + RGB(36*1,36*1,60*1), + RGB(121,130,150), + RGB(78,88,110), + RGB(36*1,36*1,60*1), + RGB(255,255,255), + RGB(0,0,180), + RGB(0,255,0), + RGB(0,0,128), + RGB(0,0,0), + RGB(0,255,0), +}; + +int WADlg_initted() +{ + return !!wadlg_bitmap; +} + +int WADlg_getColor(int idx) +{ + if (idx < 0 || idx >= WADLG_NUM_COLORS) return 0; + return wadlg_colors[idx]; +} + +HBITMAP WADlg_getBitmap() +{ + return wadlg_bitmap; +} + +void WADlg_init(HWND hwndWinamp) // call this on init, or on WM_DISPLAYCHANGE +{ + if (wadlg_bitmap) DeleteObject(wadlg_bitmap); + wadlg_bitmap = (HBITMAP) SendMessage(hwndWinamp,WM_WA_IPC,0,IPC_GET_GENSKINBITMAP); + if (wadlg_bitmap) + { + HDC tmpDC=CreateCompatibleDC(NULL); + HGDIOBJ o=SelectObject(tmpDC,(HGDIOBJ)wadlg_bitmap); + int defbgcol=GetPixel(tmpDC,111,0); + for (int x = 0; x < WADLG_NUM_COLORS; x ++) + { + int a=GetPixel(tmpDC,48+x*2,0); + if (a == CLR_INVALID || a == RGB(0,198,255) || a == defbgcol) + { + //defaults for old skins + if (x == WADLG_SELBAR_FGCOLOR || x == WADLG_INACT_SELBAR_FGCOLOR) a=wadlg_colors[WADLG_WNDFG]; + else if (x == WADLG_SELBAR_BGCOLOR || x == WADLG_INACT_SELBAR_BGCOLOR) + { + a=wadlg_colors[WADLG_SELCOLOR]; + if (x == WADLG_INACT_SELBAR_BGCOLOR) + a=((a/2)&0x7F7F7F)+(((wadlg_colors[WADLG_WNDBG])/2)&0x7F7F7F); + } + else if (x == WADLG_ITEMBG2) + { + a=wadlg_colors[WADLG_ITEMBG]; + } + else if (x == WADLG_ITEMFG2) + { + a=wadlg_colors[WADLG_ITEMFG]; + } + else a=wadlg_defcolors[x]; + } + wadlg_colors[x]=a; + } + + SelectObject(tmpDC,o); + DeleteDC(tmpDC); + } +} + +void WADlg_close() +{ + if (wadlg_bitmap) DeleteObject(wadlg_bitmap); + wadlg_bitmap=0; + if (wadlg_lastbrush) DeleteObject(wadlg_lastbrush); + wadlg_lastbrush=0; +} + +void WADlg_dotLine(HDC hdc, int left, int top, int len, int vert) +{ + for(int i=(top&1);i<len-1;i+=2) + { + if(vert) + { + MoveToEx(hdc,left,top+i,NULL); + LineTo(hdc,left,top+i+1); + } + else + { + MoveToEx(hdc,left+i,top,NULL); + LineTo(hdc,left+i+1,top); + } + } +} + +LRESULT WADlg_handleDialogMsgs(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (uMsg == WM_DRAWITEM) + { + DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam; + if (di->CtlType == ODT_BUTTON) { + wchar_t wt[256]; + RECT r; + GetDlgItemTextW(hwndDlg,(INT)wParam,wt,sizeof(wt)/sizeof(*wt)); + + HDC hdc = CreateCompatibleDC(di->hDC); + HBITMAP hbmpOld = (HBITMAP)SelectObject(hdc, wadlg_bitmap); + + r=di->rcItem; + SetStretchBltMode(di->hDC,COLORONCOLOR); + + int yoffs = (di->itemState & ODS_SELECTED) ? 15 : 0; + + BitBlt(di->hDC,r.left,r.top,4,4,hdc,0,yoffs,SRCCOPY); // top left + StretchBlt(di->hDC,r.left+4,r.top,r.right-r.left-4-4,4,hdc,4,yoffs,47-4-4,4,SRCCOPY); // top center + BitBlt(di->hDC,r.right-4,r.top,4,4,hdc,47-4,yoffs,SRCCOPY); // top right + + StretchBlt(di->hDC,r.left,r.top+4,4,r.bottom-r.top-4-4,hdc,0,4+yoffs,4,15-4-4,SRCCOPY); // left edge + StretchBlt(di->hDC,r.right-4,r.top+4,4,r.bottom-r.top-4-4,hdc,47-4,4+yoffs,4,15-4-4,SRCCOPY); // right edge + + // center + StretchBlt(di->hDC,r.left+4,r.top+4,r.right-r.left-4-4,r.bottom-r.top-4-4,hdc,4,4+yoffs,47-4-4,15-4-4,SRCCOPY); + + BitBlt(di->hDC,r.left,r.bottom-4,4,4,hdc,0,15-4+yoffs,SRCCOPY); // bottom left + StretchBlt(di->hDC,r.left+4,r.bottom-4,r.right-r.left-4-4,4,hdc,4,15-4+yoffs,47-4-4,4,SRCCOPY); // bottom center + BitBlt(di->hDC,r.right-4,r.bottom-4,4,4,hdc,47-4,15-4+yoffs,SRCCOPY); // bottom right + + // draw text + SetBkMode(di->hDC,TRANSPARENT); + + // this will do a different style for the button text depending on enabled state of the button + COLORREF colour = wadlg_colors[WADLG_BUTTONFG]; + if(!IsWindowEnabled(di->hwndItem)){ + COLORREF fg = wadlg_colors[WADLG_WNDFG], + bg = wadlg_colors[WADLG_WNDBG]; + colour = RGB((GetRValue(fg)+GetRValue(bg))/2, + (GetGValue(fg)+GetGValue(bg))/2, + (GetBValue(fg)+GetBValue(bg))/2); + } + SetTextColor(di->hDC,colour); + + if (di->itemState & ODS_SELECTED) {r.left+=2; r.top+=2;} + DrawTextW(di->hDC,wt,-1,&r,DT_VCENTER|DT_SINGLELINE|DT_CENTER); + + SelectObject(hdc, hbmpOld); + DeleteDC(hdc); + + if(GetFocus()==di->hwndItem) { + HPEN hpen, hpenOld; + hpen =CreatePen(PS_SOLID,0,RGB(0,0,0)); + hpenOld = (HPEN)SelectObject(di->hDC, hpen); + WADlg_dotLine(di->hDC,r.left+2,r.top+2,r.right-r.left-3,0); + WADlg_dotLine(di->hDC,r.right-3,r.top+2,r.bottom-r.top-3,1); + WADlg_dotLine(di->hDC,r.left+2,r.top+2,r.bottom-r.top-3,1); + WADlg_dotLine(di->hDC,r.left+2,r.bottom-3,r.right-r.left-3,0); + SelectObject(di->hDC, hpenOld); + DeleteObject(hpen); + } + } + } + + switch(uMsg) + { + case WM_CTLCOLORLISTBOX: + case WM_CTLCOLORDLG: + case WM_CTLCOLORBTN: + case WM_CTLCOLORSTATIC: + case WM_CTLCOLOREDIT: + { + int bgcolor=(uMsg == WM_CTLCOLOREDIT || uMsg == WM_CTLCOLORLISTBOX) ? wadlg_colors[WADLG_ITEMBG] : (uMsg == WM_CTLCOLORBTN ? wadlg_colors[WADLG_ITEMBG] : wadlg_colors[WADLG_WNDBG]); + LOGBRUSH lb={BS_SOLID,GetNearestColor((HDC)wParam,bgcolor)}; + if (wadlg_lastbrush) DeleteObject(wadlg_lastbrush); + wadlg_lastbrush=CreateBrushIndirect(&lb); + SetTextColor((HDC)wParam,uMsg == WM_CTLCOLORSTATIC ? wadlg_colors[WADLG_WNDFG] : wadlg_colors[WADLG_ITEMFG]); + SetBkColor((HDC)wParam,lb.lbColor); + return (LRESULT)wadlg_lastbrush; + } + } + return 0; +} + +static int RectInRect(RECT *rect1, RECT *rect2) +{ + // this has a bias towards true + + // this could probably be optimized a lot + return ((rect1->top >= rect2->top && rect1->top <= rect2->bottom) || + (rect1->bottom >= rect2->top && rect1->bottom <= rect2->bottom) || + (rect2->top >= rect1->top && rect2->top <= rect1->bottom) || + (rect2->bottom >= rect1->top && rect2->bottom <= rect1->bottom)) // vertical intersect + && + ((rect1->left >= rect2->left && rect1->left <= rect2->right) || + (rect1->right >= rect2->left && rect1->right <= rect2->right) || + (rect2->left >= rect1->left && rect2->left <= rect1->right) || + (rect2->right >= rect1->left && rect2->right <= rect1->right)) // horiz intersect + ; +} + +static void WADlg_removeFromRgn(HRGN hrgn, int left, int top, int right, int bottom) +{ + HRGN rgn2=CreateRectRgn(left,top,right,bottom); + CombineRgn(hrgn,hrgn,rgn2,RGN_DIFF); + DeleteObject(rgn2); +} + +void WADlg_DrawChildWindowBorders(HWND hwndDlg, int *tab, int tabsize) +{ + PAINTSTRUCT ps; + BeginPaint(hwndDlg,&ps); + HRGN hrgn = (ps.fErase) ? CreateRectRgnIndirect(&ps.rcPaint) : NULL; + HPEN pen = CreatePen(PS_SOLID, 0, wadlg_colors[WADLG_HILITE]); + HGDIOBJ o = SelectObject(ps.hdc, pen); + + while (tabsize--) + { + RECT r; + int a = *tab++; + GetWindowRect(GetDlgItem(hwndDlg, a & 0xffff),&r); + MapWindowPoints(HWND_DESKTOP, hwndDlg, (LPPOINT)&r, 2); + + if (RectInRect(&ps.rcPaint,&r)) + { + if ((a & 0xffff0000) == DCW_SUNKENBORDER) + { + MoveToEx(ps.hdc,r.left,r.bottom,NULL); + LineTo(ps.hdc,r.right,r.bottom); + LineTo(ps.hdc,r.right,r.top-1); + if(hrgn) + { + WADlg_removeFromRgn(hrgn,r.left,r.bottom,r.right,r.bottom+1); + WADlg_removeFromRgn(hrgn,r.right,r.top,r.right+1,r.bottom); + } + } + else if ((a & 0xffff0000) == DCW_DIVIDER) + { + if (r.right - r.left < r.bottom - r.top) // vertical + { + int left=(r.left+r.right)/2; + MoveToEx(ps.hdc,left,r.top,NULL); + LineTo(ps.hdc,left,r.bottom+1); + if(hrgn) WADlg_removeFromRgn(hrgn,left,r.top,left+1,r.bottom); + } + else // horiz + { + int top=(r.top+r.bottom)/2; + MoveToEx(ps.hdc,r.left,top,NULL); + LineTo(ps.hdc,r.right+1,top); + if(hrgn) WADlg_removeFromRgn(hrgn,r.left,top,r.right,top+1); + } + } + } + } + + SelectObject(ps.hdc, o); + DeleteObject(pen); + + if(hrgn) + { + //erase bkgnd while clipping out our own drawn stuff (for flickerless display) + HBRUSH b = CreateSolidBrush(wadlg_colors[WADLG_WNDBG]); + FillRgn(ps.hdc,hrgn,b); + DeleteObject(b); + DeleteObject(hrgn); + } + EndPaint(hwndDlg,&ps); +} +#endif + +#endif//_WA_DLG_H_
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/wasabi/Winamp/wa_hotkeys.h b/Src/Plugins/Input/in_wv/wasabi/Winamp/wa_hotkeys.h new file mode 100644 index 00000000..6739a576 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Winamp/wa_hotkeys.h @@ -0,0 +1,39 @@ +#ifndef WA_HOTKEYS +#define WA_HOTKEYS + +//#define IPC_GEN_HOTKEYS_ADD xxxx //pass a genHotkeysAddStruct * struct in data +// +//To get the IPC_GEN_HOTKEYS_ADD IPC number, do this: +// +// genhotkeys_add_ipc=SendMessage(winampWindow,WM_WA_IPC,(WPARAM)&"GenHotkeysAdd",IPC_REGISTER_WINAMP_IPCMESSAGE); +// +//Then you can use: +// +// PostMessage(winampWindow,WM_WA_IPC,(WPARAM)&myGenHotkeysAddStruct,genhotkeys_add_ipc); +// + +//flags for the genHotkeysAddStruct struct +#define HKF_BRING_TO_FRONT 0x1 // calls SetForegroundWindow before sending the message +#define HKF_HWND_WPARAM 0x2 // sets wParam with Winamp's window handle +#define HKF_COPY 0x4 // copies returned text to the clipboard +#define HKF_PLPOS_WPARAM 0x8 // sets wParam with current pledit position +#define HKF_ISPLAYING_WL 0x10 // sets wParam to genHotkeysAddStruct's wParam if playing, lParam if not + // uses IPC_ISPLAYING to check if playing +#define HKF_SHOWHIDE 0x20 // brings Winamp to front or minimizes Winamp if already at front +#define HKF_NOSENDMSG 0x40 // don't send any message to the winamp window + +#define HKF_DISABLED 0x80000000 + +typedef struct { + char *name; //name that will appear in the Global Hotkeys preferences panel + DWORD flags; //one or more flags from above + UINT uMsg; //message that will be sent to winamp's main window (must always be !=NULL) + WPARAM wParam; //wParam that will be sent to winamp's main window + LPARAM lParam; //lParam that will be sent to winamp's main window + char *id; //unique string to identify this command - case insensitive + HWND wnd; //set the HWND to send message (or 0 for main winamp window) + + int extended[6]; //for future extension - always set to zero! +} genHotkeysAddStruct; + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/wasabi/Winamp/wa_ipc.h b/Src/Plugins/Input/in_wv/wasabi/Winamp/wa_ipc.h new file mode 100644 index 00000000..dd2559b5 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/Winamp/wa_ipc.h @@ -0,0 +1,2468 @@ +/* +** Copyright (C) 1997-2008 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +*/ + +#ifndef _WA_IPC_H_ +#define _WA_IPC_H_ + +#include <windows.h> +#include <stddef.h> +#if (_MSC_VER <= 1200) +typedef int intptr_t; +#endif +/* +** This is the modern replacement for the classic 'frontend.h'. Most of these +** updates are designed for in-process use, i.e. from a plugin. +** +*/ + +/* Most of the IPC_* messages involve sending the message in the form of: +** result = SendMessage(hwnd_winamp,WM_WA_IPC,(parameter),IPC_*); +** Where different then this is specified (typically with WM_COPYDATA variants) +** +** When you use SendMessage(hwnd_winamp,WM_WA_IPC,(parameter),IPC_*) and specify a IPC_* +** which is not currently implemented/supported by the Winamp version being used then it +** will return 1 for 'result'. This is a good way of helping to check if an api being +** used which returns a function pointer, etc is even going to be valid. +*/ + +#define WM_WA_IPC WM_USER + +#define WINAMP_VERSION_MAJOR(winampVersion) ((winampVersion & 0x0000FF00) >> 12) +#define WINAMP_VERSION_MINOR(winampVersion) (winampVersion & 0x000000FF) // returns, i.e. 0x12 for 5.12 and 0x10 for 5.1... + + +#define IPC_GETVERSION 0 +/* int version = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETVERSION); +** +** The version returned will be 0x20yx for Winamp 2.yx. +** Versions previous to Winamp 2.0 typically (but not always) use 0x1zyx for 1.zx. +** Just a bit weird but that's the way it goes. +** +** For Winamp 5.x it uses the format 0x50yx for Winamp 5.yx +** e.g. 5.01 -> 0x5001 +** 5.09 -> 0x5009 +** 5.1 -> 0x5010 +** +** Notes: For 5.02 this api will return the same value as for a 5.01 build. +** For 5.07 this api will return the same value as for a 5.06 build. +*/ + + +#define IPC_GETVERSIONSTRING 1 + + +#define IPC_GETREGISTEREDVERSION 770 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETREGISTEREDVERSION); +** +** This will open the Winamp Preferences and show the Winamp Pro page. +*/ + + +typedef struct { + const char *filename; + const char *title; + int length; +} enqueueFileWithMetaStruct; // send this to a IPC_PLAYFILE in a non WM_COPYDATA, +// and you get the nice desired result. if title is NULL, it is treated as a "thing", +// otherwise it's assumed to be a file (for speed) + +typedef struct { + const wchar_t *filename; + const wchar_t *title; + int length; +} enqueueFileWithMetaStructW; + +#define IPC_PLAYFILE 100 // dont be fooled, this is really the same as enqueufile +#define IPC_ENQUEUEFILE 100 +#define IPC_PLAYFILEW 1100 +#define IPC_ENQUEUEFILEW 1100 +/* This is sent as a WM_COPYDATA with IPC_PLAYFILE as the dwData member and the string +** of the file / playlist to be enqueued into the playlist editor as the lpData member. +** This will just enqueue the file or files since you can use this to enqueue a playlist. +** It will not clear the current playlist or change the playback state. +** +** COPYDATASTRUCT cds = {0}; +** cds.dwData = IPC_ENQUEUEFILE; +** cds.lpData = (void*)"c:\\test\\folder\\test.mp3"; +** cds.cbData = lstrlen((char*)cds.lpData)+1; // include space for null char +** SendMessage(hwnd_winamp,WM_COPYDATA,0,(LPARAM)&cds); +** +** +** With 2.9+ and all of the 5.x versions you can send this as a normal WM_WA_IPC +** (non WM_COPYDATA) with an enqueueFileWithMetaStruct as the param. +** If the title member is null then it is treated as a "thing" otherwise it will be +** assumed to be a file (for speed). +** +** enqueueFileWithMetaStruct eFWMS = {0}; +** eFWMS.filename = "c:\\test\\folder\\test.mp3"; +** eFWMS.title = "Whipping Good"; +** eFWMS.length = 300; // this is the number of seconds for the track +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&eFWMS,IPC_ENQUEUEFILE); +*/ + + +#define IPC_DELETE 101 +#define IPC_DELETE_INT 1101 +/* SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_DELETE); +** Use this api to clear Winamp's internal playlist. +** You should not need to use IPC_DELETE_INT since it is used internally by Winamp when +** it is dealing with some lame Windows Explorer issues (hard to believe that!). +*/ + + +#define IPC_STARTPLAY 102 +#define IPC_STARTPLAY_INT 1102 +/* SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_STARTPLAY); +** Sending this will start playback and is almost the same as hitting the play button. +** The IPC_STARTPLAY_INT version is used internally and you should not need to use it +** since it won't be any fun. +*/ + + +#define IPC_CHDIR 103 +/* This is sent as a WM_COPYDATA type message with IPC_CHDIR as the dwData value and the +** directory you want to change to as the lpData member. +** +** COPYDATASTRUCT cds = {0}; +** cds.dwData = IPC_CHDIR; +** cds.lpData = (void*)"c:\\download"; +** cds.cbData = lstrlen((char*)cds.lpData)+1; // include space for null char +** SendMessage(hwnd_winamp,WM_COPYDATA,0,(LPARAM)&cds); +** +** The above example will make Winamp change to the directory 'C:\download'. +*/ + + +#define IPC_ISPLAYING 104 +/* int res = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_ISPLAYING); +** This is sent to retrieve the current playback state of Winamp. +** If it returns 1, Winamp is playing. +** If it returns 3, Winamp is paused. +** If it returns 0, Winamp is not playing. +*/ + + +#define IPC_GETOUTPUTTIME 105 +/* int res = SendMessage(hwnd_winamp,WM_WA_IPC,mode,IPC_GETOUTPUTTIME); +** This api can return two different sets of information about current playback status. +** +** If mode = 0 then it will return the position (in ms) of the currently playing track. +** Will return -1 if Winamp is not playing. +** +** If mode = 1 then it will return the current track length (in seconds). +** Will return -1 if there are no tracks (or possibly if Winamp cannot get the length). +** +** If mode = 2 then it will return the current track length (in milliseconds). +** Will return -1 if there are no tracks (or possibly if Winamp cannot get the length). +*/ + + +#define IPC_JUMPTOTIME 106 +/* (requires Winamp 1.60+) +** SendMessage(hwnd_winamp,WM_WA_IPC,ms,IPC_JUMPTOTIME); +** This api sets the current position (in milliseconds) for the currently playing song. +** The resulting playback position may only be an approximate time since some playback +** formats do not provide exact seeking e.g. mp3 +** This returns -1 if Winamp is not playing, 1 on end of file, or 0 if it was successful. +*/ + + +#define IPC_GETMODULENAME 109 +#define IPC_EX_ISRIGHTEXE 666 +/* usually shouldnt bother using these, but here goes: +** send a WM_COPYDATA with IPC_GETMODULENAME, and an internal +** flag gets set, which if you send a normal WM_WA_IPC message with +** IPC_EX_ISRIGHTEXE, it returns whether or not that filename +** matches. lame, I know. +*/ + + +#define IPC_WRITEPLAYLIST 120 +/* (requires Winamp 1.666+) +** int cur = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_WRITEPLAYLIST); +** +** IPC_WRITEPLAYLIST will write the current playlist to '<winampdir>\\Winamp.m3u' and +** will also return the current playlist position (see IPC_GETLISTPOS). +** +** This is kinda obsoleted by some of the newer 2.x api items but it still is good for +** use with a front-end program (instead of a plug-in) and you want to see what is in the +** current playlist. +** +** This api will only save out extended file information in the #EXTINF entry if Winamp +** has already read the data such as if the file was played of scrolled into view. If +** Winamp has not read the data then you will only find the file with its filepath entry +** (as is the base requirements for a m3u playlist). +*/ + + +#define IPC_SETPLAYLISTPOS 121 +/* (requires Winamp 2.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,position,IPC_SETPLAYLISTPOS) +** IPC_SETPLAYLISTPOS sets the playlist position to the specified 'position'. +** It will not change playback status or anything else. It will just set the current +** position in the playlist and will update the playlist view if necessary. +** +** If you use SendMessage(hwnd_winamp,WM_COMMAND,MAKEWPARAM(WINAMP_BUTTON2,0),0); +** after using IPC_SETPLAYLISTPOS then Winamp will start playing the file at 'position'. +*/ + + +#define IPC_SETVOLUME 122 +/* (requires Winamp 2.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,volume,IPC_SETVOLUME); +** IPC_SETVOLUME sets the volume of Winamp (between the range of 0 to 255). +** +** If you pass 'volume' as -666 then the message will return the current volume. +** int curvol = SendMessage(hwnd_winamp,WM_WA_IPC,-666,IPC_SETVOLUME); +*/ + + +#define IPC_GETVOLUME(hwnd_winamp) SendMessage(hwnd_winamp,WM_WA_IPC,-666,IPC_SETVOLUME) +/* (requires Winamp 2.0+) +** int curvol = IPC_GETVOLUME(hwnd_winamp); +** This will return the current volume of Winamp or +*/ + + +#define IPC_SETPANNING 123 +/* (requires Winamp 2.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,panning,IPC_SETPANNING); +** IPC_SETPANNING sets the panning of Winamp from 0 (left) to 255 (right). +** +** At least in 5.x+ this works from -127 (left) to 127 (right). +** +** If you pass 'panning' as -666 to this api then it will return the current panning. +** int curpan = SendMessage(hwnd_winamp,WM_WA_IPC,-666,IPC_SETPANNING); +*/ + + +#define IPC_GETLISTLENGTH 124 +/* (requires Winamp 2.0+) +** int length = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLISTLENGTH); +** IPC_GETLISTLENGTH returns the length of the current playlist as the number of tracks. +*/ + + +#define IPC_GETLISTPOS 125 +/* (requires Winamp 2.05+) +** int pos=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLISTPOS); +** IPC_GETLISTPOS returns the current playlist position (which is shown in the playlist +** editor as a differently coloured text entry e.g is yellow for the classic skin). +** +** This api is a lot like IPC_WRITEPLAYLIST but a lot faster since it does not have to +** write out the whole of the current playlist first. +*/ + + +#define IPC_GETINFO 126 +/* (requires Winamp 2.05+) +** int inf=SendMessage(hwnd_winamp,WM_WA_IPC,mode,IPC_GETINFO); +** IPC_GETINFO returns info about the current playing song. The value +** it returns depends on the value of 'mode'. +** Mode Meaning +** ------------------ +** 0 Samplerate, in kilohertz (i.e. 44) +** 1 Bitrate (i.e. 128) +** 2 Channels (i.e. 2) +** 3 (5+) Video LOWORD=w HIWORD=h +** 4 (5+) > 65536, string (video description) +** 5 (5.25+) Samplerate, in hertz (i.e. 44100) +*/ + + +#define IPC_GETEQDATA 127 +/* (requires Winamp 2.05+) +** int data=SendMessage(hwnd_winamp,WM_WA_IPC,pos,IPC_GETEQDATA); +** IPC_GETEQDATA queries the status of the EQ. +** The value returned depends on what 'pos' is set to: +** Value Meaning +** ------------------ +** 0-9 The 10 bands of EQ data. 0-63 (+20db - -20db) +** 10 The preamp value. 0-63 (+20db - -20db) +** 11 Enabled. zero if disabled, nonzero if enabled. +** 12 Autoload. zero if disabled, nonzero if enabled. +*/ + + +#define IPC_SETEQDATA 128 +/* (requires Winamp 2.05+) +** SendMessage(hwnd_winamp,WM_WA_IPC,pos,IPC_GETEQDATA); +** SendMessage(hwnd_winamp,WM_WA_IPC,value,IPC_SETEQDATA); +** IPC_SETEQDATA sets the value of the last position retrieved +** by IPC_GETEQDATA. This is pretty lame, and we should provide +** an extended version that lets you do a MAKELPARAM(pos,value). +** someday... + + new (2.92+): + if the high byte is set to 0xDB, then the third byte specifies + which band, and the bottom word specifies the value. +*/ + + +#define IPC_ADDBOOKMARK 129 +#define IPC_ADDBOOKMARKW 131 +/* (requires Winamp 2.4+) +** This is sent as a WM_COPYDATA using IPC_ADDBOOKMARK as the dwData value and the +** directory you want to change to as the lpData member. This will add the specified +** file / url to the Winamp bookmark list. +** +** COPYDATASTRUCT cds = {0}; +** cds.dwData = IPC_ADDBOOKMARK; +** cds.lpData = (void*)"http://www.blah.com/listen.pls"; +** cds.cbData = lstrlen((char*)cds.lpData)+1; // include space for null char +** SendMessage(hwnd_winamp,WM_COPYDATA,0,(LPARAM)&cds); +** +** +** In Winamp 5.0+ we use this as a normal WM_WA_IPC and the string is null separated as +** the filename and then the title of the entry. +** +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(char*)"filename\0title\0",IPC_ADDBOOKMARK); +** +** This will notify the library / bookmark editor that a bookmark was added. +** Note that using this message in this context does not actually add the bookmark. +** Do not use, it is essentially just a notification type message :) +*/ + + +#define IPC_INSTALLPLUGIN 130 +/* This is not implemented (and is very unlikely to be done due to safety concerns). +** If it was then you could do a WM_COPYDATA with a path to a .wpz and it would then +** install the plugin for you. +** +** COPYDATASTRUCT cds = {0}; +** cds.dwData = IPC_INSTALLPLUGIN; +** cds.lpData = (void*)"c:\\path\\to\\file.wpz"; +** cds.cbData = lstrlen((char*)cds.lpData)+1; // include space for null char +** SendMessage(hwnd_winamp,WM_COPYDATA,0,(LPARAM)&cds); +*/ + + +#define IPC_RESTARTWINAMP 135 +/* (requires Winamp 2.2+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_RESTARTWINAMP); +** IPC_RESTARTWINAMP will restart Winamp (isn't that obvious ? :) ) +** If this fails to make Winamp start after closing then there is a good chance one (or +** more) of the currently installed plugins caused Winamp to crash on exit (either as a +** silent crash or a full crash log report before it could call itself start again. +*/ + + +#define IPC_ISFULLSTOP 400 +/* (requires winamp 2.7+ I think) +** int ret=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_ISFULLSTOP); +** This is useful for when you're an output plugin and you want to see if the stop/close +** happening is a full stop or if you are just between tracks. This returns non zero if +** it is a full stop or zero if it is just a new track. +** benski> i think it's actually the other way around - +** !0 for EOF and 0 for user pressing stop +*/ + + +#define IPC_INETAVAILABLE 242 +/* (requires Winamp 2.05+) +** int val=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_INETAVAILABLE); +** IPC_INETAVAILABLE will return 1 if an Internet connection is available for Winamp and +** relates to the internet connection type setting on the main general preferences page +** in the Winamp preferences. +*/ + + +#define IPC_UPDTITLE 243 +/* (requires Winamp 2.2+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_UPDTITLE); +** IPC_UPDTITLE will ask Winamp to update the information about the current title and +** causes GetFileInfo(..) in the input plugin associated with the current playlist entry +** to be called. This can be called such as when an input plugin is buffering a file so +** that it can cause the buffer percentage to appear in the playlist. +*/ + + +#define IPC_REFRESHPLCACHE 247 +/* (requires Winamp 2.2+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_REFRESHPLCACHE); +** IPC_REFRESHPLCACHE will flush the playlist cache buffer and you send this if you want +** Winamp to go refetch the titles for all of the entries in the current playlist. +** +** 5.3+: pass a wchar_t * string in wParam, and it'll do a strnicmp() before clearing the cache +*/ + + +#define IPC_GET_SHUFFLE 250 +/* (requires Winamp 2.4+) +** int val=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GET_SHUFFLE); +** IPC_GET_SHUFFLE returns the status of the shuffle option. +** If set then it will return 1 and if not set then it will return 0. +*/ + + +#define IPC_GET_REPEAT 251 +/* (requires Winamp 2.4+) +** int val=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GET_REPEAT); +** IPC_GET_REPEAT returns the status of the repeat option. +** If set then it will return 1 and if not set then it will return 0. +*/ + + +#define IPC_SET_SHUFFLE 252 +/* (requires Winamp 2.4+) +** SendMessage(hwnd_winamp,WM_WA_IPC,value,IPC_SET_SHUFFLE); +** IPC_SET_SHUFFLE sets the status of the shuffle option. +** If 'value' is 1 then shuffle is turned on. +** If 'value' is 0 then shuffle is turned off. +*/ + + +#define IPC_SET_REPEAT 253 +/* (requires Winamp 2.4+) +** SendMessage(hwnd_winamp,WM_WA_IPC,value,IPC_SET_REPEAT); +** IPC_SET_REPEAT sets the status of the repeat option. +** If 'value' is 1 then shuffle is turned on. +** If 'value' is 0 then shuffle is turned off. +*/ + + +#define IPC_ENABLEDISABLE_ALL_WINDOWS 259 // 0xdeadbeef to disable +/* (requires Winamp 2.9+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(enable?0:0xdeadbeef),IPC_ENABLEDISABLE_ALL_WINDOWS); +** Sending this message with 0xdeadbeef as the param will disable all winamp windows and +** any other values will enable all of the Winamp windows again. When disabled you won't +** get any response on clicking or trying to do anything to the Winamp windows. If the +** taskbar icon is shown then you may still have control ;) +*/ + + +#define IPC_GETWND 260 +/* (requires Winamp 2.9+) +** HWND h=SendMessage(hwnd_winamp,WM_WA_IPC,IPC_GETWND_xxx,IPC_GETWND); +** returns the HWND of the window specified. +*/ + #define IPC_GETWND_EQ 0 // use one of these for the param + #define IPC_GETWND_PE 1 + #define IPC_GETWND_MB 2 + #define IPC_GETWND_VIDEO 3 +#define IPC_ISWNDVISIBLE 261 // same param as IPC_GETWND + + +/************************************************************************ +***************** in-process only (WE LOVE PLUGINS) +************************************************************************/ + +#define IPC_SETSKINW 199 +#define IPC_SETSKIN 200 +/* (requires Winamp 2.04+, only usable from plug-ins (not external apps)) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)"skinname",IPC_SETSKIN); +** IPC_SETSKIN sets the current skin to "skinname". Note that skinname +** can be the name of a skin, a skin .zip file, with or without path. +** If path isn't specified, the default search path is the winamp skins +** directory. +*/ + + +#define IPC_GETSKIN 201 +#define IPC_GETSKINW 1201 +/* (requires Winamp 2.04+, only usable from plug-ins (not external apps)) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)skinname_buffer,IPC_GETSKIN); +** IPC_GETSKIN puts the directory where skin bitmaps can be found +** into skinname_buffer. +** skinname_buffer must be MAX_PATH characters in length. +** When using a .zip'd skin file, it'll return a temporary directory +** where the ZIP was decompressed. +*/ + + +#define IPC_EXECPLUG 202 +/* (requires Winamp 2.04+, only usable from plug-ins (not external apps)) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)"vis_file.dll",IPC_EXECPLUG); +** IPC_EXECPLUG executes a visualization plug-in pointed to by WPARAM. +** the format of this string can be: +** "vis_whatever.dll" +** "vis_whatever.dll,0" // (first mod, file in winamp plug-in dir) +** "C:\\dir\\vis_whatever.dll,1" +*/ + + +#define IPC_GETPLAYLISTFILE 211 +#define IPC_GETPLAYLISTFILEW 214 +/* (requires Winamp 2.04+, only usable from plug-ins (not external apps)) +** char *name=SendMessage(hwnd_winamp,WM_WA_IPC,index,IPC_GETPLAYLISTFILE); +** IPC_GETPLAYLISTFILE gets the filename of the playlist entry [index]. +** returns a pointer to it. returns NULL on error. +*/ + + +#define IPC_GETPLAYLISTTITLE 212 +#define IPC_GETPLAYLISTTITLEW 213 +/* (requires Winamp 2.04+, only usable from plug-ins (not external apps)) +** char *name=SendMessage(hwnd_winamp,WM_WA_IPC,index,IPC_GETPLAYLISTTITLE); +** +** IPC_GETPLAYLISTTITLE gets the title of the playlist entry [index]. +** returns a pointer to it. returns NULL on error. +*/ + + +#define IPC_GETHTTPGETTER 240 +/* retrieves a function pointer to a HTTP retrieval function. +** if this is unsupported, returns 1 or 0. +** the function should be: +** int (*httpRetrieveFile)(HWND hwnd, char *url, char *file, char *dlgtitle); +** if you call this function, with a parent window, a URL, an output file, and a dialog title, +** it will return 0 on successful download, 1 on error. +*/ + + +#define IPC_GETHTTPGETTERW 1240 +/* int (*httpRetrieveFileW)(HWND hwnd, char *url, wchar_t *file, wchar_t *dlgtitle); */ + + +#define IPC_MBOPEN 241 +/* (requires Winamp 2.05+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_MBOPEN); +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)url,IPC_MBOPEN); +** IPC_MBOPEN will open a new URL in the minibrowser. if url is NULL, it will open the Minibrowser window. +*/ + + +#define IPC_CHANGECURRENTFILE 245 +/* (requires Winamp 2.05+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)file,IPC_CHANGECURRENTFILE); +** IPC_CHANGECURRENTFILE will set the current playlist item. +*/ + + +#define IPC_CHANGECURRENTFILEW 1245 +/* (requires Winamp 5.3+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)file,IPC_CHANGECURRENTFILEW); +** IPC_CHANGECURRENTFILEW will set the current playlist item. +*/ + + +#define IPC_GETMBURL 246 +/* (requires Winamp 2.2+) +** char buffer[4096]; // Urls can be VERY long +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)buffer,IPC_GETMBURL); +** IPC_GETMBURL will retrieve the current Minibrowser URL into buffer. +** buffer must be at least 4096 bytes long. +*/ + + +#define IPC_MBBLOCK 248 +/* (requires Winamp 2.4+) +** SendMessage(hwnd_winamp,WM_WA_IPC,value,IPC_MBBLOCK); +** +** IPC_MBBLOCK will block the Minibrowser from updates if value is set to 1 +*/ + + +#define IPC_MBOPENREAL 249 +/* (requires Winamp 2.4+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)url,IPC_MBOPENREAL); +** +** IPC_MBOPENREAL works the same as IPC_MBOPEN except that it will works even if +** IPC_MBBLOCK has been set to 1 +*/ + + +#define IPC_ADJUST_OPTIONSMENUPOS 280 +/* (requires Winamp 2.9+) +** int newpos=SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)adjust_offset,IPC_ADJUST_OPTIONSMENUPOS); +** moves where winamp expects the Options menu in the main menu. Useful if you wish to insert a +** menu item above the options/skins/vis menus. +*/ + + +#define IPC_GET_HMENU 281 +/* (requires Winamp 2.9+) +** HMENU hMenu=SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)0,IPC_GET_HMENU); +** values for data: +** 0 : main popup menu +** 1 : main menubar file menu +** 2 : main menubar options menu +** 3 : main menubar windows menu +** 4 : main menubar help menu +** other values will return NULL. +*/ + + +#define IPC_GET_EXTENDED_FILE_INFO 290 //pass a pointer to the following struct in wParam +#define IPC_GET_EXTENDED_FILE_INFO_HOOKABLE 296 +/* (requires Winamp 2.9+) +** to use, create an extendedFileInfoStruct, point the values filename and metadata to the +** filename and metadata field you wish to query, and ret to a buffer, with retlen to the +** length of that buffer, and then SendMessage(hwnd_winamp,WM_WA_IPC,&struct,IPC_GET_EXTENDED_FILE_INFO); +** the results should be in the buffer pointed to by ret. +** returns 1 if the decoder supports a getExtendedFileInfo method +*/ +typedef struct { + const char *filename; + const char *metadata; + char *ret; + size_t retlen; +} extendedFileInfoStruct; + + +#define IPC_GET_BASIC_FILE_INFO 291 //pass a pointer to the following struct in wParam +typedef struct { + const char *filename; + + int quickCheck; // set to 0 to always get, 1 for quick, 2 for default (if 2, quickCheck will be set to 0 if quick wasnot used) + + // filled in by winamp + int length; + char *title; + int titlelen; +} basicFileInfoStruct; + + +#define IPC_GET_BASIC_FILE_INFOW 1291 //pass a pointer to the following struct in wParam +typedef struct { + const wchar_t *filename; + + int quickCheck; // set to 0 to always get, 1 for quick, 2 for default (if 2, quickCheck will be set to 0 if quick wasnot used) + + // filled in by winamp + int length; + wchar_t *title; + int titlelen; +} basicFileInfoStructW; + + +#define IPC_GET_EXTLIST 292 //returns doublenull delimited. GlobalFree() it when done. if data is 0, returns raw extlist, if 1, returns something suitable for getopenfilename +#define IPC_GET_EXTLISTW 1292 // wide char version of above + + +#define IPC_INFOBOX 293 +typedef struct { + HWND parent; + char *filename; +} infoBoxParam; + + +#define IPC_INFOBOXW 1293 +typedef struct { + HWND parent; + const wchar_t *filename; +} infoBoxParamW; + + +#define IPC_SET_EXTENDED_FILE_INFO 294 //pass a pointer to the a extendedFileInfoStruct in wParam +/* (requires Winamp 2.9+) +** to use, create an extendedFileInfoStruct, point the values filename and metadata to the +** filename and metadata field you wish to write in ret. (retlen is not used). and then +** SendMessage(hwnd_winamp,WM_WA_IPC,&struct,IPC_SET_EXTENDED_FILE_INFO); +** returns 1 if the metadata is supported +** Call IPC_WRITE_EXTENDED_FILE_INFO once you're done setting all the metadata you want to update +*/ + + +#define IPC_WRITE_EXTENDED_FILE_INFO 295 +/* (requires Winamp 2.9+) +** writes all the metadata set thru IPC_SET_EXTENDED_FILE_INFO to the file +** returns 1 if the file has been successfully updated, 0 if error +*/ + + +#define IPC_FORMAT_TITLE 297 +typedef struct +{ + char *spec; // NULL=default winamp spec + void *p; + + char *out; + int out_len; + + char * (*TAGFUNC)(const char * tag, void * p); //return 0 if not found + void (*TAGFREEFUNC)(char * tag,void * p); +} waFormatTitle; + + +#define IPC_FORMAT_TITLE_EXTENDED 298 // similiar to IPC_FORMAT_TITLE, but falls back to Winamp's %tags% if your passed tag function doesn't handle it +typedef struct +{ + const wchar_t *filename; + int useExtendedInfo; // set to 1 if you want the Title Formatter to query the input plugins for any tags that your tag function fails on + const wchar_t *spec; // NULL=default winamp spec + void *p; + + wchar_t *out; + int out_len; + + wchar_t * (*TAGFUNC)(const wchar_t * tag, void * p); //return 0 if not found, -1 for empty tag + void (*TAGFREEFUNC)(wchar_t *tag, void *p); +} waFormatTitleExtended; + + +#define IPC_COPY_EXTENDED_FILE_INFO 299 +typedef struct +{ + const char *source; + const char *dest; +} copyFileInfoStruct; + + +#define IPC_COPY_EXTENDED_FILE_INFOW 1299 +typedef struct +{ + const wchar_t *source; + const wchar_t *dest; +} copyFileInfoStructW; + + +typedef struct { + int (*inflateReset)(void *strm); + int (*inflateInit_)(void *strm,const char *version, int stream_size); + int (*inflate)(void *strm, int flush); + int (*inflateEnd)(void *strm); + unsigned long (*crc32)(unsigned long crc, const unsigned char *buf, unsigned int len); +} wa_inflate_struct; + +#define IPC_GETUNCOMPRESSINTERFACE 331 +/* returns a function pointer to uncompress(). +** int (*uncompress)(unsigned char *dest, unsigned long *destLen, const unsigned char *source, unsigned long sourceLen); +** right out of zlib, useful for decompressing zlibbed data. +** if you pass the parm of 0x10100000, it will return a wa_inflate_struct * to an inflate API. +*/ + + +typedef struct _prefsDlgRec { + HINSTANCE hInst; // dll instance containing the dialog resource + int dlgID; // resource identifier of the dialog + void *proc; // window proceedure for handling the dialog defined as + // LRESULT CALLBACK PrefsPage(HWND,UINT,WPARAM,LPARAM) + + char *name; // name shown for the prefs page in the treelist + intptr_t where; // section in the treelist the prefs page is to be added to + // 0 for General Preferences + // 1 for Plugins + // 2 for Skins + // 3 for Bookmarks (no longer in the 5.0+ prefs) + // 4 for Prefs (the old 'Setup' section - no longer in 5.0+) + + intptr_t _id; + struct _prefsDlgRec *next; // no longer implemented as a linked list, now used by Winamp for other means +} prefsDlgRec; + +typedef struct _prefsDlgRecW { + HINSTANCE hInst; // dll instance containing the dialog resource + int dlgID; // resource identifier of the dialog + void *proc; // window proceedure for handling the dialog defined as + // LRESULT CALLBACK PrefsPage(HWND,UINT,WPARAM,LPARAM) + + wchar_t *name; // name shown for the prefs page in the treelist + intptr_t where; // section in the treelist the prefs page is to be added to + // 0 for General Preferences + // 1 for Plugins + // 2 for Skins + // 3 for Bookmarks (no longer in the 5.0+ prefs) + // 4 for Prefs (the old 'Setup' section - no longer in 5.0+) + + intptr_t _id; + struct _prefsDlgRec *next; // no longer implemented as a linked list, now used by Winamp for other means +} prefsDlgRecW; + +#define IPC_ADD_PREFS_DLG 332 +#define IPC_ADD_PREFS_DLGW 1332 +#define IPC_REMOVE_PREFS_DLG 333 +/* (requires Winamp 2.9+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&prefsRec,IPC_ADD_PREFS_DLG); +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&prefsRec,IPC_REMOVE_PREFS_DLG); +** +** IPC_ADD_PREFS_DLG: +** To use this you need to allocate a prefsDlgRec structure (either on the heap or with +** some global data but NOT on the stack) and then initialise the members of the structure +** (see the definition of the prefsDlgRec structure above). +** +** hInst - dll instance of where the dialog resource is located. +** dlgID - id of the dialog resource. +** proc - dialog window procedure for the prefs dialog. +** name - name of the prefs page as shown in the preferences list. +** where - see above for the valid locations the page can be added. +** +** Then you do SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&prefsRec,IPC_ADD_PREFS_DLG); +** +** example: +** +** prefsDlgRec* prefsRec = 0; +** prefsRec = GlobalAlloc(GPTR,sizeof(prefsDlgRec)); +** prefsRec->hInst = hInst; +** prefsRec->dlgID = IDD_PREFDIALOG; +** prefsRec->name = "Pref Page"; +** prefsRec->where = 0; +** prefsRec->proc = PrefsPage; +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&prefsRec,IPC_ADD_PREFS_DLG); +** +** +** IPC_REMOVE_PREFS_DLG: +** To use you pass the address of the same prefsRec you used when adding the prefs page +** though you shouldn't really ever have to do this but it's good to clean up after you +** when you're plugin is being unloaded. +** +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&prefsRec,IPC_REMOVE_PREFS_DLG); +** +** IPC_ADD_PREFS_DLGW +** requires Winamp 5.53+ +*/ + + +#define IPC_OPENPREFSTOPAGE 380 +/* SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&prefsRec,IPC_OPENPREFSTOPAGE); +** +** There are two ways of opening a preferences page. +** +** The first is to pass an id of a builtin preferences page (see below for ids) or a +** &prefsDlgRec of the preferences page to open and this is normally done if you are +** opening a prefs page you added yourself. +** +** If the page id does not or the &prefsRec is not valid then the prefs dialog will be +** opened to the first page available (usually the Winamp Pro page). +** +** (requires Winamp 5.04+) +** Passing -1 for param will open the preferences dialog to the last page viewed. +** +** Note: v5.0 to 5.03 had a bug in this api +** +** On the first call then the correct prefs page would be opened to but on the next call +** the prefs dialog would be brought to the front but the page would not be changed to the +** specified. +** In 5.04+ it will change to the prefs page specified if the prefs dialog is already open. +*/ + +/* Builtin Preference page ids (valid for 5.0+) +** (stored in the lParam member of the TVITEM structure from the tree item) +** +** These can be useful if you want to detect a specific prefs page and add things to it +** yourself or something like that ;) +** +** Winamp Pro 20 +** General Preferences 0 +** File Types 1 +** Playlist 23 +** Titles 21 +** Playback 42 (added in 5.25) +** Station Info 41 (added in 5.11 & removed in 5.5) +** Video 24 +** Localization 25 (added in 5.5) +** Skins 40 +** Classic Skins 22 +** Plugins 30 +** Input 31 +** Output 32 +** Visualisation 33 +** DSP/Effect 34 +** General Purpose 35 +** +** Note: +** Custom page ids begin from 60 +** The value of the normal custom pages (Global Hotkeys, Jump To File, etc) is not +** guaranteed since it depends on the order in which the plugins are loaded which can +** change on different systems. +** +** Global Hotkeys, Jump To File, Media Library (under General Preferences and child pages), +** Media Library (under Plugins), Portables, CD Ripping and Modern Skins are custom pages +** created by the plugins shipped with Winamp. +*/ + + +#define IPC_GETINIFILE 334 +/* (requires Winamp 2.9+) +** char *ini=(char*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETINIFILE); +** This returns a pointer to the full file path of winamp.ini. +** +** char ini_path[MAX_PATH] = {0}; +** +** void GetIniFilePath(HWND hwnd){ +** if(SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETVERSION) >= 0x2900){ +** // this gets the string of the full ini file path +** lstrcpyn(ini_path,(char*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETINIFILE),sizeof(ini_path)); +** } +** else{ +** char* p = ini_path; +** p += GetModuleFileName(0,ini_path,sizeof(ini_path)) - 1; +** while(p && *p != '.'){p--;} +** lstrcpyn(p+1,"ini",sizeof(ini_path)); +** } +** } +*/ + + +#define IPC_GETINIDIRECTORY 335 +/* (requires Winamp 2.9+) +** char *dir=(char*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETINIDIRECTORY); +** This returns a pointer to the directory where winamp.ini can be found and is +** useful if you want store config files but you don't want to use winamp.ini. +*/ + + +#define IPC_GETPLUGINDIRECTORY 336 +/* (requires Winamp 5.11+) +** char *plugdir=(char*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETPLUGINDIRECTORY); +** This returns a pointer to the directory where Winamp has its plugins stored and is +** useful if you want store config files in plugins.ini in the plugins folder or for +** accessing any local files in the plugins folder. +*/ + + +#define IPC_GETM3UDIRECTORY 337 +/* (requires Winamp 5.11+) +** char *m3udir=(char*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETM3UDIRECTORY); +** This returns a pointer to the directory where winamp.m3u (and winamp.m3u8 if supported) is stored in. +*/ + + +#define IPC_GETM3UDIRECTORYW 338 +/* (requires Winamp 5.3+) +** wchar_t *m3udirW=(wchar_t*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETM3UDIRECTORYW); +** This returns a pointer to the directory where winamp.m3u (and winamp.m3u8 if supported) is stored in. +*/ + + +#define IPC_SPAWNBUTTONPOPUP 361 // param = +// 0 = eject +// 1 = previous +// 2 = next +// 3 = pause +// 4 = play +// 5 = stop + + +#define IPC_OPENURLBOX 360 +/* (requires Winamp 5.0+) +** HGLOBAL hglobal = (HGLOBAL)SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(HWND)parent,IPC_OPENURLBOX); +** You pass a hwnd for the dialog to be parented to (which modern skin support uses). +** This will return a HGLOBAL that needs to be freed with GlobalFree() if this worked. +*/ + + +#define IPC_OPENFILEBOX 362 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(HWND)parent,IPC_OPENFILEBOX); +** You pass a hwnd for the dialog to be parented to (which modern skin support uses). +*/ + + +#define IPC_OPENDIRBOX 363 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(HWND)parent,IPC_OPENDIRBOX); +** You pass a hwnd for the dialog to be parented to (which modern skin support uses). +*/ + + +#define IPC_SETDIALOGBOXPARENT 364 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(HWND)parent,IPC_SETDIALOGBOXPARENT); +** Pass 'parent' as the window which will be used as the parent for a number of the built +** in Winamp dialogs and is useful when you are taking over the whole of the UI so that +** the dialogs will not appear at the bottom right of the screen since the main winamp +** window is located at 3000x3000 by gen_ff when this is used. Call this again with +** parent = null to reset the parent back to the orginal Winamp window. +*/ + +#define IPC_GETDIALOGBOXPARENT 365 +/* (requires Winamp 5.51+) +** HWND hwndParent = SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)0, IPC_GETDIALOGBOXPARENT); +** hwndParent can/must be passed to all modal dialogs (including MessageBox) thats uses winamp as a parent +*/ + +#define IPC_UPDATEDIALOGBOXPARENT 366 +/* (requires Winamp 5.53+) +** if you previous called IPC_SETDIALOGBOXPARENT, call this every time your window resizes +*/ + +#define IPC_DRO_MIN 401 // reserved for DrO +#define IPC_SET_JTF_COMPARATOR 409 +/* pass me an int (__cdecl *)(const char *, const char *) in wParam */ +#define IPC_SET_JTF_COMPARATOR_W 410 +/* pass me an int (__cdecl *)(const wchar_t *, const wchar_t *) in wParam ... maybe someday :) */ +#define IPC_SET_JTF_DRAWTEXT 416 + +#define IPC_DRO_MAX 499 + + +// pass 0 for a copy of the skin HBITMAP +// pass 1 for name of font to use for playlist editor likeness +// pass 2 for font charset +// pass 3 for font size +#define IPC_GET_GENSKINBITMAP 503 + + +typedef struct +{ + HWND me; //hwnd of the window + + #define EMBED_FLAGS_NORESIZE 0x1 + // set this bit to keep window from being resizable + + #define EMBED_FLAGS_NOTRANSPARENCY 0x2 + // set this bit to make gen_ff turn transparency off for this window + + #define EMBED_FLAGS_NOWINDOWMENU 0x4 + // set this bit to prevent gen_ff from automatically adding your window to the right-click menu + + #define EMBED_FLAGS_GUID 0x8 + // (5.31+) call SET_EMBED_GUID(yourEmbedWindowStateStruct, GUID) to define a GUID for this window + + #define SET_EMBED_GUID(windowState, windowGUID) { windowState->flags |= EMBED_FLAGS_GUID; *((GUID *)&windowState->extra_data[4])=windowGUID; } + #define GET_EMBED_GUID(windowState) (*((GUID *)&windowState->extra_data[4])) + + int flags; // see above + + RECT r; + void *user_ptr; // for application use + int extra_data[64]; // for internal winamp use +} embedWindowState; + +#define IPC_GET_EMBEDIF 505 +/* (requires Winamp 2.9+) +** HWND myframe = (HWND)SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&wa_wnd,IPC_GET_EMBEDIF); +** +** or +** +** HWND myframe = 0; +** HWND (*embed)(embedWindowState *params)=0; +** *(void**)&embed = (void*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GET_EMBEDIF); +** myframe = embed(&wa_wnd); +** +** You pass an embedWindowState* and it will return a hwnd for the frame window or if you +** pass wParam as null then it will return a HWND embedWindow(embedWindowState *); +*/ + +#define IPC_SKINWINDOW 534 + +typedef struct __SKINWINDOWPARAM +{ + HWND hwndToSkin; + GUID windowGuid; +} SKINWINDOWPARAM; + + + +#define IPC_EMBED_ENUM 532 +typedef struct embedEnumStruct +{ + int (*enumProc)(embedWindowState *ws, struct embedEnumStruct *param); // return 1 to abort + int user_data; // or more :) +} embedEnumStruct; + // pass + + +#define IPC_EMBED_ISVALID 533 +/* (requires Winamp 2.9+) +** int valid = SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)embedhwnd,IPC_EMBED_ISVALID); +** Pass a hwnd in the wParam to this to check if the hwnd is a valid embed window or not. +*/ + + +#define IPC_CONVERTFILE 506 +/* (requires Winamp 2.92+) +** Converts a given file to a different format (PCM, MP3, etc...) +** To use, pass a pointer to a waFileConvertStruct struct +** This struct can be either on the heap or some global +** data, but NOT on the stack. At least, until the conversion is done. +** +** eg: SendMessage(hwnd_winamp,WM_WA_IPC,&myConvertStruct,IPC_CONVERTFILE); +** +** Return value: +** 0: Can't start the conversion. Look at myConvertStruct->error for details. +** 1: Conversion started. Status messages will be sent to the specified callbackhwnd. +** Be sure to call IPC_CONVERTFILE_END when your callback window receives the +** IPC_CB_CONVERT_DONE message. +*/ +typedef struct +{ + char *sourcefile; // "c:\\source.mp3" + char *destfile; // "c:\\dest.pcm" + intptr_t destformat[8]; // like 'PCM ',srate,nch,bps. + //hack alert! you can set destformat[6]=mmioFOURCC('I','N','I',' '); and destformat[7]=(int)my_ini_file; (where my_ini_file is a char*) + HWND callbackhwnd; // window that will receive the IPC_CB_CONVERT notification messages + + //filled in by winamp.exe + char *error; //if IPC_CONVERTFILE returns 0, the reason will be here + + int bytes_done; //you can look at both of these values for speed statistics + int bytes_total; + int bytes_out; + + int killswitch; // don't set it manually, use IPC_CONVERTFILE_END + intptr_t extra_data[64]; // for internal winamp use +} convertFileStruct; + + +#define IPC_CONVERTFILEW 515 +// (requires Winamp 5.36+) +typedef struct +{ + wchar_t *sourcefile; // "c:\\source.mp3" + wchar_t *destfile; // "c:\\dest.pcm" + intptr_t destformat[8]; // like 'PCM ',srate,nch,bps. + //hack alert! you can set destformat[6]=mmioFOURCC('I','N','I',' '); and destformat[7]=(int)my_ini_file; (where my_ini_file is a char*) + HWND callbackhwnd; // window that will receive the IPC_CB_CONVERT notification messages + + //filled in by winamp.exe + wchar_t *error; //if IPC_CONVERTFILE returns 0, the reason will be here + + int bytes_done; //you can look at both of these values for speed statistics + int bytes_total; + int bytes_out; + + int killswitch; // don't set it manually, use IPC_CONVERTFILE_END + intptr_t extra_data[64]; // for internal winamp use +} convertFileStructW; + + +#define IPC_CONVERTFILE_END 507 +/* (requires Winamp 2.92+) +** Stop/ends a convert process started from IPC_CONVERTFILE +** You need to call this when you receive the IPC_CB_CONVERTDONE message or when you +** want to abort a conversion process +** +** eg: SendMessage(hwnd_winamp,WM_WA_IPC,&myConvertStruct,IPC_CONVERTFILE_END); +** +** No return value +*/ + + +#define IPC_CONVERTFILEW_END 516 +// (requires Winamp 5.36+) + +typedef struct { + HWND hwndParent; + int format; + + //filled in by winamp.exe + HWND hwndConfig; + int extra_data[8]; + //hack alert! you can set extra_data[6]=mmioFOURCC('I','N','I',' '); and extra_data[7]=(int)my_ini_file; (where my_ini_file is a char*) +} convertConfigStruct; + + +#define IPC_CONVERT_CONFIG 508 +#define IPC_CONVERT_CONFIG_END 509 + +typedef struct +{ + void (*enumProc)(intptr_t user_data, const char *desc, int fourcc); + intptr_t user_data; +} converterEnumFmtStruct; +#define IPC_CONVERT_CONFIG_ENUMFMTS 510 +/* (requires Winamp 2.92+) +*/ + +typedef struct +{ + char cdletter; + char *playlist_file; + HWND callback_hwnd; + + //filled in by winamp.exe + char *error; +} burnCDStruct; +#define IPC_BURN_CD 511 +/* (requires Winamp 5.0+) +*/ + +typedef struct +{ + convertFileStruct *cfs; + int priority; +} convertSetPriority; + + +#define IPC_CONVERT_SET_PRIORITY 512 + +typedef struct +{ + convertFileStructW *cfs; + int priority; +} convertSetPriorityW; + + +#define IPC_CONVERT_SET_PRIORITYW 517 +// (requires Winamp 5.36+) + +typedef struct +{ + unsigned int format; //fourcc value + char *item; // config item, eg "bitrate" + char *data; // buffer to recieve, or buffer that contains the data + int len; // length of the data buffer (only used when getting a config item) + char *configfile; // config file to read from +} convertConfigItem; + + +#define IPC_CONVERT_CONFIG_SET_ITEM 513 // returns TRUE if successful +#define IPC_CONVERT_CONFIG_GET_ITEM 514 // returns TRUE if successful + + +typedef struct +{ + const char *filename; + char *title; // 2048 bytes + int length; + int force_useformatting; // can set this to 1 if you want to force a url to use title formatting shit +} waHookTitleStruct; + +#define IPC_HOOK_TITLES 850 +/* (requires Winamp 5.0+) +** If you hook this message and modify the information then make sure to return TRUE. +** If you don't hook the message then make sure you pass it on through the subclass chain. +** +** LRESULT CALLBACK WinampWndProc(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) +** { +** LRESULT ret = CallWindowProc((WNDPROC)WinampProc,hwnd,umsg,wParam,lParam); +** +** if(message==WM_WA_IPC && lParam==IPC_HOOK_TITLES) +** { +** waHookTitleStruct *ht = (waHookTitleStruct *) wParam; +** // Doing ATF stuff with ht->title, whatever... +** return TRUE; +** } +** return ret; +** } +*/ + +typedef struct +{ + const wchar_t *filename; + wchar_t *title; // 2048 characters + int length; + int force_useformatting; // can set this to 1 if you want to force a url to use title formatting shit +} waHookTitleStructW; +#define IPC_HOOK_TITLESW 851 +/* (requires Winamp 5.3+) +** See information on IPC_HOOK_TITLES for how to process this. +*/ + + +#define IPC_GETSADATAFUNC 800 +// 0: returns a char *export_sa_get() that returns 150 bytes of data +// 1: returns a export_sa_setreq(int want); + + +#define IPC_GETVUDATAFUNC 801 +// 0: returns a int export_vu_get(int channel) that returns 0-255 (or -1 for bad channel) + + +#define IPC_ISMAINWNDVISIBLE 900 +/* (requires Winamp 5.0+) +** int visible=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_ISMAINWNDVISIBLE); +** You send this to Winamp to query if the main window is visible or not such as by +** unchecking the option in the main right-click menu. If the main window is visible then +** this will return 1 otherwise it returns 0. +*/ + + +typedef struct +{ + int numElems; + int *elems; + HBITMAP bm; // set if you want to override +} waSetPlColorsStruct; + +#define IPC_SETPLEDITCOLORS 920 +/* (requires Winamp 5.0+) +** This is sent by gen_ff when a modern skin is being loaded to set the colour scheme for +** the playlist editor. When sent numElems is usually 6 and matches with the 6 possible +** colours which are provided be pledit.txt from the classic skins. The elems array is +** defined as follows: +** +** elems = 0 => normal text +** elems = 1 => current text +** elems = 2 => normal background +** elems = 3 => selected background +** elems = 4 => minibroswer foreground +** elems = 5 => minibroswer background +** +** if(uMsg == WM_WA_IPC && lParam == IPC_SETPLEDITCOLORS) +** { +** waSetPlColorsStruct* colStr = (waSetPlColorsStruct*)wp; +** if(colStr) +** { +** // set or inspect the colours being used (basically for gen_ff's benefit) +** } +** } +*/ + + +typedef struct +{ + HWND wnd; + int xpos; // in screen coordinates + int ypos; +} waSpawnMenuParms; + +// waSpawnMenuParms2 is used by the menubar submenus +typedef struct +{ + HWND wnd; + int xpos; // in screen coordinates + int ypos; + int width; + int height; +} waSpawnMenuParms2; + +// the following IPC use waSpawnMenuParms as parameter +#define IPC_SPAWNEQPRESETMENU 933 +#define IPC_SPAWNFILEMENU 934 //menubar +#define IPC_SPAWNOPTIONSMENU 935 //menubar +#define IPC_SPAWNWINDOWSMENU 936 //menubar +#define IPC_SPAWNHELPMENU 937 //menubar +#define IPC_SPAWNPLAYMENU 938 //menubar +#define IPC_SPAWNPEFILEMENU 939 //menubar +#define IPC_SPAWNPEPLAYLISTMENU 940 //menubar +#define IPC_SPAWNPESORTMENU 941 //menubar +#define IPC_SPAWNPEHELPMENU 942 //menubar +#define IPC_SPAWNMLFILEMENU 943 //menubar +#define IPC_SPAWNMLVIEWMENU 944 //menubar +#define IPC_SPAWNMLHELPMENU 945 //menubar +#define IPC_SPAWNPELISTOFPLAYLISTS 946 + + +#define WM_WA_SYSTRAY WM_USER+1 +/* This is sent by the system tray when an event happens (you might want to simulate it). +** +** if(uMsg == WM_WA_SYSTRAY) +** { +** switch(lParam) +** { +** // process the messages sent from the tray +** } +** } +*/ + + +#define WM_WA_MPEG_EOF WM_USER+2 +/* Input plugins send this when they are done playing back the current file to inform +** Winamp or anyother installed plugins that the current +** +** if(uMsg == WM_WA_MPEG_EOF) +** { +** // do what is needed here +** } +*/ + + +//// video stuff + +#define IPC_IS_PLAYING_VIDEO 501 // returns >1 if playing, 0 if not, 1 if old version (so who knows):) +#define IPC_GET_IVIDEOOUTPUT 500 // see below for IVideoOutput interface +#define VIDEO_MAKETYPE(A,B,C,D) ((A) | ((B)<<8) | ((C)<<16) | ((D)<<24)) +#define VIDUSER_SET_INFOSTRING 0x1000 +#define VIDUSER_GET_VIDEOHWND 0x1001 +#define VIDUSER_SET_VFLIP 0x1002 +#define VIDUSER_SET_TRACKSELINTERFACE 0x1003 // give your ITrackSelector interface as param2 +#define VIDUSER_OPENVIDEORENDERER 0x1004 +#define VIDUSER_CLOSEVIDEORENDERER 0x1005 +#define VIDUSER_GETPOPUPMENU 0x1006 +#define VIDUSER_SET_INFOSTRINGW 0x1007 + +typedef struct +{ + int w; + int h; + int vflip; + double aspectratio; + unsigned int fmt; +} VideoOpenStruct; + +#ifndef NO_IVIDEO_DECLARE +#ifdef __cplusplus + +class VideoOutput; +class SubsItem; + +#ifndef _NSV_DEC_IF_H_ +struct YV12_PLANE { + unsigned char* baseAddr; + long rowBytes; +} ; + +struct YV12_PLANES { + YV12_PLANE y; + YV12_PLANE u; + YV12_PLANE v; +}; +#endif + +class IVideoOutput +{ + public: + virtual ~IVideoOutput() { } + virtual int open(int w, int h, int vflip, double aspectratio, unsigned int fmt)=0; + virtual void setcallback(LRESULT (*msgcallback)(void *token, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam), void *token) { (void)token; (void)msgcallback; /* to eliminate warning C4100 */ } + virtual void close()=0; + virtual void draw(void *frame)=0; + virtual void drawSubtitle(SubsItem *item) {UNREFERENCED_PARAMETER(item); } + virtual void showStatusMsg(const char *text) {UNREFERENCED_PARAMETER(text); } + virtual int get_latency() { return 0; } + virtual void notifyBufferState(int bufferstate) { UNREFERENCED_PARAMETER(bufferstate); } /* 0-255*/ + virtual INT_PTR extended(INT_PTR param1, INT_PTR param2, INT_PTR param3) { UNREFERENCED_PARAMETER(param1); UNREFERENCED_PARAMETER(param2); UNREFERENCED_PARAMETER(param3); return 0; } // Dispatchable, eat this! +}; + +class ITrackSelector +{ + public: + virtual int getNumAudioTracks()=0; + virtual void enumAudioTrackName(int n, const char *buf, int size)=0; + virtual int getCurAudioTrack()=0; + virtual int getNumVideoTracks()=0; + virtual void enumVideoTrackName(int n, const char *buf, int size)=0; + virtual int getCurVideoTrack()=0; + + virtual void setAudioTrack(int n)=0; + virtual void setVideoTrack(int n)=0; +}; + +#endif //cplusplus +#endif//NO_IVIDEO_DECLARE + +// these messages are callbacks that you can grab by subclassing the winamp window + +// wParam = +#define IPC_CB_WND_EQ 0 // use one of these for the param +#define IPC_CB_WND_PE 1 +#define IPC_CB_WND_MB 2 +#define IPC_CB_WND_VIDEO 3 +#define IPC_CB_WND_MAIN 4 + +#define IPC_CB_ONSHOWWND 600 +#define IPC_CB_ONHIDEWND 601 + +#define IPC_CB_GETTOOLTIP 602 + +#define IPC_CB_MISC 603 + #define IPC_CB_MISC_TITLE 0 // start of playing/stop/pause + #define IPC_CB_MISC_VOLUME 1 // volume/pan + #define IPC_CB_MISC_STATUS 2 // start playing/stop/pause/ffwd/rwd + #define IPC_CB_MISC_EQ 3 + #define IPC_CB_MISC_INFO 4 + #define IPC_CB_MISC_VIDEOINFO 5 + #define IPC_CB_MISC_TITLE_RATING 6 // (5.5+ for when the rating is changed via the songticker menu on current file) + +/* Example of using IPC_CB_MISC_STATUS to detect the start of track playback with 5.x +** +** if(lParam == IPC_CB_MISC && wParam == IPC_CB_MISC_STATUS) +** { +** if(SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_ISPLAYING) == 1 && +** !SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETOUTPUTTIME)) +** { +** char* file = (char*)SendMessage(hwnd_winamp,WM_WA_IPC, +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLISTPOS),IPC_GETPLAYLISTFILE); +** // only output if a valid file was found +** if(file) +** { +** MessageBox(hwnd_winamp,file,"starting",0); +** // or do something else that you need to do +** } +** } +** } +*/ + + +#define IPC_CB_CONVERT_STATUS 604 // param value goes from 0 to 100 (percent) +#define IPC_CB_CONVERT_DONE 605 + + +#define IPC_ADJUST_FFWINDOWSMENUPOS 606 +/* (requires Winamp 2.9+) +** int newpos=SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)adjust_offset,IPC_ADJUST_FFWINDOWSMENUPOS); +** This will move where Winamp expects the freeform windows in the menubar windows main +** menu. This is useful if you wish to insert a menu item above extra freeform windows. +*/ + + +#define IPC_ISDOUBLESIZE 608 +/* (requires Winamp 5.0+) +** int dsize=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_ISDOUBLESIZE); +** You send this to Winamp to query if the double size mode is enabled or not. +** If it is on then this will return 1 otherwise it will return 0. +*/ + + +#define IPC_ADJUST_FFOPTIONSMENUPOS 609 +/* (requires Winamp 2.9+) +** int newpos=SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)adjust_offset,IPC_ADJUST_FFOPTIONSMENUPOS); +** moves where winamp expects the freeform preferences item in the menubar windows main +** menu. This is useful if you wish to insert a menu item above the preferences item. +** +** Note: This setting was ignored by gen_ff until it was fixed in 5.1 +** gen_ff would assume thatthe menu position was 11 in all cases and so when you +** had two plugins attempting to add entries into the main right click menu it +** would cause the 'colour themes' submenu to either be incorrectly duplicated or +** to just disappear.instead. +*/ + + +#define IPC_GETTIMEDISPLAYMODE 610 +/* (requires Winamp 5.0+) +** int mode=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETTIMEDISPLAYMODE); +** This will return the status of the time display i.e. shows time elapsed or remaining. +** This returns 0 if Winamp is displaying time elapsed or 1 for the time remaining. +*/ + + +#define IPC_SETVISWND 611 +/* (requires Winamp 5.0+) +** int viswnd=(HWND)SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(HWND)viswnd,IPC_SETVISWND); +** This allows you to set a window to receive the following message commands (which are +** used as part of the modern skin integration). +** When you have finished or your visualisation is closed then send wParam as zero to +** ensure that things are correctly tidied up. +*/ + +/* The following messages are received as the LOWORD(wParam) of the WM_COMMAND message. +** See %SDK%\winamp\wa5vis.txt for more info about visualisation integration in Winamp. +*/ +#define ID_VIS_NEXT 40382 +#define ID_VIS_PREV 40383 +#define ID_VIS_RANDOM 40384 +#define ID_VIS_FS 40389 +#define ID_VIS_CFG 40390 +#define ID_VIS_MENU 40391 + + +#define IPC_GETVISWND 612 +/* (requires Winamp 5.0+) +** int viswnd=(HWND)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETVISWND); +** This returns a HWND to the visualisation command handler window if set by IPC_SETVISWND. +*/ + + +#define IPC_ISVISRUNNING 613 +/* (requires Winamp 5.0+) +** int visrunning=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_ISVISRUNNING); +** This will return 1 if a visualisation is currently running and 0 if one is not running. +*/ + + +#define IPC_CB_VISRANDOM 628 // param is status of random + + +#define IPC_SETIDEALVIDEOSIZE 614 +/* (requires Winamp 5.0+) +** This is sent by Winamp back to itself so it can be trapped and adjusted as needed with +** the desired width in HIWORD(wParam) and the desired height in LOWORD(wParam). +** +** if(uMsg == WM_WA_IPC){ +** if(lParam == IPC_SETIDEALVIDEOSIZE){ +** wParam = MAKEWPARAM(height,width); +** } +** } +*/ + + +#define IPC_GETSTOPONVIDEOCLOSE 615 +/* (requires Winamp 5.0+) +** int sovc=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETSTOPONVIDEOCLOSE); +** This will return 1 if 'stop on video close' is enabled and 0 if it is disabled. +*/ + + +#define IPC_SETSTOPONVIDEOCLOSE 616 +/* (requires Winamp 5.0+) +** int sovc=SendMessage(hwnd_winamp,WM_WA_IPC,enabled,IPC_SETSTOPONVIDEOCLOSE); +** Set enabled to 1 to enable and 0 to disable the 'stop on video close' option. +*/ + + +typedef struct { + HWND hwnd; + int uMsg; + WPARAM wParam; + LPARAM lParam; +} transAccelStruct; + +#define IPC_TRANSLATEACCELERATOR 617 +/* (requires Winamp 5.0+) +** (deprecated as of 5.53x+) +*/ + +typedef struct { + int cmd; + int x; + int y; + int align; +} windowCommand; // send this as param to an IPC_PLCMD, IPC_MBCMD, IPC_VIDCMD + + +#define IPC_CB_ONTOGGLEAOT 618 + + +#define IPC_GETPREFSWND 619 +/* (requires Winamp 5.0+) +** HWND prefs = (HWND)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETPREFSWND); +** This will return a handle to the preferences dialog if it is open otherwise it will +** return zero. A simple check with the OS api IsWindow(..) is a good test if it's valid. +** +** e.g. this will open (or close if already open) the preferences dialog and show if we +** managed to get a valid +** SendMessage(hwnd_winamp,WM_COMMAND,MAKEWPARAM(WINAMP_OPTIONS_PREFS,0),0); +** MessageBox(hwnd_winamp,(IsWindow((HWND)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETPREFSWND))?"Valid":"Not Open"),0,MB_OK); +*/ + + +#define IPC_SET_PE_WIDTHHEIGHT 620 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&point,IPC_SET_PE_WIDTHHEIGHT); +** You pass a pointer to a POINT structure which holds the width and height and Winamp +** will set the playlist editor to that size (this is used by gen_ff on skin changes). +** There does not appear to be any bounds limiting with this so it is possible to create +** a zero size playlist editor window (which is a pretty silly thing to do). +*/ + + +#define IPC_GETLANGUAGEPACKINSTANCE 621 +/* (requires Winamp 5.0+) +** HINSTANCE hInst = (HINSTANCE)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLANGUAGEPACKINSTANCE); +** This will return the HINSTANCE to the currently used language pack file for winamp.exe +** +** (5.5+) +** If you pass 1 in wParam then you will have zero returned if a language pack is in use. +** if(!SendMessage(hwnd_winamp,WM_WA_IPC,1,IPC_GETLANGUAGEPACKINSTANCE)){ +** // winamp is currently using a language pack +** } +** +** If you pass 2 in wParam then you will get the path to the language pack folder. +** wchar_t* lngpackfolder = (wchar_t*)SendMessage(hwnd_winamp,WM_WA_IPC,2,IPC_GETLANGUAGEPACKINSTANCE); +** +** If you pass 3 in wParam then you will get the path to the currently extracted language pack. +** wchar_t* lngpack = (wchar_t*)SendMessage(hwnd_winamp,WM_WA_IPC,3,IPC_GETLANGUAGEPACKINSTANCE); +** +** If you pass 4 in wParam then you will get the name of the currently used language pack. +** wchar_t* lngname = (char*)SendMessage(hwnd_winamp,WM_WA_IPC,4,IPC_GETLANGUAGEPACKINSTANCE); +*/ +#define LANG_IDENT_STR 0 +#define LANG_LANG_CODE 1 +#define LANG_COUNTRY_CODE 2 +/* +** (5.51+) +** If you pass 5 in LOWORD(wParam) then you will get the ident string/code string +** (based on the param passed in the HIWORD(wParam) of the currently used language pack. +** The string returned with LANG_IDENT_STR is used to represent the language that the +** language pack is intended for following ISO naming conventions for consistancy. +** +** wchar_t* ident_str = (wchar_t*)SendMessage(hwnd_winamp,WM_WA_IPC,MAKEWPARAM(5,LANG_XXX),IPC_GETLANGUAGEPACKINSTANCE); +** +** e.g. +** For the default language it will return the following for the different LANG_XXX codes +** LANG_IDENT_STR -> "en-US" (max buffer size of this is 9 wchar_t) +** LANG_LANG_CODE -> "en" (language code) +** LANG_COUNTRY_CODE -> "US" (country code) +** +** On pre 5.51 installs you can get LANG_IDENT_STR using the following method +** (you'll have to custom process the string returned if you want the langugage or country but that's easy ;) ) +** +** #define LANG_PACK_LANG_ID 65534 (if you don't have lang.h) +** HINSTANCE hInst = (HINSTANCE)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLANGUAGEPACKINSTANCE); +** TCHAR buffer[9] = {0}; +** LoadString(hInst,LANG_PACK_LANG_ID,buffer,sizeof(buffer)); +** +** +** +** The following example shows how using the basic api will allow you to load the playlist +** context menu resource from the currently loaded language pack or it will fallback to +** the default winamp.exe instance. +** +** HINSTANCE lang = (HINSTANCE)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLANGUAGEPACKINSTANCE); +** HMENU popup = GetSubMenu(GetSubMenu((LoadMenu(lang?lang:GetModuleHandle(0),MAKEINTRESOURCE(101))),2),5); +** // do processing as needed on the menu before displaying it +** TrackPopupMenuEx(orig,TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_RIGHTBUTTON,rc.left,rc.bottom,hwnd_owner,0); +** DestroyMenu(popup); +** +** If you need a specific menu handle then look at IPC_GET_HMENU for more information. +*/ + + +#define IPC_CB_PEINFOTEXT 622 // data is a string, ie: "04:21/45:02" + + +#define IPC_CB_OUTPUTCHANGED 623 // output plugin was changed in config + + +#define IPC_GETOUTPUTPLUGIN 625 +/* (requires Winamp 5.0+) +** char* outdll = (char*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETOUTPUTPLUGIN); +** This returns a string of the current output plugin's dll name. +** e.g. if the directsound plugin was selected then this would return 'out_ds.dll'. +*/ + + +#define IPC_SETDRAWBORDERS 626 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,enabled,IPC_SETDRAWBORDERS); +** Set enabled to 1 to enable and 0 to disable drawing of the playlist editor and winamp +** gen class windows (used by gen_ff to allow it to draw its own window borders). +*/ + + +#define IPC_DISABLESKINCURSORS 627 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,enabled,IPC_DISABLESKINCURSORS); +** Set enabled to 1 to enable and 0 to disable the use of skinned cursors. +*/ + + +#define IPC_GETSKINCURSORS 628 +/* (requires Winamp 5.36+) +** data = (WACURSOR)cursorId. (check wa_dlg.h for values) +*/ + + +#define IPC_CB_RESETFONT 629 + + +#define IPC_IS_FULLSCREEN 630 +/* (requires Winamp 5.0+) +** int val=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_IS_FULLSCREEN); +** This will return 1 if the video or visualisation is in fullscreen mode or 0 otherwise. +*/ + + +#define IPC_SET_VIS_FS_FLAG 631 +/* (requires Winamp 5.0+) +** A vis should send this message with 1/as param to notify winamp that it has gone to or has come back from fullscreen mode +*/ + + +#define IPC_SHOW_NOTIFICATION 632 + + +#define IPC_GETSKININFO 633 +#define IPC_GETSKININFOW 1633 +/* (requires Winamp 5.0+) +** This is a notification message sent to the main Winamp window by itself whenever it +** needs to get information to be shown about the current skin in the 'Current skin +** information' box on the main Skins page in the Winamp preferences. +** +** When this notification is received and the current skin is one you are providing the +** support for then you return a valid buffer for Winamp to be able to read from with +** information about it such as the name of the skin file. +** +** if(uMsg == WM_WA_IPC && lParam == IPC_GETSKININFO){ +** if(is_our_skin()){ +** return is_our_skin_name(); +** } +** } +*/ + + +#define IPC_GET_MANUALPLADVANCE 634 +/* (requires Winamp 5.03+) +** int val=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GET_MANUALPLADVANCE); +** IPC_GET_MANUALPLADVANCE returns the status of the Manual Playlist Advance. +** If enabled this will return 1 otherwise it will return 0. +*/ + + +#define IPC_SET_MANUALPLADVANCE 635 +/* (requires Winamp 5.03+) +** SendMessage(hwnd_winamp,WM_WA_IPC,value,IPC_SET_MANUALPLADVANCE); +** IPC_SET_MANUALPLADVANCE sets the status of the Manual Playlist Advance option. +** Set value = 1 to turn it on and value = 0 to turn it off. +*/ + + +#define IPC_GET_NEXT_PLITEM 636 +/* (requires Winamp 5.04+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_EOF_GET_NEXT_PLITEM); +** +** Sent to Winamp's main window when an item has just finished playback or the next +** button has been pressed and requesting the new playlist item number to go to. +** +** Subclass this message in your application to return the new item number. +** Return -1 for normal Winamp operation (default) or the new item number in +** the playlist to be played instead of the originally selected next track. +*/ + + +#define IPC_GET_PREVIOUS_PLITEM 637 +/* (requires Winamp 5.04+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_EOF_GET_PREVIOUS_PLITEM); +** +** Sent to Winamp's main window when the previous button has been pressed and Winamp is +** requesting the new playlist item number to go to. +** +** Return -1 for normal Winamp operation (default) or the new item number in +** the playlist to be played instead of the originally selected previous track. +** +** This is primarily provided for the JTFE plugin (gen_jumpex.dll). +*/ + + +#define IPC_IS_WNDSHADE 638 +/* (requires Winamp 5.04+) +** int is_shaded=SendMessage(hwnd_winamp,WM_WA_IPC,wnd,IPC_IS_WNDSHADE); +** Pass 'wnd' as an id as defined for IPC_GETWND or pass -1 to query the status of the +** main window. This returns 1 if the window is in winshade mode and 0 if it is not. +** Make sure you only test for this on a 5.04+ install otherwise you get a false result. +** (See the notes about unhandled WM_WA_IPC messages). +*/ + + +#define IPC_SETRATING 639 +/* (requires Winamp 5.04+ with ML) +** int rating=SendMessage(hwnd_winamp,WM_WA_IPC,rating,IPC_SETRATING); +** This will allow you to set the 'rating' on the current playlist entry where 'rating' +** is an integer value from 0 (no rating) to 5 (5 stars). +** +** The following example should correctly allow you to set the rating for any specified +** playlist entry assuming of course that you're trying to get a valid playlist entry. +** +** void SetPlaylistItemRating(int item_to_set, int rating_to_set){ +** int cur_pos=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLISTPOS); +** SendMessage(hwnd_winamp,WM_WA_IPC,item_to_set,IPC_SETPLAYLISTPOS); +** SendMessage(hwnd_winamp,WM_WA_IPC,rating_to_set,IPC_SETRATING); +** SendMessage(hwnd_winamp,WM_WA_IPC,cur_pos,IPC_SETPLAYLISTPOS); +** } +*/ + + +#define IPC_GETRATING 640 +/* (requires Winamp 5.04+ with ML) +** int rating=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETRATING); +** This returns the current playlist entry's rating between 0 (no rating) to 5 (5 stars). +** +** The following example should correctly allow you to get the rating for any specified +** playlist entry assuming of course that you're trying to get a valid playlist entry. +** +** int GetPlaylistItemRating(int item_to_get, int rating_to_set){ +** int cur_pos=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLISTPOS), rating = 0; +** SendMessage(hwnd_winamp,WM_WA_IPC,item_to_get,IPC_SETPLAYLISTPOS); +** rating = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETRATING); +** SendMessage(hwnd_winamp,WM_WA_IPC,cur_pos,IPC_SETPLAYLISTPOS); +** return rating; +** } +*/ + + +#define IPC_GETNUMAUDIOTRACKS 641 +/* (requires Winamp 5.04+) +** int n = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETNUMAUDIOTRACKS); +** This will return the number of audio tracks available from the currently playing item. +*/ + + +#define IPC_GETNUMVIDEOTRACKS 642 +/* (requires Winamp 5.04+) +** int n = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETNUMVIDEOTRACKS); +** This will return the number of video tracks available from the currently playing item. +*/ + + +#define IPC_GETAUDIOTRACK 643 +/* (requires Winamp 5.04+) +** int cur = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETAUDIOTRACK); +** This will return the id of the current audio track for the currently playing item. +*/ + + +#define IPC_GETVIDEOTRACK 644 +/* (requires Winamp 5.04+) +** int cur = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETVIDEOTRACK); +** This will return the id of the current video track for the currently playing item. +*/ + + +#define IPC_SETAUDIOTRACK 645 +/* (requires Winamp 5.04+) +** SendMessage(hwnd_winamp,WM_WA_IPC,track,IPC_SETAUDIOTRACK); +** This allows you to switch to a new audio track (if supported) in the current playing file. +*/ + + +#define IPC_SETVIDEOTRACK 646 +/* (requires Winamp 5.04+) +** SendMessage(hwnd_winamp,WM_WA_IPC,track,IPC_SETVIDEOTRACK); +** This allows you to switch to a new video track (if supported) in the current playing file. +*/ + + +#define IPC_PUSH_DISABLE_EXIT 647 +/* (requires Winamp 5.04+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_PUSH_DISABLE_EXIT); +** This will let you disable or re-enable the UI exit functions (close button, context +** menu, alt-f4). Remember to call IPC_POP_DISABLE_EXIT when you are done doing whatever +** was required that needed to prevent exit otherwise you have to kill the Winamp process. +*/ + + +#define IPC_POP_DISABLE_EXIT 648 +/* (requires Winamp 5.04+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_POP_DISABLE_EXIT); +** See IPC_PUSH_DISABLE_EXIT +*/ + + +#define IPC_IS_EXIT_ENABLED 649 +/* (requires Winamp 5.04+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_IS_EXIT_ENABLED); +** This will return 0 if the 'exit' option of Winamp's menu is disabled and 1 otherwise. +*/ + + +#define IPC_IS_AOT 650 +/* (requires Winamp 5.04+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_IS_AOT); +** This will return the status of the always on top flag. +** Note: This may not match the actual TOPMOST window flag while another fullscreen +** application is focused if the user has the 'Disable always on top while fullscreen +** applications are focused' option under the General Preferences page is checked. +*/ + + +#define IPC_USES_RECYCLEBIN 651 +/* (requires Winamp 5.09+) +** int use_bin=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_USES_RECYCLEBIN); +** This will return 1 if the deleted file should be sent to the recycle bin or +** 0 if deleted files should be deleted permanently (default action for < 5.09). +** +** Note: if you use this on pre 5.09 installs of Winamp then it will return 1 which is +** not correct but is due to the way that SendMessage(..) handles un-processed messages. +** Below is a quick case for checking if the returned value is correct. +** +** if(SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_USES_RECYCLEBIN) && +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETVERSION)>=0x5009) +** { +** // can safely follow the option to recycle the file +** } +** else +* { +** // need to do a permanent delete of the file +** } +*/ + + +#define IPC_FLUSHAUDITS 652 +/* +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_FLUSHAUDITS); +** +** Will flush any pending audits in the global audits queue +** +*/ + +#define IPC_GETPLAYITEM_START 653 +#define IPC_GETPLAYITEM_END 654 + + +#define IPC_GETVIDEORESIZE 655 +#define IPC_SETVIDEORESIZE 656 + + +#define IPC_INITIAL_SHOW_STATE 657 +/* (requires Winamp 5.36+) +** int show_state = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_INITIAL_SHOW_STATE); +** returns the processed value of nCmdShow when Winamp was started +** (see MSDN documentation the values passed to WinMain(..) for what this should be) +** +** e.g. +** if(SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_INITIAL_SHOW_STATE) == SW_SHOWMINIMIZED){ +** // we are starting minimised so process as needed (keep our window hidden) +** } +** +** Useful for seeing if winamp was run minimised on startup so you can act accordingly. +** On pre-5.36 versions this will effectively return SW_NORMAL/SW_SHOWNORMAL due to the +** handling of unknown apis returning 1 from Winamp. +*/ + +// >>>>>>>>>>> Next is 658 + +#define IPC_PLCMD 1000 + +#define PLCMD_ADD 0 +#define PLCMD_REM 1 +#define PLCMD_SEL 2 +#define PLCMD_MISC 3 +#define PLCMD_LIST 4 + +//#define IPC_MBCMD 1001 + +#define MBCMD_BACK 0 +#define MBCMD_FORWARD 1 +#define MBCMD_STOP 2 +#define MBCMD_RELOAD 3 +#define MBCMD_MISC 4 + +#define IPC_VIDCMD 1002 + +#define VIDCMD_FULLSCREEN 0 +#define VIDCMD_1X 1 +#define VIDCMD_2X 2 +#define VIDCMD_LIB 3 +#define VIDPOPUP_MISC 4 + +//#define IPC_MBURL 1003 //sets the URL +//#define IPC_MBGETCURURL 1004 //copies the current URL into wParam (have a 4096 buffer ready) +//#define IPC_MBGETDESC 1005 //copies the current URL description into wParam (have a 4096 buffer ready) +//#define IPC_MBCHECKLOCFILE 1006 //checks that the link file is up to date (otherwise updates it). wParam=parent HWND +//#define IPC_MBREFRESH 1007 //refreshes the "now playing" view in the library +//#define IPC_MBGETDEFURL 1008 //copies the default URL into wParam (have a 4096 buffer ready) + +#define IPC_STATS_LIBRARY_ITEMCNT 1300 // updates library count status + +/* +** IPC's in the message range 2000 - 3000 are reserved internally for freeform messages. +** These messages are taken from ff_ipc.h which is part of the Modern skin integration. +*/ + +#define IPC_FF_FIRST 2000 + +#define IPC_FF_COLOURTHEME_CHANGE IPC_FF_ONCOLORTHEMECHANGED +#define IPC_FF_ONCOLORTHEMECHANGED IPC_FF_FIRST + 3 +/* +** This is a notification message sent when the user changes the colour theme in a Modern +** skin and can also be detected when the Modern skin is first loaded as the gen_ff plugin +** applies relevant settings and styles (like the colour theme). +** +** The value of wParam is the name of the new color theme being switched to. +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(const char*)colour_theme_name,IPC_FF_ONCOLORTHEMECHANGED); +** +** (IPC_FF_COLOURTHEME_CHANGE is the name i (DrO) was using before getting a copy of +** ff_ipc.h with the proper name in it). +*/ + + +#define IPC_FF_ISMAINWND IPC_FF_FIRST + 4 +/* +** int ismainwnd = (HWND)SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(HWND)test_wnd,IPC_FF_ISMAINWND); +** +** This allows you to determine if the window handle passed to it is a modern skin main +** window or not. If it is a main window or any of its windowshade variants then it will +** return 1. +** +** Because of the way modern skins are implemented, it is possible for this message to +** return a positive test result for a number of window handles within the current Winamp +** process. This appears to be because you can have a visible main window, a compact main +** window and also a winshaded version. +** +** The following code example below is one way of seeing how this api works since it will +** enumerate all windows related to Winamp at the time and allows you to process as +** required when a detection happens. +** +** +** EnumThreadWindows(GetCurrentThreadId(),enumWndProc,0); +** +** BOOL CALLBACK enumWndProc(HWND hwnd, LPARAM lParam){ +** +** if(SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)hwnd,IPC_FF_ISMAINWND)){ +** // do processing in here +** // or continue the enum for other main windows (if they exist) +** // and just comment out the line below +** return 0; +** } +** return 1; +** } +*/ + + +#define IPC_FF_GETCONTENTWND IPC_FF_FIRST + 5 +/* +** HWND wa2embed = (HWND)SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(HWND)test_wnd,IPC_FF_GETCONTENTWND); +** +** This will return the Winamp 2 window that is embedded in the window's container +** i.e. if hwnd is the playlist editor windowshade hwnd then it will return the Winamp 2 +** playlist editor hwnd. +** +** If no content is found such as the window has nothing embedded then this will return +** the hwnd passed to it. +*/ + + +#define IPC_FF_NOTIFYHOTKEY IPC_FF_FIRST + 6 +/* +** This is a notification message sent when the user uses a global hotkey combination +** which had been registered with the gen_hotkeys plugin. +** +** The value of wParam is the description of the hotkey as passed to gen_hotkeys. +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(const char*)hotkey_desc,IPC_FF_NOTIFYHOTKEY); +*/ + +#define IPC_FF_LAST 3000 + + +/* +** General IPC messages in Winamp +** +** All notification messages appear in the lParam of the main window message proceedure. +*/ + + +#define IPC_GETDROPTARGET 3001 +/* (requires Winamp 5.0+) +** IDropTarget* IDrop = (IDropTarget*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETDROPTARGET); +** +** You call this to retrieve a copy of the IDropTarget interface which Winamp created for +** handling external drag and drop operations on to it's Windows. This is only really +** useful if you're providing an alternate interface and want your Windows to provide the +** same drag and drop support as Winamp normally provides the user. Check out MSDN or +** your prefered search facility for more information about the IDropTarget interface and +** what's needed to handle it in your own instance. +*/ + + +#define IPC_PLAYLIST_MODIFIED 3002 +/* (requires Winamp 5.0+) +** This is a notification message sent to the main Winamp window whenever the playlist is +** modified in any way e.g. the addition/removal of a playlist entry. +** +** It is not a good idea to do large amounts of processing in this notification since it +** will slow down Winamp as playlist entries are modified (especially when you're adding +** in a large playlist). +** +** if(uMsg == WM_WA_IPC && lParam == IPC_PLAYLIST_MODIFIED) +** { +** // do what you need to do here +** } +*/ + + +#define IPC_PLAYING_FILE 3003 +/* (requires Winamp 5.0+) +** This is a notification message sent to the main Winamp window when playback begins for +** a file. This passes the full filepath in the wParam of the message received. +** +** if(uMsg == WM_WA_IPC && lParam == IPC_PLAYING_FILE) +** { +** // do what you need to do here, e.g. +** process_file((char*)wParam); +** } +*/ + + +#define IPC_PLAYING_FILEW 13003 +/* (requires Winamp 5.0+) +** This is a notification message sent to the main Winamp window when playback begins for +** a file. This passes the full filepath in the wParam of the message received. +** +** if(uMsg == WM_WA_IPC && lParam == IPC_PLAYING_FILEW) +** { +** // do what you need to do here, e.g. +** process_file((wchar_t*)wParam); +** } +*/ + + +#define IPC_FILE_TAG_MAY_HAVE_UPDATED 3004 +#define IPC_FILE_TAG_MAY_HAVE_UPDATEDW 3005 +/* (requires Winamp 5.0+) +** This is a notification message sent to the main Winamp window when a file's tag +** (e.g. id3) may have been updated. This appears to be sent when the InfoBox(..) function +** of the associated input plugin returns a 1 (which is the file information dialog/editor +** call normally). +** +** if(uMsg == WM_WA_IPC && lParam == IPC_FILE_TAG_MAY_HAVE_UPDATED) +** { +** // do what you need to do here, e.g. +** update_info_on_file_update((char*)wParam); +** } +*/ + + +#define IPC_ALLOW_PLAYTRACKING 3007 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,allow,IPC_ALLOW_PLAYTRACKING); +** Send allow as nonzero to allow play tracking and zero to disable the mode. +*/ + + +#define IPC_HOOK_OKTOQUIT 3010 +/* (requires Winamp 5.0+) +** This is a notification message sent to the main Winamp window asking if it's okay to +** close or not. Return zero to prevent Winamp from closing or return anything non-zero +** to allow Winamp to close. +** +** The best implementation of this option is to let the message pass through to the +** original window proceedure since another plugin may want to have a say in the matter +** with regards to Winamp closing. +** +** if(uMsg == WM_WA_IPC && lParam == IPC_HOOK_OKTOQUIT) +** { +** // do what you need to do here, e.g. +** if(no_shut_down()) +** { +** return 1; +** } +** } +*/ + + +#define IPC_WRITECONFIG 3011 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,write_type,IPC_WRITECONFIG); +** +** Send write_type as 2 to write all config settings and the current playlist. +** +** Send write_type as 1 to write the playlist and common settings. +** This won't save the following ini settings:: +** +** defext, titlefmt, proxy, visplugin_name, dspplugin_name, check_ft_startup, +** visplugin_num, pe_fontsize, visplugin_priority, visplugin_autoexec, dspplugin_num, +** sticon, splash, taskbar, dropaotfs, ascb_new, ttips, riol, minst, whichicon, +** whichicon2, addtolist, snap, snaplen, parent, hilite, disvis, rofiob, shownumsinpl, +** keeponscreen, eqdsize, usecursors, fixtitles, priority, shuffle_morph_rate, +** useexttitles, bifont, inet_mode, ospb, embedwnd_freesize, no_visseh +** (the above was valid for 5.1) +** +** Send write_type as 0 to write the common and less common settings and no playlist. +*/ + + +#define IPC_UPDATE_URL 3012 +// pass the URL (char *) in lparam, will be free()'d on done. + + +#define IPC_GET_RANDFUNC 3015 // returns a function to get a random number +/* (requires Winamp 5.1+) +** int (*randfunc)(void) = (int(*)(void))SendMessage(plugin.hwndParent,WM_WA_IPC,0,IPC_GET_RANDFUNC); +** if(randfunc && randfunc != 1){ +** randfunc(); +** } +** +** This will return a positive 32-bit number (essentially 31-bit). +** The check for a returned value of 1 is because that's the default return value from +** SendMessage(..) when it is not handled so is good to check for it in this situation. +*/ + + +#define IPC_METADATA_CHANGED 3017 +/* (requires Winamp 5.1+) +** int changed=SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(char*)field,IPC_METADATA_CHANGED); +** a plugin can SendMessage this to winamp if internal metadata has changes. +** wParam should be a char * of what field changed +** +** Currently used for internal actions (and very few at that) the intent of this api is +** to allow a plugin to call it when metadata has changed in the current playlist entry +** e.g.a new id3v2 tag was found in a stream +** +** The wparam value can either be NULL or a pointer to an ansi string for the metadata +** which has changed. This can be thought of as an advanced version of IPC_UPDTITLE and +** could be used to allow for update of the current title when a specific tag changed. +** +** Not recommended to be used since there is not the complete handling implemented in +** Winamp for it at the moment. +*/ + + +#define IPC_SKIN_CHANGED 3018 +/* (requires Winamp 5.1+) +** This is a notification message sent to the main Winamp window by itself whenever +** the skin in use is changed. There is no information sent by the wParam for this. +** +** if(message == WM_WA_IPC && lparam == IPC_SKIN_CHANGED) +** { +** // do what you need to do to handle skin changes, e.g. call WADlg_init(hwnd_winamp); +** } +*/ + + +#define IPC_REGISTER_LOWORD_COMMAND 3019 +/* (requires Winamp 5.1+) +** WORD id = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_REGISTER_LOWORD_COMMAND); +** This will assign you a unique id for making your own commands such as for extra menu +** entries. The starting value returned by this message will potentially change as and +** when the main resource file of Winamp is updated with extra data so assumptions cannot +** be made on what will be returned and plugin loading order will affect things as well. + +** 5.33+ +** If you want to reserve more than one id, you can pass the number of ids required in wParam +*/ + + +#define IPC_GET_DISPATCH_OBJECT 3020 // gets winamp main IDispatch * (for embedded webpages) +#define IPC_GET_UNIQUE_DISPATCH_ID 3021 // gives you a unique dispatch ID that won't conflict with anything in winamp's IDispatch * +#define IPC_ADD_DISPATCH_OBJECT 3022 // add your own dispatch object into winamp's. This lets embedded webpages access your functions +// pass a pointer to DispatchInfo (see below). Winamp makes a copy of all this data so you can safely delete it later +typedef struct +{ + wchar_t *name; // filled in by plugin, make sure it's a unicode string!! (i.e. L"myObject" instead of "myObject). + struct IDispatch *dispatch; // filled in by plugin + DWORD id; // filled in by winamp on return +} DispatchInfo; + +// see IPC_JSAPI2_GET_DISPATCH_OBJECT for version 2 of the Dispatchable scripting interface + +#define IPC_GET_PROXY_STRING 3023 +/* (requires Winamp 5.11+) +** char* proxy_string=(char*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GET_PROXY_STRING); +** This will return the same string as is shown on the General Preferences page. +*/ + + +#define IPC_USE_REGISTRY 3024 +/* (requires Winamp 5.11+) +** int reg_enabled=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_USE_REGISTRY); +** This will return 0 if you should leave your grubby hands off the registry (i.e. for +** lockdown mode). This is useful if Winamp is run from a USB drive and you can't alter +** system settings, etc. +*/ + + +#define IPC_GET_API_SERVICE 3025 +/* (requires Winamp 5.12+) +** api_service* api_service = (api_service)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GET_API_SERVICE); +** This api will return Winamp's api_service pointer (which is what Winamp3 used, heh). +** If this api is not supported in the Winamp version that is being used at the time then +** the returned value from this api will be 1 which is a good version check. +** +** As of 5.12 there is support for .w5s plugins which reside in %WinampDir%\System and +** are intended for common code that can be shared amongst other plugins e.g. jnetlib.w5s +** which contains jnetlib in one instance instead of being duplicated multiple times in +** all of the plugins which need internet access. +** +** Details on the .w5s specifications will come at some stage (possibly). +*/ + + +typedef struct { + const wchar_t *filename; + const wchar_t *metadata; + wchar_t *ret; + size_t retlen; +} extendedFileInfoStructW; + +#define IPC_GET_EXTENDED_FILE_INFOW 3026 +/* (requires Winamp 5.13+) +** Pass a pointer to the above struct in wParam +*/ + + +#define IPC_GET_EXTENDED_FILE_INFOW_HOOKABLE 3027 +#define IPC_SET_EXTENDED_FILE_INFOW 3028 +/* (requires Winamp 5.13+) +** Pass a pointer to the above struct in wParam +*/ + + +#define IPC_PLAYLIST_GET_NEXT_SELECTED 3029 +/* (requires 5.2+) +** int pl_item = SendMessage(hwnd_winamp,WM_WA_IPC,start,IPC_PLAYLIST_GET_NEXT_SELECTED); +** +** This works just like the ListView_GetNextItem(..) macro for ListView controls. +** 'start' is the index of the playlist item that you want to begin the search after or +** set this as -1 for the search to begin with the first item of the current playlist. +** +** This will return the index of the selected playlist according to the 'start' value or +** it returns -1 if there is no selection or no more selected items according to 'start'. +** +** Quick example: +** +** int sel = -1; +** // keep incrementing the start of the search so we get all of the selected entries +** while((sel = SendMessage(hwnd_winamp,WM_WA_IPC,sel,IPC_PLAYLIST_GET_NEXT_SELECTED))!=-1){ +** // show the playlist file entry of the selected item(s) if there were any +** MessageBox(hwnd_winamp,(char*)SendMessage(hwnd_winamp,WM_WA_IPC,sel,IPC_GETPLAYLISTFILE),0,0); +** } +*/ + + +#define IPC_PLAYLIST_GET_SELECTED_COUNT 3030 +/* (requires 5.2+) +** int selcnt = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_PLAYLIST_GET_SELECTED_COUNT); +** This will return the number of selected playlist entries. +*/ + + +#define IPC_GET_PLAYING_FILENAME 3031 +// returns wchar_t * of the currently playing filename + +#define IPC_OPEN_URL 3032 +// send either ANSI or Unicode string (it'll figure it out, but it MUST start with "h"!, so don't send ftp:// or anything funny) +// you can also send this one from another process via WM_COPYDATA (unicode only) + + +#define IPC_USE_UXTHEME_FUNC 3033 +/* (requires Winamp 5.35+) +** int ret = SendMessage(hwnd_winamp,WM_WA_IPC,param,IPC_USE_UXTHEME_FUNC); +** param can be IPC_ISWINTHEMEPRESENT or IPC_ISAEROCOMPOSITIONACTIVE or a valid hwnd. +** +** If you pass a hwnd then it will apply EnableThemeDialogTexture(ETDT_ENABLETAB) +** so your tabbed dialogs can use the correct theme (on supporting OSes ie XP+). +** +** Otherwise this will return a value based on the param passed (as defined below). +** For compatability, the return value will be zero on success (as 1 is returned +** for unsupported ipc calls on older Winamp versions) +*/ + #define IPC_ISWINTHEMEPRESENT 0 +/* This will return 0 if uxtheme.dll is present +** int isthemethere = !SendMessage(hwnd_winamp,WM_WA_IPC,IPC_ISWINTHEMEPRESENT,IPC_USE_UXTHEME_FUNC); +*/ + #define IPC_ISAEROCOMPOSITIONACTIVE 1 +/* This will return 0 if aero composition is active +** int isaero = !SendMessage(hwnd_winamp,WM_WA_IPC,IPC_ISAEROCOMPOSITIONACTIVE,IPC_USE_UXTHEME_FUNC); +*/ + + +#define IPC_GET_PLAYING_TITLE 3034 +// returns wchar_t * of the current title + + +#define IPC_CANPLAY 3035 +// pass const wchar_t *, returns an in_mod * or 0 + + +typedef struct { + // fill these in... + size_t size; // init to sizeof(artFetchData) + HWND parent; // parent window of the dialogue + + // fill as much of these in as you can, otherwise leave them 0 + const wchar_t *artist; + const wchar_t *album; + int year, amgArtistId, amgAlbumId; + + int showCancelAll; // if set to 1, this shows a "Cancel All" button on the dialogue + + // winamp will fill these in if the call returns successfully: + void* imgData; // a buffer filled with compressed image data. free with WASABI_API_MEMMGR->sysFree() + int imgDataLen; // the size of the buffer + wchar_t type[10]; // eg: "jpg" + const wchar_t *gracenoteFileId; // if you know it +} artFetchData; + +#define IPC_FETCH_ALBUMART 3036 +/* pass an artFetchData*. This will show a dialog guiding the use through choosing art, and return when it's finished +** return values: +** 1: error showing dialog +** 0: success +** -1: cancel was pressed +** -2: cancel all was pressed +*/ + +#define IPC_JSAPI2_GET_DISPATCH_OBJECT 3037 +/* pass your service's unique ID, as a wchar_t * string, in wParam +** Winamp will copy the string, so don't worry about keeping it around +** An IDispatch * object will be returned (cast the return value from SendMessage) +** This IDispatch can be used for scripting/automation/VB interaction +** Pass to IE via IDocHostUIHandler::GetExternal and it will become window.external in javscript +*/ + +#define IPC_REGISTER_WINAMP_IPCMESSAGE 65536 +/* (requires Winamp 5.0+) +** DWORD id = SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)name,IPC_REGISTER_WINAMP_IPCMESSAGE); +** The value 'id' returned is > 65536 and is incremented on subsequent calls for unique +** 'name' values passed to it. By using the same 'name' in different plugins will allow a +** common runtime api to be provided for the currently running instance of Winamp +** e.g. +** PostMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)my_param,registered_ipc); +** Have a look at wa_hotkeys.h for an example on how this api is used in practice for a +** custom WM_WA_IPC message. +** +** if(uMsg == WM_WA_IPC && lParam == id_from_register_winamp_ipcmessage){ +** // do things +** } +*/ + +#endif//_WA_IPC_H_
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/wasabi/api/memmgr/api_memmgr.h b/Src/Plugins/Input/in_wv/wasabi/api/memmgr/api_memmgr.h new file mode 100644 index 00000000..3f2448c3 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/api/memmgr/api_memmgr.h @@ -0,0 +1,78 @@ +#ifndef __API_MEMMGR_H +#define __API_MEMMGR_H + +#include "../../bfc/dispatch.h" +#include "../../bfc/platform/types.h" + +class NOVTABLE api_memmgr : public Dispatchable +{ +protected: + api_memmgr() {} + ~api_memmgr() {} + +public: + void *sysMalloc(size_t size); + void sysFree(void *ptr); + void *sysRealloc(void *ptr, size_t newsize); + void sysMemChanged(void *ptr); + + DISPATCH_CODES + { + API_MEMMGR_SYSMALLOC = 0, + API_MEMMGR_SYSFREE = 10, + API_MEMMGR_SYSREALLOC = 20, + API_MEMMGR_SYSMEMCHANGED = 30, + }; + + // Some helper templates to new and delete objects with the memory manager + // you need to be cautious with Delete() and inheritance, particularly if you're dealing with a base class + // as the pointer to the derived class might not equal to the pointer to the base class, particularly with multiple inheritance + // e.g. class C : public A, public B {}; C c; assert((A*)&c == (B*)&c); will likely fail + + template <class Class> + void New(Class **obj) + { + size_t toAlloc = sizeof(Class); + void *mem = sysMalloc(toAlloc); + *obj = new (mem) Class; + } + + template <class Class> + void Delete(Class *obj) + { + if (obj) + { + obj->~Class(); + sysFree(obj); + } + } +}; + +inline void *api_memmgr::sysMalloc(size_t size) +{ + return _call(API_MEMMGR_SYSMALLOC, (void *)NULL, size); +} + +inline void api_memmgr::sysFree(void *ptr) +{ + _voidcall(API_MEMMGR_SYSFREE, ptr); +} + +inline void *api_memmgr::sysRealloc(void *ptr, size_t newsize) +{ + return _call(API_MEMMGR_SYSREALLOC, (void *)NULL, ptr, newsize); +} + +inline void api_memmgr::sysMemChanged(void *ptr) +{ + _voidcall(API_MEMMGR_SYSMEMCHANGED, ptr); +} + + +// {000CF46E-4DF6-4a43-BBE7-40E7A3EA02ED} +static const GUID memMgrApiServiceGuid = +{ 0xcf46e, 0x4df6, 0x4a43, { 0xbb, 0xe7, 0x40, 0xe7, 0xa3, 0xea, 0x2, 0xed } }; + +//extern api_memmgr *memmgrApi; + +#endif diff --git a/Src/Plugins/Input/in_wv/wasabi/api/service/api_service.h b/Src/Plugins/Input/in_wv/wasabi/api/service/api_service.h new file mode 100644 index 00000000..44ef819d --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/api/service/api_service.h @@ -0,0 +1,155 @@ +// ---------------------------------------------------------------------------- +// Generated by InterfaceFactory [Wed May 07 00:56:11 2003] +// +// File : api_service.h +// Class : api_service +// class layer : Dispatchable Interface +// ---------------------------------------------------------------------------- + +#ifndef __API_SERVICE_H +#define __API_SERVICE_H + +#include "../../bfc/dispatch.h" +#include "../../bfc/platform/types.h" + +namespace SvcNotify { + enum { + ONREGISTERED=100, // init yourself here -- not all other services are registered yet + ONSTARTUP=200, // everyone is initialized, safe to talk to other services + ONAPPRUNNING=210, // app is showing and processing events + ONSHUTDOWN=300, // studio is shutting down, release resources from other services + ONDEREGISTERED=400, // bye bye + ONDBREADCOMPLETE=500,// after db is read in (happens asynchronously after ONSTARTUP) + ONBEFORESHUTDOWN=600, // system is about to shutdown, call WASABI_API_APP->main_cancelShutdown() to cancel + }; +} + + class waServiceFactory; + +// ---------------------------------------------------------------------------- + +class NOVTABLE api_service: public Dispatchable { + protected: + api_service() {} + ~api_service() {} + public: + int service_register(waServiceFactory *svc); + int service_deregister(waServiceFactory *svc); + size_t service_getNumServices(FOURCC svc_type); + waServiceFactory *service_enumService(FOURCC svc_type, size_t n); + waServiceFactory *service_getServiceByGuid(GUID guid); + int service_lock(waServiceFactory *owner, void *svcptr); + int service_clientLock(void *svcptr); + int service_release(void *svcptr); + const char *service_getTypeName(FOURCC svc_type); + #ifdef WASABI_COMPILE_COMPONENTS + GUID service_getOwningComponent(void *svcptr); + GUID service_getLockingComponent(void *svcptr); + #endif // WASABI_COMPILE_COMPONENTS + int service_unlock(void *svcptr); + int service_isvalid(FOURCC svctype, waServiceFactory *service); + // removes "me" from the services list and finds a second service with the same GUID and puts it in the same position + // this is used by the lazy loader service factory - you shouldn't need it for any other purposes. + // returns 0 if compaction actually happened + int service_compactDuplicates(waServiceFactory *me); + + protected: + enum { + API_SERVICE_SERVICE_REGISTER = 10, + API_SERVICE_SERVICE_DEREGISTER = 20, + API_SERVICE_SERVICE_GETNUMSERVICES = 30, + API_SERVICE_SERVICE_ENUMSERVICE = 40, + API_SERVICE_SERVICE_GETSERVICEBYGUID = 50, + API_SERVICE_SERVICE_LOCK = 60, + API_SERVICE_SERVICE_CLIENTLOCK = 70, + API_SERVICE_SERVICE_RELEASE = 80, + API_SERVICE_SERVICE_GETTYPENAME = 90, + #ifdef WASABI_COMPILE_COMPONENTS + API_SERVICE_SERVICE_GETOWNINGCOMPONENT = 100, + API_SERVICE_SERVICE_GETLOCKINGCOMPONENT = 110, + #endif // WASABI_COMPILE_COMPONENTS + API_SERVICE_SERVICE_UNLOCK = 120, + API_SERVICE_ISVALID = 130, + API_SERVICE_COMPACT_DUPLICATES = 140, + }; +}; + +// ---------------------------------------------------------------------------- + +inline int api_service::service_register(waServiceFactory *svc) { + int __retval = _call(API_SERVICE_SERVICE_REGISTER, (int)0, svc); + return __retval; +} + +inline int api_service::service_deregister(waServiceFactory *svc) { + int __retval = _call(API_SERVICE_SERVICE_DEREGISTER, (int)0, svc); + return __retval; +} + +inline size_t api_service::service_getNumServices(FOURCC svc_type) { + int __retval = _call(API_SERVICE_SERVICE_GETNUMSERVICES, (int)0, svc_type); + return __retval; +} + +inline waServiceFactory *api_service::service_enumService(FOURCC svc_type, size_t n) { + waServiceFactory *__retval = _call(API_SERVICE_SERVICE_ENUMSERVICE, (waServiceFactory *)0, svc_type, n); + return __retval; +} + +inline waServiceFactory *api_service::service_getServiceByGuid(GUID guid) { + waServiceFactory *__retval = _call(API_SERVICE_SERVICE_GETSERVICEBYGUID, (waServiceFactory *)0, guid); + return __retval; +} + +inline int api_service::service_lock(waServiceFactory *owner, void *svcptr) { + int __retval = _call(API_SERVICE_SERVICE_LOCK, (int)0, owner, svcptr); + return __retval; +} + +inline int api_service::service_clientLock(void *svcptr) { + int __retval = _call(API_SERVICE_SERVICE_CLIENTLOCK, (int)0, svcptr); + return __retval; +} + +inline int api_service::service_release(void *svcptr) { + int __retval = _call(API_SERVICE_SERVICE_RELEASE, (int)0, svcptr); + return __retval; +} + +inline const char *api_service::service_getTypeName(FOURCC svc_type) { + const char *__retval = _call(API_SERVICE_SERVICE_GETTYPENAME, (const char *)0, svc_type); + return __retval; +} + +#ifdef WASABI_COMPILE_COMPONENTS +inline GUID api_service::service_getOwningComponent(void *svcptr) { + GUID __retval = _call(API_SERVICE_SERVICE_GETOWNINGCOMPONENT, INVALID_GUID, svcptr); + return __retval; +} + +inline GUID api_service::service_getLockingComponent(void *svcptr) { + GUID __retval = _call(API_SERVICE_SERVICE_GETLOCKINGCOMPONENT, INVALID_GUID, svcptr); + return __retval; +} + +#endif // WASABI_COMPILE_COMPONENTS +inline int api_service::service_unlock(void *svcptr) { + int __retval = _call(API_SERVICE_SERVICE_UNLOCK, (int)0, svcptr); + return __retval; +} + +inline int api_service::service_isvalid(FOURCC svctype, waServiceFactory *service) { + int __retval = _call(API_SERVICE_ISVALID, (int)0, svctype, service); + return __retval; +} + +inline int api_service::service_compactDuplicates(waServiceFactory *me) +{ + return _call(API_SERVICE_COMPACT_DUPLICATES, (int)1, me); +} +// ---------------------------------------------------------------------------- + + +extern api_service *serviceApi; + +#endif // __API_SERVICE_H diff --git a/Src/Plugins/Input/in_wv/wasabi/api/service/services.h b/Src/Plugins/Input/in_wv/wasabi/api/service/services.h new file mode 100644 index 00000000..264dea66 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/api/service/services.h @@ -0,0 +1,68 @@ +#ifndef _SERVICES_H +#define _SERVICES_H + +#include <bfc/std_mkncc.h> // for MKnCC() + +// lower-case service names are reserved by Nullsoft for future use +// upper-case service names are for 3rd parties to extend the service system + +// if you have a service that is unique to a component, make it type +// UNIQUE and register it by GUID + + +namespace WaSvc { + enum { + NONE=MK4CC('n','o','n','e'), + UNIQUE=MK4CC('u','n','i','q'), // for unique services, enumed by GUID + OBJECT=MK4CC('o','b','j','f'), // for unique objects, enumed by GUID + CONTEXTCMD=MK4CC('c','c','m','d'), // context menu command svc_contextCmd.h + DEVICE=MK3CC('d','e','v'), // portable device svc_device.h + FILEREADER=MK4CC('f','s','r','d'), // file system reader (disk, zip, http) + FILESELECTOR=MK4CC('f','s','e','l'), // file selector svc_filesel.h + STORAGEVOLENUM=MK4CC('f','s','e','n'), // storage volume enumerator. + IMAGEGENERATOR=MK4CC('i','m','g','n'), // image generator svc_imggen.h + IMAGELOADER=MK4CC('i','m','g','l'), // image loader svc_imgload.h + IMAGEWRITER=MK4CC('i','m','g','w'), // image writer + ITEMMANAGER=MK4CC('i','m','g','r'), // item manager svc_itemmgr.h + PLAYLISTREADER=MK4CC('p','l','r','d'), // playlist reader - DEPRECATED - only for wa3 + PLAYLISTWRITER=MK4CC('p','l','w','r'), // playlist writer - DEPRECATED - only for wa3 + MEDIACONVERTER=MK4CC('c','o','n','v'), // media converter + MEDIACORE=MK4CC('c','o','r','e'), // media core + MEDIARECORDER=MK4CC('m','r','e','c'), // media recorder + SCRIPTOBJECT=MK4CC('m','a','k','i'), // third party script object +// TRANSLATOR=MK4CC('x','l','a','t'), // text translator + WINDOWCREATE=MK4CC('w','n','d','c'), // window creator + XMLPROVIDER=MK4CC('x','m','l','p'), // xml provider + DB=MK2CC('d','b'), // database + SKINFILTER=MK4CC('f','l','t','r'), // bitmap/colorref skin filter + METADATA=MK4CC('m','t','d','t'), // play item meta data + METATAG=MK4CC('m','t','t','g'), // metadata tagging of play items + EVALUATOR=MK4CC('e','v','a','l'), // evaluate a string + MINIBROWSER=MK2CC('m','b'), // minibrowser + TOOLTIPSRENDERER=MK4CC('t','t','i','p'), // tooltips renderer + XUIOBJECT=MK4CC('x','u','i','o'), // xml gui objects + STRINGCONVERTER=MK4CC('u','t','f','8'), // unicode string conversion + ACTION=MK3CC('a','c','t'), // custom actions (ie: for buttons) + COREADMIN=MK4CC('c','a','d','m'), // core administrator + DROPTARGET=MK4CC('d','r','o','p'), // drop targets + OBJECTDIR=MK4CC('o','b','j','d'), // object directory + TEXTFEED=MK4CC('t','x','t','f'), // text feed, to send text to various XUI objects (ie: <Text> by using display="textfeedid" + ACCESSIBILITY=MK4CC('a','c','c','s'), // accessibility service + ACCESSIBILITYROLESERVER=MK4CC('r','o','l','e'), // accessibility roleServer services + EXPORTER=MK3CC('e','x','p'), // exporter + COLLECTION=MK4CC('c','l','c','t'), // named xml overridable collection + REDIRECT=MK4CC('r','e','d','r'), // filename redirect + FONTRENDER=MK4CC('f','o','n','t'), // font renderer (bitmap/truetype/freetype) + SRCCLASSFACTORY=MK4CC('c','l','f','a'), // source code class factory + SRCEDITOR=MK4CC('s','e','d','t'), // source code editor + MP4AUDIODECODER=MK4CC('m','4','a','d'), // mp4 audio decoder + PLAYLISTREADER_WA5=MK4CC('p','l','r','5'), // playlist reader + PLAYLISTWRITER_WA5=MK4CC('p','l','w','5'), // playlist writer + PLAYLISTHANDLER=MK3CC('p','l','h'), // playlist handler + TAGPROVIDER=MK4CC('t','a','g','z'), // tag provider (for ATF engine) + NSVFACTORY=MK4CC('n','s','v','f'), // NSV factory (to create NSV objects) + JSAPI2_APICREATOR=MK4CC('j','s','a','c'), // API Creator for the Javascript API + }; +}; + +#endif diff --git a/Src/Plugins/Input/in_wv/wasabi/api/service/waservicefactory.h b/Src/Plugins/Input/in_wv/wasabi/api/service/waservicefactory.h new file mode 100644 index 00000000..99c32d3f --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/api/service/waservicefactory.h @@ -0,0 +1,97 @@ +// ---------------------------------------------------------------------------- +// Generated by InterfaceFactory [Wed May 07 00:57:16 2003] +// +// File : waservicefactory.h +// Class : waServiceFactory +// class layer : Dispatchable Interface +// ---------------------------------------------------------------------------- + +#ifndef __WASERVICEFACTORY_H +#define __WASERVICEFACTORY_H + +#include "../../bfc/dispatch.h" +#include "../../bfc/nsguid.h" +#include "api_service.h" +// ---------------------------------------------------------------------------- + +class NOVTABLE waServiceFactory: public Dispatchable { + protected: + waServiceFactory() throw() {} + ~waServiceFactory() throw() {} + protected: + + public: + FOURCC getServiceType(); + const char *getServiceName(); + GUID getGuid(); + void *getInterface(int global_lock = TRUE); + int supportNonLockingGetInterface(); + int releaseInterface(void *ifc); + const wchar_t *getTestString(); + int serviceNotify(int msg, intptr_t param1 = 0, intptr_t param2 = 0); + + protected: + enum { + WASERVICEFACTORY_GETSERVICETYPE = 100, + WASERVICEFACTORY_GETSERVICENAME = 200, + WASERVICEFACTORY_GETGUID = 210, + WASERVICEFACTORY_GETINTERFACE = 300, + WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE = 301, + WASERVICEFACTORY_RELEASEINTERFACE = 310, + WASERVICEFACTORY_GETTESTSTRING = 500, + WASERVICEFACTORY_SERVICENOTIFY = 600, + }; +}; + +// ---------------------------------------------------------------------------- + +inline FOURCC waServiceFactory::getServiceType() { + FOURCC __retval = _call(WASERVICEFACTORY_GETSERVICETYPE, (FOURCC)NULL); + return __retval; +} + +inline const char *waServiceFactory::getServiceName() { + const char *__retval = _call(WASERVICEFACTORY_GETSERVICENAME, (const char *)0); + return __retval; +} + +inline GUID waServiceFactory::getGuid() { + GUID __retval = _call(WASERVICEFACTORY_GETGUID, INVALID_GUID); + return __retval; +} + +inline void *waServiceFactory::getInterface(int global_lock) { + void *__retval = _call(WASERVICEFACTORY_GETINTERFACE, (void *)NULL, global_lock); + +#if 0 // unused in Winamp 5 + // -- generated code - edit in waservicefactoryi.h + // support old code that always locks even when global_lock==FALSE + if (!global_lock && __retval != NULL && !supportNonLockingGetInterface()) + WASABI_API_SVC->service_unlock(__retval); +#endif + return __retval; +} + +inline int waServiceFactory::supportNonLockingGetInterface() { + int __retval = _call(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, (int)0); + return __retval; +} + +inline int waServiceFactory::releaseInterface(void *ifc) { + int __retval = _call(WASERVICEFACTORY_RELEASEINTERFACE, (int)0, ifc); + return __retval; +} + +inline const wchar_t *waServiceFactory::getTestString() { + return _call(WASERVICEFACTORY_GETTESTSTRING, (const wchar_t *)0); + +} + +inline int waServiceFactory::serviceNotify(int msg, intptr_t param1, intptr_t param2) { + int __retval = _call(WASERVICEFACTORY_SERVICENOTIFY, (int)0, msg, param1, param2); + return __retval; +} + +// ---------------------------------------------------------------------------- + +#endif // __WASERVICEFACTORY_H diff --git a/Src/Plugins/Input/in_wv/wasabi/bfc/dispatch.h b/Src/Plugins/Input/in_wv/wasabi/bfc/dispatch.h new file mode 100644 index 00000000..0d5b2002 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/bfc/dispatch.h @@ -0,0 +1,559 @@ +#pragma once +//#include <bfc/platform/platform.h> +#include "platform/types.h" +#include "platform/guid.h" + +#ifdef WIN32 +#ifndef NOVTABLE +#define NOVTABLE __declspec(novtable) +#endif +#else +#define NOVTABLE +#endif +class DispatchableCallback; + +#pragma warning(disable: 4786) +#pragma warning(disable: 4275) +#pragma warning(disable: 4100) + +enum +{ + DISPATCH_SUCCESS=0, + DISPATCH_FAILURE=1, +}; + +// TODO: define this to stdcall for linux, win64, mac +#define WASABICALL + +class NOVTABLE Dispatchable { +public: +// // fake virtual destructor +// void destruct() { _voidcall(DESTRUCT); } + + // this is virtual so it is visible across modules + virtual int WASABICALL _dispatch(int msg, void *retval, void **params=0, int nparam=0)=0; + + + /* added 22 May 2007. these aren't used yet. To be used in the future + in the meantime, don't use negative numbers for your msg values */ + size_t AddRef(); + size_t Release(); + int QueryInterface(GUID interface_guid, void **object); + enum + { + ADDREF=-1, + RELEASE=-2, + QUERYINTERFACE=-3, + }; +protected: +// // protected real destructor +// ~Dispatchable() {} + // helper templates to implement client-side methods + int _voidcall(int msg) { + return _dispatch(msg, 0); + } + + template<class PARAM1> + int _voidcall(int msg, PARAM1 param1) { + void *params[1] = { ¶m1 }; + return _dispatch(msg, 0, params, 1); + } + + template<class PARAM1, class PARAM2> + int _voidcall(int msg, PARAM1 param1, PARAM2 param2) { + void *params[2] = { ¶m1, ¶m2 }; + return _dispatch(msg, 0, params, 2); + } + + template<class PARAM1, class PARAM2, class PARAM3> + int _voidcall(int msg, PARAM1 param1, PARAM2 param2, PARAM3 param3) { + void *params[3] = { ¶m1, ¶m2, ¶m3 }; + return _dispatch(msg, 0, params, 3); + } + + template<class PARAM1, class PARAM2, class PARAM3, class PARAM4> + int _voidcall(int msg, PARAM1 param1, PARAM2 param2, PARAM3 param3, PARAM4 param4) { + void *params[4] = { ¶m1, ¶m2, ¶m3, ¶m4 }; + return _dispatch(msg, 0, params, 4); + } + + template<class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5> + int _voidcall(int msg, PARAM1 param1, PARAM2 param2, PARAM3 param3, PARAM4 param4, PARAM5 param5) { +// void *params[4] = { ¶m1, ¶m2, ¶m3, ¶m4, ¶m5 }; // mig found another bug + void *params[5] = { ¶m1, ¶m2, ¶m3, ¶m4, ¶m5 }; + return _dispatch(msg, 0, params, 5); + } + + template<class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6> + int _voidcall(int msg, PARAM1 param1, PARAM2 param2, PARAM3 param3, PARAM4 param4, PARAM5 param5, PARAM6 param6) { +// void *params[4] = { ¶m1, ¶m2, ¶m3, ¶m4, ¶m5, ¶m6 }; // mig found another bug + void *params[6] = { ¶m1, ¶m2, ¶m3, ¶m4, ¶m5, ¶m6 }; + return _dispatch(msg, 0, params, 6); + } + + template<class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7> + int _voidcall(int msg, PARAM1 param1, PARAM2 param2, PARAM3 param3, PARAM4 param4, PARAM5 param5, PARAM6 param6, PARAM7 param7) { + void *params[7] = { ¶m1, ¶m2, ¶m3, ¶m4, ¶m5, ¶m6 , ¶m7 }; + return _dispatch(msg, 0, params, 7); + } + + template<class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8> + int _voidcall(int msg, PARAM1 param1, PARAM2 param2, PARAM3 param3, PARAM4 param4, PARAM5 param5, PARAM6 param6, PARAM7 param7, PARAM8 param8) { + void *params[8] = { ¶m1, ¶m2, ¶m3, ¶m4, ¶m5, ¶m6 , ¶m7 , ¶m8 }; + return _dispatch(msg, 0, params, 8); + } + + template<class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8, class PARAM9> + int _voidcall(int msg, PARAM1 param1, PARAM2 param2, PARAM3 param3, PARAM4 param4, PARAM5 param5, PARAM6 param6, PARAM7 param7, PARAM8 param8, PARAM9 param9) { + void *params[9] = { ¶m1, ¶m2, ¶m3, ¶m4, ¶m5, ¶m6 , ¶m7 , ¶m8 , ¶m9 }; + return _dispatch(msg, 0, params, 9); + } + + template<class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8, class PARAM9, class PARAM10> + int _voidcall(int msg, PARAM1 param1, PARAM2 param2, PARAM3 param3, PARAM4 param4, PARAM5 param5, PARAM6 param6, PARAM7 param7, PARAM8 param8, PARAM9 param9, PARAM10 param10) { + void *params[10] = { ¶m1, ¶m2, ¶m3, ¶m4, ¶m5, ¶m6 , ¶m7 , ¶m8 , ¶m9 , ¶m10 }; + return _dispatch(msg, 0, params, 10); + } + + template<class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8, class PARAM9, class PARAM10, class PARAM11, class PARAM12, class PARAM13, class PARAM14> + int _voidcall(int msg, PARAM1 param1, PARAM2 param2, PARAM3 param3, PARAM4 param4, PARAM5 param5, PARAM6 param6, PARAM7 param7, PARAM8 param8, PARAM9 param9, PARAM10 param10, PARAM11 param11, PARAM12 param12, PARAM13 param13, PARAM14 param14) { + void *params[14] = { ¶m1, ¶m2, ¶m3, ¶m4, ¶m5, ¶m6 , ¶m7 , ¶m8 , ¶m9 , ¶m10 , ¶m11 , ¶m12 , ¶m13 , ¶m14 }; + return _dispatch(msg, 0, params, 14); + } + + template<class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8, class PARAM9, class PARAM10, class PARAM11, class PARAM12, class PARAM13, class PARAM14, class PARAM15, class PARAM16> + int _voidcall(int msg, PARAM1 param1, PARAM2 param2, PARAM3 param3, PARAM4 param4, PARAM5 param5, PARAM6 param6, PARAM7 param7, PARAM8 param8, PARAM9 param9, PARAM10 param10, PARAM11 param11, PARAM12 param12, PARAM13 param13, PARAM14 param14, PARAM15 param15, PARAM16 param16) { + void *params[16] = { ¶m1, ¶m2, ¶m3, ¶m4, ¶m5, ¶m6 , ¶m7 , ¶m8 , ¶m9 , ¶m10 , ¶m11 , ¶m12 , ¶m13 , ¶m14 , ¶m15 , ¶m16 }; + return _dispatch(msg, 0, params, 16); + } + + template<class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8, class PARAM9, class PARAM10, class PARAM11, class PARAM12, class PARAM13, class PARAM14, class PARAM15, class PARAM16, class PARAM17> + int _voidcall(int msg, PARAM1 param1, PARAM2 param2, PARAM3 param3, PARAM4 param4, PARAM5 param5, PARAM6 param6, PARAM7 param7, PARAM8 param8, PARAM9 param9, PARAM10 param10, PARAM11 param11, PARAM12 param12, PARAM13 param13, PARAM14 param14, PARAM15 param15, PARAM16 param16, PARAM17 param17) { + void *params[17] = { ¶m1, ¶m2, ¶m3, ¶m4, ¶m5, ¶m6 , ¶m7 , ¶m8 , ¶m9 , ¶m10 , ¶m11 , ¶m12 , ¶m13 , ¶m14 , ¶m15 , ¶m16 , ¶m17 }; + return _dispatch(msg, 0, params, 17); + } + + + template<class RETURN_TYPE> + RETURN_TYPE _call(int msg, RETURN_TYPE defval) { + RETURN_TYPE retval; + if (_dispatch(msg, &retval)) return retval; + return defval; + } + + template<class RETURN_TYPE, class PARAM1> + RETURN_TYPE _call(int msg, RETURN_TYPE defval, PARAM1 param1) { + void *params[1] = { ¶m1 }; + RETURN_TYPE retval; + if (_dispatch(msg, &retval, params, 1)) return retval; + return defval; + } + + template<class RETURN_TYPE, class PARAM1, class PARAM2> + RETURN_TYPE _call(int msg, RETURN_TYPE defval, PARAM1 param1, PARAM2 param2) { + void *params[2] = { ¶m1, ¶m2 }; + RETURN_TYPE retval; + if (_dispatch(msg, &retval, params, 2)) return retval; + return defval; + } + + template<class RETURN_TYPE, class PARAM1, class PARAM2, class PARAM3> + RETURN_TYPE _call(int msg, RETURN_TYPE defval, PARAM1 param1, PARAM2 param2, PARAM3 param3) { + void *params[3] = { ¶m1, ¶m2, ¶m3 }; + RETURN_TYPE retval; + if (_dispatch(msg, &retval, params, 3)) return retval; + return defval; + } + + template<class RETURN_TYPE, class PARAM1, class PARAM2, class PARAM3, class PARAM4> + RETURN_TYPE _call(int msg, RETURN_TYPE defval, PARAM1 param1, PARAM2 param2, PARAM3 param3, PARAM4 param4) { + void *params[4] = { ¶m1, ¶m2, ¶m3, ¶m4 }; + RETURN_TYPE retval; + if (_dispatch(msg, &retval, params, 4)) return retval; + return defval; + } + + template<class RETURN_TYPE, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5> + RETURN_TYPE _call(int msg, RETURN_TYPE defval, PARAM1 param1, PARAM2 param2, PARAM3 param3, PARAM4 param4, PARAM5 param5) { + void *params[5] = { ¶m1, ¶m2, ¶m3, ¶m4, ¶m5 }; + RETURN_TYPE retval; + if (_dispatch(msg, &retval, params, 5)) return retval; + return defval; + } + + template<class RETURN_TYPE, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6> + RETURN_TYPE _call(int msg, RETURN_TYPE defval, PARAM1 param1, PARAM2 param2, PARAM3 param3, PARAM4 param4, PARAM5 param5, PARAM6 param6) { + void *params[6] = { ¶m1, ¶m2, ¶m3, ¶m4, ¶m5, ¶m6 }; + RETURN_TYPE retval; + if (_dispatch(msg, &retval, params, 6)) return retval; + return defval; + } + + template<class RETURN_TYPE, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7> + RETURN_TYPE _call(int msg, RETURN_TYPE defval, PARAM1 param1, PARAM2 param2, PARAM3 param3, PARAM4 param4, PARAM5 param5, PARAM6 param6, PARAM7 param7) { + void *params[7] = { ¶m1, ¶m2, ¶m3, ¶m4, ¶m5, ¶m6, ¶m7 }; + RETURN_TYPE retval; + if (_dispatch(msg, &retval, params, 7)) return retval; + return defval; + } + + template<class RETURN_TYPE, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8> + RETURN_TYPE _call(int msg, RETURN_TYPE defval, PARAM1 param1, PARAM2 param2, PARAM3 param3, PARAM4 param4, PARAM5 param5, PARAM6 param6, PARAM7 param7, PARAM8 param8) { + void *params[8] = { ¶m1, ¶m2, ¶m3, ¶m4, ¶m5, ¶m6, ¶m7, ¶m8 }; + RETURN_TYPE retval; + if (_dispatch(msg, &retval, params, 8)) return retval; + return defval; + } + + template<class RETURN_TYPE, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8, class PARAM9> + RETURN_TYPE _call(int msg, RETURN_TYPE defval, PARAM1 param1, PARAM2 param2, PARAM3 param3, PARAM4 param4, PARAM5 param5, PARAM6 param6, PARAM7 param7, PARAM8 param8, PARAM9 param9) { + void *params[9] = { ¶m1, ¶m2, ¶m3, ¶m4, ¶m5, ¶m6, ¶m7, ¶m8, ¶m9 }; + RETURN_TYPE retval; + if (_dispatch(msg, &retval, params, 9)) return retval; + return defval; + } + + template<class RETURN_TYPE, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8, class PARAM9, class PARAM10> + RETURN_TYPE _call(int msg, RETURN_TYPE defval, PARAM1 param1, PARAM2 param2, PARAM3 param3, PARAM4 param4, PARAM5 param5, PARAM6 param6, PARAM7 param7, PARAM8 param8, PARAM9 param9, PARAM10 param10) { + void *params[10] = { ¶m1, ¶m2, ¶m3, ¶m4, ¶m5, ¶m6, ¶m7, ¶m8, ¶m9, ¶m10 }; + RETURN_TYPE retval; + if (_dispatch(msg, &retval, params, 10)) return retval; + return defval; + } + + template <class CLASSNAME, class RETVAL> + void cb(RETVAL (CLASSNAME::*fn)(), void *retval, void **params) { + *static_cast<RETVAL*>(retval) = (static_cast<CLASSNAME *>(this)->*fn)(); + } + + template <class CLASSNAME> + void vcb(void (CLASSNAME::*fn)(), void *retval, void **params) { + (static_cast<CLASSNAME *>(this)->*fn)(); + } + + template <class CLASSNAME, class RETVAL, class PARAM1> + void cb(RETVAL (CLASSNAME::*fn)(PARAM1), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + *static_cast<RETVAL*>(retval) = (static_cast<CLASSNAME *>(this)->*fn)(*p1); + } + + template <class CLASSNAME, class PARAM1> + void vcb(void (CLASSNAME::*fn)(PARAM1), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + (static_cast<CLASSNAME *>(this)->*fn)(*p1); + } + + template <class CLASSNAME, class PARAM1, class PARAM2> + void vcb(void (CLASSNAME::*fn)(PARAM1, PARAM2), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2); + } + + template <class CLASSNAME, class RETVAL, class PARAM1, class PARAM2> + void cb(RETVAL (CLASSNAME::*fn)(PARAM1, PARAM2), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + *static_cast<RETVAL*>(retval) = (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2); + } + + // 3 params + template <class CLASSNAME, class PARAM1, class PARAM2, class PARAM3> + void vcb(void (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3); + } + + template <class CLASSNAME, class RETVAL, class PARAM1, class PARAM2, class PARAM3> + void cb(RETVAL (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + *static_cast<RETVAL*>(retval) = (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3); + } + + // 4 params + template <class CLASSNAME, class PARAM1, class PARAM2, class PARAM3, class PARAM4> + void vcb(void (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3, PARAM4), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + PARAM4 *p4 = static_cast<PARAM4*>(params[3]); + (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3, *p4); + } + + template <class CLASSNAME, class RETVAL, class PARAM1, class PARAM2, class PARAM3, class PARAM4> + void cb(RETVAL (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3, PARAM4), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + PARAM4 *p4 = static_cast<PARAM4*>(params[3]); + *static_cast<RETVAL*>(retval) = (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3, *p4); + } + + // 5 params + template <class CLASSNAME, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5> + void vcb(void (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3, PARAM4, PARAM5), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + PARAM4 *p4 = static_cast<PARAM4*>(params[3]); + PARAM5 *p5 = static_cast<PARAM5*>(params[4]); + (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3, *p4, *p5); + } + + template <class CLASSNAME, class RETVAL, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5> + void cb(RETVAL (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3, PARAM4, PARAM5), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + PARAM4 *p4 = static_cast<PARAM4*>(params[3]); + PARAM5 *p5 = static_cast<PARAM5*>(params[4]); + *static_cast<RETVAL*>(retval) = (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3, *p4, *p5); + } + + // 6 params + template <class CLASSNAME, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6> + void vcb(void (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3, PARAM4, PARAM5, PARAM6), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + PARAM4 *p4 = static_cast<PARAM4*>(params[3]); + PARAM5 *p5 = static_cast<PARAM5*>(params[4]); + PARAM6 *p6 = static_cast<PARAM6*>(params[5]); + (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3, *p4, *p5, *p6); + } + + template <class CLASSNAME, class RETVAL, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6> + void cb(RETVAL (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3, PARAM4, PARAM5, PARAM6), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + PARAM4 *p4 = static_cast<PARAM4*>(params[3]); + PARAM5 *p5 = static_cast<PARAM5*>(params[4]); + PARAM6 *p6 = static_cast<PARAM6*>(params[5]); + *static_cast<RETVAL*>(retval) = (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3, *p4, *p5, *p6); + } + + // 7 params + template <class CLASSNAME, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7> + void vcb(void (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3, PARAM4, PARAM5, PARAM6, PARAM7), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + PARAM4 *p4 = static_cast<PARAM4*>(params[3]); + PARAM5 *p5 = static_cast<PARAM5*>(params[4]); + PARAM6 *p6 = static_cast<PARAM6*>(params[5]); + PARAM7 *p7 = static_cast<PARAM7*>(params[6]); + (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3, *p4, *p5, *p6, *p7); + } + + template <class CLASSNAME, class RETVAL, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7> + void cb(RETVAL (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3, PARAM4, PARAM5, PARAM6, PARAM7), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + PARAM4 *p4 = static_cast<PARAM4*>(params[3]); + PARAM5 *p5 = static_cast<PARAM5*>(params[4]); + PARAM6 *p6 = static_cast<PARAM6*>(params[5]); + PARAM7 *p7 = static_cast<PARAM7*>(params[6]); + *static_cast<RETVAL*>(retval) = (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3, *p4, *p5, *p6, *p7); + } + + // 8 params + template <class CLASSNAME, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8> + void vcb(void (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3, PARAM4, PARAM5, PARAM6, PARAM7, PARAM8), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + PARAM4 *p4 = static_cast<PARAM4*>(params[3]); + PARAM5 *p5 = static_cast<PARAM5*>(params[4]); + PARAM6 *p6 = static_cast<PARAM6*>(params[5]); + PARAM7 *p7 = static_cast<PARAM7*>(params[6]); + PARAM8 *p8 = static_cast<PARAM8*>(params[7]); + (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8); + } + + template <class CLASSNAME, class RETVAL, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8> + void cb(RETVAL (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3, PARAM4, PARAM5, PARAM6, PARAM7, PARAM8), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + PARAM4 *p4 = static_cast<PARAM4*>(params[3]); + PARAM5 *p5 = static_cast<PARAM5*>(params[4]); + PARAM6 *p6 = static_cast<PARAM6*>(params[5]); + PARAM7 *p7 = static_cast<PARAM7*>(params[6]); + PARAM8 *p8 = static_cast<PARAM8*>(params[7]); + *static_cast<RETVAL*>(retval) = (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8); + } + + // 9 params + template <class CLASSNAME, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8, class PARAM9> + void vcb(void (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3, PARAM4, PARAM5, PARAM6, PARAM7, PARAM8, PARAM9), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + PARAM4 *p4 = static_cast<PARAM4*>(params[3]); + PARAM5 *p5 = static_cast<PARAM5*>(params[4]); + PARAM6 *p6 = static_cast<PARAM6*>(params[5]); + PARAM7 *p7 = static_cast<PARAM7*>(params[6]); + PARAM8 *p8 = static_cast<PARAM8*>(params[7]); + PARAM9 *p9 = static_cast<PARAM9*>(params[8]); + (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9); + } + + template <class CLASSNAME, class RETVAL, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8, class PARAM9> + void cb(RETVAL (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3, PARAM4, PARAM5, PARAM6, PARAM7, PARAM8, PARAM9), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + PARAM4 *p4 = static_cast<PARAM4*>(params[3]); + PARAM5 *p5 = static_cast<PARAM5*>(params[4]); + PARAM6 *p6 = static_cast<PARAM6*>(params[5]); + PARAM7 *p7 = static_cast<PARAM7*>(params[6]); + PARAM8 *p8 = static_cast<PARAM8*>(params[7]); + PARAM9 *p9 = static_cast<PARAM9*>(params[8]); + *static_cast<RETVAL*>(retval) = (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9); + } + + // 10 params + template <class CLASSNAME, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8, class PARAM9, class PARAM10> + void vcb(void (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3, PARAM4, PARAM5, PARAM6, PARAM7, PARAM8, PARAM9, PARAM10), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + PARAM4 *p4 = static_cast<PARAM4*>(params[3]); + PARAM5 *p5 = static_cast<PARAM5*>(params[4]); + PARAM6 *p6 = static_cast<PARAM6*>(params[5]); + PARAM7 *p7 = static_cast<PARAM7*>(params[6]); + PARAM8 *p8 = static_cast<PARAM8*>(params[7]); + PARAM9 *p9 = static_cast<PARAM9*>(params[8]); + PARAM10 *p10 = static_cast<PARAM10*>(params[9]); + (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10); + } + + template <class CLASSNAME, class RETVAL, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8, class PARAM9, class PARAM10> + void cb(RETVAL (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3, PARAM4, PARAM5, PARAM6, PARAM7, PARAM8, PARAM9, PARAM10), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + PARAM4 *p4 = static_cast<PARAM4*>(params[3]); + PARAM5 *p5 = static_cast<PARAM5*>(params[4]); + PARAM6 *p6 = static_cast<PARAM6*>(params[5]); + PARAM7 *p7 = static_cast<PARAM7*>(params[6]); + PARAM8 *p8 = static_cast<PARAM8*>(params[7]); + PARAM9 *p9 = static_cast<PARAM9*>(params[8]); + PARAM10 *p10 = static_cast<PARAM10*>(params[9]); + *static_cast<RETVAL*>(retval) = (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10); + } + + // 14 params + template <class CLASSNAME, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8, class PARAM9, class PARAM10, class PARAM11, class PARAM12, class PARAM13, class PARAM14> + void vcb(void (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3, PARAM4, PARAM5, PARAM6, PARAM7, PARAM8, PARAM9, PARAM10, PARAM11, PARAM12, PARAM13, PARAM14), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + PARAM4 *p4 = static_cast<PARAM4*>(params[3]); + PARAM5 *p5 = static_cast<PARAM5*>(params[4]); + PARAM6 *p6 = static_cast<PARAM6*>(params[5]); + PARAM7 *p7 = static_cast<PARAM7*>(params[6]); + PARAM8 *p8 = static_cast<PARAM8*>(params[7]); + PARAM9 *p9 = static_cast<PARAM9*>(params[8]); + PARAM10 *p10 = static_cast<PARAM10*>(params[9]); + PARAM11 *p11 = static_cast<PARAM11*>(params[10]); + PARAM12 *p12 = static_cast<PARAM12*>(params[11]); + PARAM13 *p13 = static_cast<PARAM13*>(params[12]); + PARAM14 *p14 = static_cast<PARAM14*>(params[13]); + (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11, *p12, *p13, *p14); + } + + // 16 params + template <class CLASSNAME, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8, class PARAM9, class PARAM10, class PARAM11, class PARAM12, class PARAM13, class PARAM14, class PARAM15, class PARAM16> + void vcb(void (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3, PARAM4, PARAM5, PARAM6, PARAM7, PARAM8, PARAM9, PARAM10, PARAM11, PARAM12, PARAM13, PARAM14, PARAM15, PARAM16), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + PARAM4 *p4 = static_cast<PARAM4*>(params[3]); + PARAM5 *p5 = static_cast<PARAM5*>(params[4]); + PARAM6 *p6 = static_cast<PARAM6*>(params[5]); + PARAM7 *p7 = static_cast<PARAM7*>(params[6]); + PARAM8 *p8 = static_cast<PARAM8*>(params[7]); + PARAM9 *p9 = static_cast<PARAM9*>(params[8]); + PARAM10 *p10 = static_cast<PARAM10*>(params[9]); + PARAM11 *p11 = static_cast<PARAM11*>(params[10]); + PARAM12 *p12 = static_cast<PARAM12*>(params[11]); + PARAM13 *p13 = static_cast<PARAM13*>(params[12]); + PARAM14 *p14 = static_cast<PARAM14*>(params[13]); + PARAM15 *p15 = static_cast<PARAM15*>(params[14]); + PARAM16 *p16 = static_cast<PARAM16*>(params[15]); + (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11, *p12, *p13, *p14, *p15, *p16); + } + + // 17 params + template <class CLASSNAME, class PARAM1, class PARAM2, class PARAM3, class PARAM4, class PARAM5, class PARAM6, class PARAM7, class PARAM8, class PARAM9, class PARAM10, class PARAM11, class PARAM12, class PARAM13, class PARAM14, class PARAM15, class PARAM16, class PARAM17> + void vcb(void (CLASSNAME::*fn)(PARAM1, PARAM2, PARAM3, PARAM4, PARAM5, PARAM6, PARAM7, PARAM8, PARAM9, PARAM10, PARAM11, PARAM12, PARAM13, PARAM14, PARAM15, PARAM16, PARAM17), void *retval, void **params) { + PARAM1 *p1 = static_cast<PARAM1*>(params[0]); + PARAM2 *p2 = static_cast<PARAM2*>(params[1]); + PARAM3 *p3 = static_cast<PARAM3*>(params[2]); + PARAM4 *p4 = static_cast<PARAM4*>(params[3]); + PARAM5 *p5 = static_cast<PARAM5*>(params[4]); + PARAM6 *p6 = static_cast<PARAM6*>(params[5]); + PARAM7 *p7 = static_cast<PARAM7*>(params[6]); + PARAM8 *p8 = static_cast<PARAM8*>(params[7]); + PARAM9 *p9 = static_cast<PARAM9*>(params[8]); + PARAM10 *p10 = static_cast<PARAM10*>(params[9]); + PARAM11 *p11 = static_cast<PARAM11*>(params[10]); + PARAM12 *p12 = static_cast<PARAM12*>(params[11]); + PARAM13 *p13 = static_cast<PARAM13*>(params[12]); + PARAM14 *p14 = static_cast<PARAM14*>(params[13]); + PARAM15 *p15 = static_cast<PARAM15*>(params[14]); + PARAM16 *p16 = static_cast<PARAM16*>(params[15]); + PARAM17 *p17 = static_cast<PARAM17*>(params[16]); + (static_cast<CLASSNAME *>(this)->*fn)(*p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11, *p12, *p13, *p14, *p15, *p16, *p17); + } + + + enum { DESTRUCT=0xffff }; +}; +#define CB(x, y) case (x): cb(&CBCLASS::y, retval, params); break; +#define VCB(x, y) case (x): vcb(&CBCLASS::y, retval, params); break; + +#define RECVS_DISPATCH virtual int WASABICALL _dispatch(int msg, void *retval, void **params=0, int nparam=0) + +#define START_DISPATCH \ + int CBCLASS::_dispatch(int msg, void *retval, void **params, int nparam) { \ + switch (msg) { +#define START_DISPATCH_INLINE \ + int WASABICALL CBCLASS::_dispatch(int msg, void *retval, void **params, int nparam) { \ + switch (msg) { + +//FINISH case DESTRUCT: delete this; return 1; +#define END_DISPATCH \ + default: return 0; \ + } \ + return 1; \ + } +#define FORWARD_DISPATCH(x) \ + default: return x::_dispatch(msg, retval, params, nparam); \ + } \ + return 1; \ + } + +#define DISPATCH_CODES enum + +inline size_t Dispatchable::AddRef() +{ + return _call(Dispatchable::ADDREF, 0); +} + +inline size_t Dispatchable::Release() +{ + return _call(Dispatchable::RELEASE, 0); +} + +inline int Dispatchable::QueryInterface(GUID interface_guid, void **object) +{ + return _call(Dispatchable::QUERYINTERFACE, 0, interface_guid, object); +} diff --git a/Src/Plugins/Input/in_wv/wasabi/bfc/nsguid.h b/Src/Plugins/Input/in_wv/wasabi/bfc/nsguid.h new file mode 100644 index 00000000..188150e4 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/bfc/nsguid.h @@ -0,0 +1,37 @@ +#ifndef _NSGUID_H +#define _NSGUID_H + +#include "platform/guid.h" +//#include <bfc/common.h> + +// Some conversion functions to allow +// us to have GUIDs translatable to and from other data types. +class nsGUID { +public: + // To the "Human Readable" character format. + // {1B3CA60C-DA98-4826-B4A9-D79748A5FD73} + static char *toChar(const GUID &guid, char *target); + static wchar_t *toCharW(const GUID &guid, wchar_t *target); + static GUID fromCharW(const wchar_t *source); + // To the "C Structure" character format. + // { 0x1b3ca60c, 0xda98, 0x4826, { 0xb4, 0xa9, 0xd7, 0x97, 0x48, 0xa5, 0xfd, 0x73 } }; + static char *toCode(const GUID &guid, char *target); + static GUID fromCode(const char *source); + + // Compare function, returns -1, 0, 1 + static int compare(const GUID &a, const GUID &b); + + // strlen("{xx xxx xxx-xxxx-xxxx-xxxx-xxx xxx xxx xxx}" + enum { GUID_STRLEN = 38 }; + +#ifdef WASABI_COMPILE_CREATEGUID + static void createGuid(GUID *g); +#endif +}; + +inline +int operator <(const GUID &a, const GUID &b) { + return (nsGUID::compare(a, b) < 0); +} + +#endif //_NSGUID_H diff --git a/Src/Plugins/Input/in_wv/wasabi/bfc/platform/guid.h b/Src/Plugins/Input/in_wv/wasabi/bfc/platform/guid.h new file mode 100644 index 00000000..b2f40f89 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/bfc/platform/guid.h @@ -0,0 +1,24 @@ +#ifndef _GUID_H +#define _GUID_H + +#include "types.h" +#include "platform.h" + + +#ifdef __cplusplus +#ifndef GUID_EQUALS_DEFINED +#define GUID_EQUALS_DEFINED +#include <memory.h> +static __inline int operator ==(const GUID &a, const GUID &b) { + return !memcmp(&a, &b, sizeof(GUID)); +} +static __inline int operator !=(const GUID &a, const GUID &b) { + return !!memcmp(&a, &b, sizeof(GUID)); +} +#endif //GUID_EQUALS_DEFINED +#endif //__cplusplus + +static const GUID INVALID_GUID = { 0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0} }; +static const GUID GENERIC_GUID = { 0xFFFFFFFF, 0xFFFF, 0xFFFF, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} }; + +#endif diff --git a/Src/Plugins/Input/in_wv/wasabi/bfc/platform/platform.h b/Src/Plugins/Input/in_wv/wasabi/bfc/platform/platform.h new file mode 100644 index 00000000..46baf8a8 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/bfc/platform/platform.h @@ -0,0 +1,500 @@ +#ifndef _PLATFORM_H +#define _PLATFORM_H + +#include "types.h" +#include "../std_mkncc.h" // for MKnCC + +#ifdef WIN32 +# include "win32.h" + +#define OSMODULEHANDLE HINSTANCE +#define INVALIDOSMODULEHANDLE ((OSMODULEHANDLE)0) +#define OSWINDOWHANDLE HWND +#define INVALIDOSWINDOWHANDLE ((OSWINDOWHANDLE)0) +#define OSICONHANDLE HICON +#define INVALIDOSICONHANDLE ((OSICONHANDLE)0) +#define OSCURSORHANDLE HICON +#define INVALIDOSCURSORHANDLE ((OSCURSORHANDLE)0) +#define OSTHREADHANDLE HANDLE +#define INVALIDOSTHREADHANDLE ((OSTHREADHANDLE)0) +#define OSREGIONHANDLE HRGN +#define INVALIDOSREGIONHANDLE ((OSREGIONHANDLE)0) +typedef HMENU OSMENUHANDLE; + +#define RGBA(r,g,b,a) ((ARGB32)((uint8_t)(r) | ((uint8_t)(g) << 8) | ((uint8_t)(b) << 16) | ((uint8_t)(a) << 24))) + +#ifndef PATH_MAX +#define PATH_MAX MAX_PATH +#endif + +#elif defined(LINUX) +# include <bfc/platform/linux.h> +#elif defined(__APPLE__) +#include <Carbon/Carbon.h> + +typedef HIShapeRef OSREGIONHANDLE; +typedef int OSCURSOR; // TODO: find a good one for this +typedef int OSCURSORHANDLE; // TODO: find a good one for this +typedef HIWindowRef OSWINDOWHANDLE; +typedef void *OSMODULEHANDLE; // TODO: +typedef CGContextRef HDC; // TODO: find a better name +typedef MenuRef OSMENUHANDLE; +typedef CGImageRef OSICONHANDLE; + +#ifdef __LITTLE_ENDIAN__ +#define RGBA(r,g,b,a) ((ARGB32)((uint8_t)(r) | ((uint8_t)(g) << 8) | ((uint8_t)(b) << 16) | ((uint8_t)(a) << 24))) +#elif defined(__BIG_ENDIAN__) +#define RGBA(r,g,b,a) ((ARGB32)((uint8_t)(a) | ((uint8_t)(r) << 8) | ((uint8_t)(g) << 16) | ((uint8_t)(b) << 24))) +#else +#error endian preprocessor symbol not defined +#endif + +#define RGB(r,g,b) RGBA(r,g,b,0xFF) + +static const HIWindowRef INVALIDOSWINDOWHANDLE = 0; // TODO: maybe there's an apple-defined name for this +#define INVALIDOSMODULEHANDLE 0 +#define INVALIDOSCURSORHANDLE 0 + +typedef char OSFNCHAR; +typedef char *OSFNSTR; + +typedef const char OSFNCCHAR; +typedef const char *OSFNCSTR; + +#define FNT(x) x + +typedef struct tagRECT +{ + int left; + int top; + int right; + int bottom; +} +RECT; +typedef RECT * LPRECT; + +inline RECT RECTFromHIRect(const HIRect *r) +{ + RECT rect; + rect.left = r->origin.x; + rect.right = r->origin.x + r->size.width; + rect.top = r->origin.y; + rect.bottom = r->origin.y + r->size.height; + return rect; +} + +inline HIRect HIRectFromRECT(const RECT *r) +{ + HIRect rect; + rect.origin.x = r->left; + rect.origin.y = r->top; + rect.size.width = r->right - r->left; + rect.size.height = r->bottom - r->top; + return rect; +} + +typedef struct tagPOINT +{ + int x; + int y; +} +POINT; +typedef struct tagPOINT * LPPOINT; + +inline HIPoint HIPointFromPOINT(const POINT *pt) +{ + HIPoint p; + p.x = pt->x; + p.y = pt->y; + return p; +} + +inline int MulDiv(int a, int b, int c) +{ + int s; + int v; + + s = 0; + if (a < 0) + { + s = !s; + a = -a; + } + if (b < 0) + { + s = !s; + b = -b; + } + if (c < 0) + { + s = !s; + c = -c; + } + double d; + d = ((double)a * (double)b) / (double)c; + if (d >= 4294967296.) + return -1; + v = d; + if (s) + v = -v; + return v; +} + +#else +#error port me +// Windows API dependant definitions for non-windows platforms + +#define __cdecl +#define __stdcall +#define WINAPI +#define WINBASEAPI +#define WINUSERAPI +#define WINGDIAPI +#define WINOLEAPI +#define CALLBACK +#define FARPROC void * + +#define FALSE 0 +#define TRUE 1 + +#define ERROR 0 + +#define CONST const +#define VOID void + +typedef unsigned long DWORD; +typedef unsigned short WORD; +typedef unsigned char BYTE; +typedef long LONG; +typedef int INT; +typedef int BOOL; +typedef short SHORT; +typedef void * PVOID; +typedef void * LPVOID; + +typedef char CHAR; +typedef unsigned short WCHAR; +typedef char * LPSTR; +typedef WCHAR * LPWSTR; +typedef const char * LPCSTR; +typedef const WCHAR * LPCWSTR; +typedef LPWSTR PTSTR, LPTSTR; +typedef LPCWSTR LPCTSTR; +typedef char TCHAR; +typedef WCHAR OLECHAR; + +typedef void * HANDLE; +typedef void * HWND; +typedef void * HDC; +typedef void * HFONT; +typedef void * HBITMAP; +typedef void * HINSTANCE; +typedef void * HICON; +typedef void * HRGN; +typedef void * HPEN; +typedef void * HBRUSH; +typedef void * HRSRC; +typedef void * HGLOBAL; +typedef void * HACCEL; +typedef void * HMODULE; +typedef void * HMENU; +typedef void * HGDIOBJ; + +typedef void * ATOM; +typedef void * CRITICAL_SECTION; +typedef void * LPCRITICAL_SECTION; + +typedef UINT WPARAM; +typedef UINT LPARAM; +typedef LONG LRESULT; +typedef UINT COLORREF; + +typedef LRESULT(*WNDPROC)(HWND, UINT, WPARAM, LPARAM); +typedef BOOL CALLBACK WNDENUMPROC(HWND, LPARAM); +typedef VOID CALLBACK *TIMERPROC(HWND, UINT, UINT, DWORD); + +typedef struct tagPOINT +{ + LONG x; + LONG y; +} +POINT; +typedef struct tagPOINT * LPPOINT; + +typedef struct tagSIZE +{ + LONG cx; + LONG cy; +} +SIZE; + + +typedef struct tagRECT +{ + LONG left; + LONG top; + LONG right; + LONG bottom; +} +RECT; +typedef RECT * LPRECT; + +typedef struct _COORD +{ + SHORT X; + SHORT Y; +} +COORD, *PCOORD; + +typedef struct tagPAINTSTRUCT +{ + HDC hdc; + BOOL fErase; + RECT rcPaint; + BOOL fRestore; + BOOL fIncUpdate; + BYTE rgbReserved[32]; +} +PAINTSTRUCT; + +typedef struct tagBITMAP +{ /* bm */ + int bmType; + int bmWidth; + int bmHeight; + int bmWidthBytes; + BYTE bmPlanes; + BYTE bmBitsPixel; + LPVOID bmBits; +} +BITMAP; + +typedef struct tagRGBQUAD +{ + BYTE rgbRed; + BYTE rgbGreen; + BYTE rgbBlue; + BYTE rgbReserved; +} +RGBQUAD; + +typedef struct tagBITMAPINFOHEADER +{ + DWORD biSize; + LONG biWidth; + LONG biHeight; + WORD biPlanes; + WORD biBitCount; + DWORD biCompression; + DWORD biSizeImage; + LONG biXPelsPerMeter; + LONG biYPelsPerMeter; + DWORD biClrUsed; + DWORD biClrImportant; +} +BITMAPINFOHEADER; + +typedef struct tagBITMAPINFO +{ + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[1]; +} +BITMAPINFO, *LPBITMAPINFO; + +typedef struct tagMSG +{ + HWND hwnd; + UINT message; + WPARAM wParam; + LPARAM lParam; + DWORD time; + POINT pt; +} +MSG; + +typedef MSG * LPMSG; + +typedef struct _RGNDATAHEADER +{ + DWORD dwSize; + DWORD iType; + DWORD nCount; + DWORD nRgnSize; + RECT rcBound; +} +RGNDATAHEADER, *PRGNDATAHEADER; + +typedef struct _RGNDATA +{ + RGNDATAHEADER rdh; + char Buffer[1]; +} +RGNDATA, *PRGNDATA; + +// Windows messages + +#define WM_SYSCOMMAND 0x112 +#define WM_LBUTTONDOWN 0x201 +#define WM_LBUTTONUP 0x202 +#define WM_RBUTTONDOWN 0x204 +#define WM_RBUTTONUP 0x205 + +#define WM_USER 0x400 + +#define WS_EX_TOOLWINDOW 0x00000080L + +#define WS_OVERLAPPED 0x00000000L +#define WS_MAXIMIZEBOX 0x00010000L +#define WS_MINIMIZEBOX 0x00020000L +#define WS_SYSMENU 0x00080000L +#define WS_CAPTION 0x00C00000L +#define WS_CLIPCHILDREN 0x02000000L +#define WS_CLIPSIBLINGS 0x04000000L +#define WS_VISIBLE 0x10000000L +#define WS_CHILD 0x40000000L +#define WS_POPUP 0x80000000L + +#define HWND_TOP ((HWND)0) +#define HWND_TOPMOST ((HWND)-1) +#define HWND_NOTOPMOST ((HWND)-2) + +#define GWL_STYLE (-16) + +#define GW_HWNDFIRST 0 +#define GW_HWNDNEXT 2 + +#define SWP_NOMOVE 0x0002 +#define SWP_NOSIZE 0x0001 +#define SWP_SHOWWINDOW 0x0040 +#define SWP_DEFERERASE 0x2000 +#define SWP_NOZORDER 0x0004 +#define SWP_NOACTIVATE 0x0010 + +#define SW_SHOW 5 + +#define SC_MINIMIZE 0xF020 +#define SC_MAXIMIZE 0xF030 +#define SC_RESTORE 0xF120 + +#define GCL_HICONSM (-34) +#define GCL_HICON (-14) + +#define MB_OK 0 +#define MB_OKCANCEL 1 +#define MB_TASKMODAL 0x2000L + +#define IDOK 1 +#define IDCANCEL 2 + +#define VK_SHIFT 0x10 +#define VK_CONTROL 0x11 +#define VK_MENU 0x12 + +#define RT_RCDATA 10 + +#define IMAGE_BITMAP 0 + +#define LR_LOADFROMFILE 0x0010 + +#define DIB_RGB_COLORS 0 + +#define MAX_PATH 1024 +#define _MAX_PATH MAX_PATH +#define _MAX_DRIVE 3 +#define _MAX_DIR 256 +#define _MAX_FNAME 256 +#define _MAX_EXT 256 + +#define GMEM_FIXED 0x0 +#define GMEM_ZEROINIT 0x40 +#define GPTR (GMEM_FIXED | GMEM_ZEROINIT) + +#define SPI_GETWORKAREA 48 + +#define SM_CXDOUBLECLK 36 +#define SM_CYDOUBLECLK 37 + +#define COLORONCOLOR 3 + +#define SRCCOPY (DWORD)0x00CC0020 + +#define BI_RGB 0L + +#define NULLREGION 1 + +#define DT_LEFT 0x00000000 +#define DT_CENTER 0x00000001 +#define DT_RIGHT 0x00000002 +#define DT_VCENTER 0x00000004 +#define DT_WORDBREAK 0x00000010 +#define DT_SINGLELINE 0x00000020 +#define DT_CALCRECT 0x00000400 +#define DT_NOPREFIX 0x00000800 +#define DT_PATH_ELLIPSIS 0x00004000 +#define DT_END_ELLIPSIS 0x00008000 +#define DT_MODIFYSTRING 0x00010000 + +#define FW_NORMAL 400 +#define FW_BOLD 700 + +#define FF_DONTCARE (0<<4) + +#define BLACK_BRUSH 4 +#define NULL_BRUSH 5 + +#define PS_SOLID 0 +#define PS_DOT 2 + +#define TRANSPARENT 1 +#define OPAQUE 2 + +#define ANSI_CHARSET 0 +#define ANSI_VAR_FONT 12 + +#define OUT_DEFAULT_PRECIS 0 +#define CLIP_DEFAULT_PRECIS 0 + +#define PROOF_QUALITY 2 + +#define VARIABLE_PITCH 2 + +#define RGN_AND 1 +#define RGN_OR 2 +#define RGN_DIFF 4 +#define RGN_COPY 5 + +#define RDH_RECTANGLES 1 + +#define MAXLONG 0x7fffffff + +// define GUID +#include <bfc/platform/guid.h> + +#endif /* not WIN32 */ + +#include <stdio.h> +#include <stdlib.h> +#ifdef __cplusplus +#include <new> +#else +#include <new.h> +#endif +#include <limits.h> + + +#ifdef WIN32 +#define OSPIPE HANDLE +#define OSPROCESSID int +#endif + +// Ode macro keyworkds +#define DISPATCH_ // makes a method dispatchable, automatically assigns a free ID (requires Interface) +#define DISPATCH(x) // makes a method dispatchable and specifies its ID (not duplicate check, requires Interface) +#define NODISPATCH // prevents a method from being dispatched if the class is marked for dispatching by default +#define EVENT // marks a method as being an event to which other classes can connect to receive notification (used by Script and DependentItem helpers) +#define SCRIPT // exposes a method to script interfaces (requires Script helper) +#define IN // Input parameter +#define OUT // Output parameter +#define INOUT // Input/Output parameter + +#endif diff --git a/Src/Plugins/Input/in_wv/wasabi/bfc/platform/types.h b/Src/Plugins/Input/in_wv/wasabi/bfc/platform/types.h new file mode 100644 index 00000000..9de1d7b1 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/bfc/platform/types.h @@ -0,0 +1,78 @@ +#ifndef __WASABI_TYPES_H +#define __WASABI_TYPES_H + +// first, some standard int types +typedef unsigned int UINT; +typedef signed int SINT; + +typedef unsigned char UCHAR; +typedef signed char SCHAR; + +typedef unsigned long ARGB32; +typedef unsigned long RGB32; + +typedef unsigned long ARGB24; +typedef unsigned long RGB24; + +typedef unsigned short ARGB16; +typedef unsigned short RGB16; + +typedef unsigned long FOURCC; + +#ifndef GUID_DEFINED + #define GUID_DEFINED + + typedef struct _GUID + { + unsigned long Data1; + unsigned short Data2; + unsigned short Data3; + unsigned char Data4[8]; + } GUID; +/* +#ifndef _REFCLSID_DEFINED +#define REFGUID const GUID & +#define _REFCLSID_DEFINED +#endif +*/ +#endif + +#if defined(_WIN32) && !defined(__GNUC__) +#include <stddef.h> + // since windows doesn't have stdint.h + typedef unsigned __int64 uint64_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int8 uint8_t; + typedef __int64 int64_t; + typedef __int32 int32_t; + typedef __int16 int16_t; + typedef __int8 int8_t; + typedef size_t ssize_t; +#else +#include <stdint.h> +#include <stddef.h> +#include <inttypes.h> +#endif + +#ifdef WIN32 +#include <windows.h> +// this is for GUID == and != +#include <objbase.h> +#ifndef GUID_EQUALS_DEFINED + #define GUID_EQUALS_DEFINED +#endif + + +#ifdef NULL + #undef NULL +#endif +#ifndef NULL + #define NULL 0 +#endif + +#ifdef _WIN32_WCE +typedef int intptr_t; +#endif +#endif +#endif diff --git a/Src/Plugins/Input/in_wv/wasabi/bfc/platform/win32.h b/Src/Plugins/Input/in_wv/wasabi/bfc/platform/win32.h new file mode 100644 index 00000000..16e61bee --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/bfc/platform/win32.h @@ -0,0 +1,38 @@ +#ifndef _WIN32_H +#define _WIN32_H + +#ifndef WIN32 +#error this file is only for win32 +#endif + +#ifndef _PLATFORM_H +#error this file should only be included from platform.h +#endif + +// this should be the *only* place windows.h gets included! +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#ifndef _WIN32_WCE +#include <io.h> +#endif + +#if defined(_MSC_VER) // msvc +# define WASABIDLLEXPORT __declspec(dllexport) +# if _MSC_VER >= 1100 +# define NOVTABLE __declspec(novtable) +# endif +#endif + +#define _TRY __try +#define _EXCEPT(x) __except(x) + +#define OSPIPE HANDLE + + +typedef WCHAR OSFNCHAR; +typedef LPWSTR OSFNSTR; +typedef LPCWSTR OSFNCSTR; + +#endif diff --git a/Src/Plugins/Input/in_wv/wasabi/bfc/std_mkncc.h b/Src/Plugins/Input/in_wv/wasabi/bfc/std_mkncc.h new file mode 100644 index 00000000..113e464d --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/bfc/std_mkncc.h @@ -0,0 +1,11 @@ +#ifndef _STD_MKNCC +#define _STD_MKNCC + +// note: this is endian-incompatible with win32's MAKEFOURCC +// otoh, it shows up nicely in a debug register ;) + +#define MK4CC(a, b, c, d) ( (((unsigned long)a)<<24)|(((unsigned long)b)<<16)|(((unsigned long)c)<<8)|((unsigned long)d) ) +#define MK3CC(b, c, d) ( (((unsigned long)b)<<16)|(((unsigned long)c)<<8)|((unsigned long)d) ) +#define MK2CC(c, d) ( (((unsigned long)c)<<8)|((unsigned long)d) ) + +#endif diff --git a/Src/Plugins/Input/in_wv/wasabi/nu/AutoChar.h b/Src/Plugins/Input/in_wv/wasabi/nu/AutoChar.h new file mode 100644 index 00000000..646df5d3 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/nu/AutoChar.h @@ -0,0 +1,133 @@ +#ifndef NULLSOFT_AUTOCHARH +#define NULLSOFT_AUTOCHARH +#ifdef WIN32 +#include <windows.h> + +inline char *AutoCharDupN(const wchar_t *convert, size_t len, UINT codePage = CP_ACP, UINT flags=0) +{ + if (!convert) + return 0; + int size = WideCharToMultiByte(codePage, flags, convert, (int)len, 0, 0, NULL, NULL); + + if (!size) + return 0; + + char *narrow = (char *)malloc((size+1)*sizeof(char)); + + if (!WideCharToMultiByte(codePage, flags, convert, (int)len, narrow, size, NULL, NULL)) + { + free(narrow); + narrow=0; + } + else + narrow[size]=0; + + return narrow; +} + +inline char *AutoCharDup(const wchar_t *convert, UINT codePage = CP_ACP, UINT flags=0) +{ + if (!convert) + return 0; + int size = WideCharToMultiByte(codePage, flags, convert, -1, 0, 0, NULL, NULL); + + if (!size) + return 0; + + char *narrow = (char *)malloc(size*sizeof(char)); + + if (!WideCharToMultiByte(codePage, flags, convert, -1, narrow, size, NULL, NULL)) + { + free(narrow); + narrow=0; + } + return narrow; +} + +class AutoChar +{ +public: + AutoChar(const wchar_t *convert, UINT codePage = CP_ACP, UINT flags=0) : narrow(0) + { + narrow = AutoCharDup(convert, codePage, flags); + } + ~AutoChar() + { + free(narrow); + narrow=0; + } + operator const char *() + { + return narrow; + } + operator char *() + { + return narrow; + } +protected: + AutoChar() : narrow(0) + { + } + char *narrow; +}; + +class AutoCharN : public AutoChar +{ +public: + AutoCharN(const wchar_t *convert, size_t len, UINT codePage = CP_ACP, UINT flags=0) + { + narrow = AutoCharDupN(convert, len, codePage, flags); + } +}; +#else +#include <stdlib.h> +#include <wchar.h> + +inline char *AutoCharDup(const wchar_t *convert) +{ + if (!convert) + return 0; + + size_t size = wcslen(convert)+1; + + if (!size) + return 0; + + char *narrow = (char *)malloc(size*sizeof(char)); + + if (wcstombs(narrow, convert, size) == (size_t)-1) + { + free(narrow); + narrow=0; + } + return narrow; +} + + +class AutoChar +{ +public: + + AutoChar(const wchar_t *convert) : narrow(0) + { + narrow = AutoCharDup(convert); + } + ~AutoChar() + { + free(narrow); + narrow=0; + } + operator const char *() + { + return narrow; + } + operator char *() + { + return narrow; + } +private: + char *narrow; +}; +#endif + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/wasabi/nu/AutoWide.h b/Src/Plugins/Input/in_wv/wasabi/nu/AutoWide.h new file mode 100644 index 00000000..26f3edb1 --- /dev/null +++ b/Src/Plugins/Input/in_wv/wasabi/nu/AutoWide.h @@ -0,0 +1,86 @@ +#ifndef AUTOWIDEH +#define AUTOWIDEH +#ifdef WIN32 +#include <windows.h> + +inline wchar_t *AutoWideDup(const char *convert, UINT codePage=CP_ACP) +{ + wchar_t *wide = 0; + if (!convert) + return 0; + + int size = MultiByteToWideChar(codePage, 0, convert, -1, 0,0); + if (!size) + return 0; + + wide = (wchar_t *)malloc(size*sizeof(wchar_t)); + if (!MultiByteToWideChar(codePage, 0, convert, -1, wide,size)) + { + free(wide); + wide=0; + } + return wide; +} + +class AutoWide +{ +public: + AutoWide(const char *convert, UINT codePage=CP_ACP) : wide(0) + { + wide = AutoWideDup(convert, codePage); + } + ~AutoWide() + { + free(wide); + wide=0; + } + operator wchar_t *() + { + return wide; + } +private: + wchar_t *wide; +}; +#elif defined(__APPLE__) +#include <string.h> +inline wchar_t *AutoWideDup(const char *convert) +{ + wchar_t *wide=0; + if (!convert) + return 0; + int size = strlen(convert)+1; + if (!size) + return 0; + + wide = (wchar_t *)malloc(size*sizeof(wchar_t)); + if (mbstowcs(wide, convert, size) == (size_t)-1) + { + free(wide); + wide=0; + } + return wide; +} + +class AutoWide +{ +public: + AutoWide(const char *convert) : wide(0) + { + wide = AutoWideDup(convert); + } + ~AutoWide() + { + free(wide); + wide=0; + } + operator wchar_t *() + { + return wide; + } +private: + wchar_t *wide; +}; + +#endif + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_wv/wavpack.rc b/Src/Plugins/Input/in_wv/wavpack.rc new file mode 100644 index 00000000..d635263b --- /dev/null +++ b/Src/Plugins/Input/in_wv/wavpack.rc @@ -0,0 +1,118 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winresrc.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Neutral (Default) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEUD) +#ifdef _WIN32 +LANGUAGE LANG_NEUTRAL, SUBLANG_DEFAULT +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_ABOUT "WavPack Decoder" + IDS_ENCODER_VERSION "WavPack encoder version: %d\n" + IDS_SOURCE "Source: %d-bit %s at %d Hz \n" + IDS_MULTICHANNEL "Channels: %d (multichannel)\n" + IDS_MONO "Channels: 1 (mono)\n" + IDS_STEREO "Channels: 2 (stereo)\n" + IDS_HYBRID "hybrid" + IDS_LOSSLESS "lossless" + IDS_LOSSY "lossy" + IDS_INTS "ints" + IDS_FLOATS "floats" + IDS_MODES "Modes" + IDS_FAST ", fast" + IDS_HIGH ", high" + IDS_VHIGH ", v.high" +END + +STRINGTABLE +BEGIN + IDS_EXTRA ", extra" + IDS_BITRATE "Average bitrate" + IDS_RATIO "Overall ratio" + IDS_KBPS "kbps" + IDS_MD5 "Original md5" + IDS_DESCRIPTION "WavPack Decoder v%s" + IDS_FILETYPE "WavPack Files (*.WV)" + IDS_ABOUT_MESSAGE "WavPack Decoder v%hs\nCopyright (c) %hs Conifer Software\n\nBuild date: %hs\n\nSee http://www.wavpack.com for information." + IDS_FAMILY_STRING "WavPack File" +END + +STRINGTABLE +BEGIN + IDS_GUID "{6DE2E465-690E-4df1-B6E2-2A9B33ED3DBB}" +END + +#endif // Neutral (Default) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// 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 ""winresrc.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/Src/Plugins/Input/in_wv/winamp.vcproj b/Src/Plugins/Input/in_wv/winamp.vcproj new file mode 100644 index 00000000..528cebb4 --- /dev/null +++ b/Src/Plugins/Input/in_wv/winamp.vcproj @@ -0,0 +1,237 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="9.00" + Name="winamp" + ProjectGUID="{1DB9C3E7-D670-46EB-B006-CCF94982D484}" + RootNamespace="winamp" + Keyword="Win32Proj" + TargetFrameworkVersion="131072" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="$(SolutionDir)$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="..\include" + PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;WINAMP_EXPORTS;_CRT_SECURE_NO_DEPRECATE" + MinimalRebuild="true" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + OutputFile="$(OutDir)\in_wv.dll" + LinkIncremental="2" + GenerateDebugInformation="true" + SubSystem="2" + RandomizedBaseAddress="1" + DataExecutionPrevention="0" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="$(SolutionDir)$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + InlineFunctionExpansion="2" + EnableIntrinsicFunctions="true" + FavorSizeOrSpeed="1" + OmitFramePointers="true" + AdditionalIncludeDirectories="..\include" + PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE" + StringPooling="true" + RuntimeLibrary="2" + BufferSecurityCheck="false" + EnableFunctionLevelLinking="true" + RuntimeTypeInfo="false" + WarningLevel="3" + DebugInformationFormat="0" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + OutputFile="$(OutDir)\in_wv.dll" + LinkIncremental="1" + GenerateManifest="false" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="false" + SubSystem="2" + OptimizeReferences="2" + EnableCOMDATFolding="2" + EntryPointSymbol="" + RandomizedBaseAddress="1" + DataExecutionPrevention="0" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCPostBuildEventTool" + CommandLine="lng_generator.exe "$(OutDir)\in_wv.dll" /build
copy "$(OutDir)\in_wv.dll" "$(ProgramFiles)\Winamp\plugins" /y
" + /> + </Configuration> + </Configurations> + <References> + <ProjectReference + ReferencedProjectIdentifier="{5CCCB9CF-0384-458F-BA08-72B73866840F}" + /> + </References> + <Files> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" + > + <File + RelativePath=".\in2.h" + > + </File> + <File + RelativePath=".\out.h" + > + </File> + <File + RelativePath=".\resource.h" + > + </File> + <File + RelativePath=".\wasabi\Wasabi.h" + > + </File> + <File + RelativePath="..\include\wavpack.h" + > + </File> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}" + > + <File + RelativePath=".\wavpack.rc" + > + </File> + </Filter> + <Filter + Name="Source Files" + Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" + > + <File + RelativePath=".\in_wv.cpp" + > + </File> + <File + RelativePath=".\wasabi\Wasabi.cpp" + > + </File> + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> |