diff options
Diffstat (limited to 'Src/Plugins/Input/in_mkv')
21 files changed, 2867 insertions, 0 deletions
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 |