Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wine-winehq
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wine
wine-winehq
Commits
e8c4c1db
Commit
e8c4c1db
authored
Mar 22, 2019
by
Nikolay Sivov
Committed by
Alexandre Julliard
Mar 22, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
mfplat: Implement IStream-based bytestream object.
Signed-off-by:
Nikolay Sivov
<
nsivov@codeweavers.com
>
Signed-off-by:
Alexandre Julliard
<
julliard@winehq.org
>
parent
16477a09
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
726 additions
and
66 deletions
+726
-66
main.c
dlls/mfplat/main.c
+587
-62
mfplat.c
dlls/mfplat/tests/mfplat.c
+96
-4
mfidl.idl
include/mfidl.idl
+42
-0
mfobjects.idl
include/mfobjects.idl
+1
-0
No files found.
dlls/mfplat/main.c
View file @
e8c4c1db
...
...
@@ -1791,10 +1791,17 @@ HRESULT WINAPI MFInitAttributesFromBlob(IMFAttributes *dest, const UINT8 *buffer
return
hr
;
}
typedef
struct
_mf
bytestream
typedef
struct
bytestream
{
struct
attributes
attributes
;
IMFByteStream
IMFByteStream_iface
;
IMFAsyncCallback
read_callback
;
IMFAsyncCallback
write_callback
;
IStream
*
stream
;
QWORD
position
;
DWORD
capabilities
;
struct
list
pending
;
CRITICAL_SECTION
cs
;
}
mfbytestream
;
static
inline
mfbytestream
*
impl_from_IMFByteStream
(
IMFByteStream
*
iface
)
...
...
@@ -1802,20 +1809,209 @@ static inline mfbytestream *impl_from_IMFByteStream(IMFByteStream *iface)
return
CONTAINING_RECORD
(
iface
,
mfbytestream
,
IMFByteStream_iface
);
}
static
HRESULT
WINAPI
mfbytestream_QueryInterface
(
IMFByteStream
*
iface
,
REFIID
riid
,
void
**
out
)
static
struct
bytestream
*
impl_from_read_callback_IMFAsyncCallback
(
IMFAsyncCallback
*
iface
)
{
mfbytestream
*
This
=
impl_from_IMFByteStream
(
iface
);
return
CONTAINING_RECORD
(
iface
,
struct
bytestream
,
read_callback
);
}
TRACE
(
"(%p)->(%s %p)
\n
"
,
This
,
debugstr_guid
(
riid
),
out
);
static
struct
bytestream
*
impl_from_write_callback_IMFAsyncCallback
(
IMFAsyncCallback
*
iface
)
{
return
CONTAINING_RECORD
(
iface
,
struct
bytestream
,
write_callback
);
}
if
(
IsEqualGUID
(
riid
,
&
IID_IUnknown
)
||
IsEqualGUID
(
riid
,
&
IID_IMFByteStream
))
enum
async_stream_op_type
{
ASYNC_STREAM_OP_READ
,
ASYNC_STREAM_OP_WRITE
,
};
struct
async_stream_op
{
IUnknown
IUnknown_iface
;
LONG
refcount
;
union
{
const
BYTE
*
src
;
BYTE
*
dest
;
}
u
;
ULONG
requested_length
;
ULONG
actual_length
;
IMFAsyncResult
*
caller
;
struct
list
entry
;
enum
async_stream_op_type
type
;
};
static
struct
async_stream_op
*
impl_async_stream_op_from_IUnknown
(
IUnknown
*
iface
)
{
return
CONTAINING_RECORD
(
iface
,
struct
async_stream_op
,
IUnknown_iface
);
}
static
HRESULT
WINAPI
async_stream_op_QueryInterface
(
IUnknown
*
iface
,
REFIID
riid
,
void
**
obj
)
{
if
(
IsEqualIID
(
riid
,
&
IID_IUnknown
))
{
*
obj
=
iface
;
IUnknown_AddRef
(
iface
);
return
S_OK
;
}
WARN
(
"Unsupported %s.
\n
"
,
debugstr_guid
(
riid
));
*
obj
=
NULL
;
return
E_NOINTERFACE
;
}
static
ULONG
WINAPI
async_stream_op_AddRef
(
IUnknown
*
iface
)
{
struct
async_stream_op
*
op
=
impl_async_stream_op_from_IUnknown
(
iface
);
ULONG
refcount
=
InterlockedIncrement
(
&
op
->
refcount
);
TRACE
(
"%p, refcount %d.
\n
"
,
iface
,
refcount
);
return
refcount
;
}
static
ULONG
WINAPI
async_stream_op_Release
(
IUnknown
*
iface
)
{
struct
async_stream_op
*
op
=
impl_async_stream_op_from_IUnknown
(
iface
);
ULONG
refcount
=
InterlockedDecrement
(
&
op
->
refcount
);
TRACE
(
"%p, refcount %d.
\n
"
,
iface
,
refcount
);
if
(
!
refcount
)
{
if
(
op
->
caller
)
IMFAsyncResult_Release
(
op
->
caller
);
heap_free
(
op
);
}
return
refcount
;
}
static
const
IUnknownVtbl
async_stream_op_vtbl
=
{
async_stream_op_QueryInterface
,
async_stream_op_AddRef
,
async_stream_op_Release
,
};
static
HRESULT
bytestream_create_io_request
(
struct
bytestream
*
stream
,
enum
async_stream_op_type
type
,
const
BYTE
*
data
,
ULONG
size
,
IMFAsyncCallback
*
callback
,
IUnknown
*
state
)
{
struct
async_stream_op
*
op
;
IMFAsyncResult
*
request
;
HRESULT
hr
;
op
=
heap_alloc
(
sizeof
(
*
op
));
if
(
!
op
)
return
E_OUTOFMEMORY
;
op
->
IUnknown_iface
.
lpVtbl
=
&
async_stream_op_vtbl
;
op
->
refcount
=
1
;
op
->
u
.
src
=
data
;
op
->
requested_length
=
size
;
op
->
type
=
type
;
if
(
FAILED
(
hr
=
MFCreateAsyncResult
((
IUnknown
*
)
&
stream
->
IMFByteStream_iface
,
callback
,
state
,
&
op
->
caller
)))
goto
failed
;
if
(
FAILED
(
hr
=
MFCreateAsyncResult
(
&
op
->
IUnknown_iface
,
type
==
ASYNC_STREAM_OP_READ
?
&
stream
->
read_callback
:
&
stream
->
write_callback
,
NULL
,
&
request
)))
goto
failed
;
MFPutWorkItemEx
(
MFASYNC_CALLBACK_QUEUE_STANDARD
,
request
);
IMFAsyncResult_Release
(
request
);
failed:
IUnknown_Release
(
&
op
->
IUnknown_iface
);
return
hr
;
}
static
HRESULT
bytestream_complete_io_request
(
struct
bytestream
*
stream
,
enum
async_stream_op_type
type
,
IMFAsyncResult
*
result
,
ULONG
*
actual_length
)
{
struct
async_stream_op
*
op
=
NULL
,
*
cur
;
HRESULT
hr
;
EnterCriticalSection
(
&
stream
->
cs
);
LIST_FOR_EACH_ENTRY
(
cur
,
&
stream
->
pending
,
struct
async_stream_op
,
entry
)
{
if
(
cur
->
caller
==
result
&&
cur
->
type
==
type
)
{
op
=
cur
;
list_remove
(
&
cur
->
entry
);
break
;
}
}
LeaveCriticalSection
(
&
stream
->
cs
);
if
(
!
op
)
return
E_INVALIDARG
;
if
(
SUCCEEDED
(
hr
=
IMFAsyncResult_GetStatus
(
result
)))
*
actual_length
=
op
->
actual_length
;
IUnknown_Release
(
&
op
->
IUnknown_iface
);
return
hr
;
}
static
HRESULT
WINAPI
bytestream_callback_QueryInterface
(
IMFAsyncCallback
*
iface
,
REFIID
riid
,
void
**
obj
)
{
if
(
IsEqualIID
(
riid
,
&
IID_IMFAsyncCallback
)
||
IsEqualIID
(
riid
,
&
IID_IUnknown
))
{
*
obj
=
iface
;
IMFAsyncCallback_AddRef
(
iface
);
return
S_OK
;
}
WARN
(
"Unsupported %s.
\n
"
,
debugstr_guid
(
riid
));
*
obj
=
NULL
;
return
E_NOINTERFACE
;
}
static
ULONG
WINAPI
bytestream_read_callback_AddRef
(
IMFAsyncCallback
*
iface
)
{
struct
bytestream
*
stream
=
impl_from_read_callback_IMFAsyncCallback
(
iface
);
return
IMFByteStream_AddRef
(
&
stream
->
IMFByteStream_iface
);
}
static
ULONG
WINAPI
bytestream_read_callback_Release
(
IMFAsyncCallback
*
iface
)
{
struct
bytestream
*
stream
=
impl_from_read_callback_IMFAsyncCallback
(
iface
);
return
IMFByteStream_Release
(
&
stream
->
IMFByteStream_iface
);
}
static
HRESULT
WINAPI
bytestream_callback_GetParameters
(
IMFAsyncCallback
*
iface
,
DWORD
*
flags
,
DWORD
*
queue
)
{
return
E_NOTIMPL
;
}
static
ULONG
WINAPI
bytestream_write_callback_AddRef
(
IMFAsyncCallback
*
iface
)
{
struct
bytestream
*
stream
=
impl_from_write_callback_IMFAsyncCallback
(
iface
);
return
IMFByteStream_AddRef
(
&
stream
->
IMFByteStream_iface
);
}
static
ULONG
WINAPI
bytestream_write_callback_Release
(
IMFAsyncCallback
*
iface
)
{
struct
bytestream
*
stream
=
impl_from_write_callback_IMFAsyncCallback
(
iface
);
return
IMFByteStream_Release
(
&
stream
->
IMFByteStream_iface
);
}
static
HRESULT
WINAPI
bytestream_QueryInterface
(
IMFByteStream
*
iface
,
REFIID
riid
,
void
**
out
)
{
struct
bytestream
*
stream
=
impl_from_IMFByteStream
(
iface
);
TRACE
(
"%p, %s, %p.
\n
"
,
iface
,
debugstr_guid
(
riid
),
out
);
if
(
IsEqualIID
(
riid
,
&
IID_IMFByteStream
)
||
IsEqualIID
(
riid
,
&
IID_IUnknown
))
{
*
out
=
&
This
->
IMFByteStream_iface
;
*
out
=
&
stream
->
IMFByteStream_iface
;
}
else
if
(
IsEqualGU
ID
(
riid
,
&
IID_IMFAttributes
))
else
if
(
IsEqualI
ID
(
riid
,
&
IID_IMFAttributes
))
{
*
out
=
&
This
->
attributes
.
IMFAttributes_iface
;
*
out
=
&
stream
->
attributes
.
IMFAttributes_iface
;
}
else
{
...
...
@@ -1828,46 +2024,55 @@ static HRESULT WINAPI mfbytestream_QueryInterface(IMFByteStream *iface, REFIID r
return
S_OK
;
}
static
ULONG
WINAPI
mf
bytestream_AddRef
(
IMFByteStream
*
iface
)
static
ULONG
WINAPI
bytestream_AddRef
(
IMFByteStream
*
iface
)
{
mfbytestream
*
This
=
impl_from_IMFByteStream
(
iface
);
ULONG
ref
=
InterlockedIncrement
(
&
This
->
attributes
.
ref
);
struct
bytestream
*
stream
=
impl_from_IMFByteStream
(
iface
);
ULONG
ref
count
=
InterlockedIncrement
(
&
stream
->
attributes
.
ref
);
TRACE
(
"
(%p) ref=%u
\n
"
,
This
,
ref
);
TRACE
(
"
%p, refcount %d.
\n
"
,
iface
,
refcount
);
return
ref
;
return
ref
count
;
}
static
ULONG
WINAPI
mf
bytestream_Release
(
IMFByteStream
*
iface
)
static
ULONG
WINAPI
bytestream_Release
(
IMFByteStream
*
iface
)
{
mfbytestream
*
This
=
impl_from_IMFByteStream
(
iface
);
ULONG
ref
=
InterlockedDecrement
(
&
This
->
attributes
.
ref
);
struct
bytestream
*
stream
=
impl_from_IMFByteStream
(
iface
);
ULONG
refcount
=
InterlockedDecrement
(
&
stream
->
attributes
.
ref
);
struct
async_stream_op
*
cur
,
*
cur2
;
TRACE
(
"
(%p) ref=%u
\n
"
,
This
,
ref
);
TRACE
(
"
%p, refcount %d.
\n
"
,
iface
,
refcount
);
if
(
!
ref
)
if
(
!
ref
count
)
{
clear_attributes_object
(
&
This
->
attributes
);
HeapFree
(
GetProcessHeap
(),
0
,
This
);
clear_attributes_object
(
&
stream
->
attributes
);
LIST_FOR_EACH_ENTRY_SAFE
(
cur
,
cur2
,
&
stream
->
pending
,
struct
async_stream_op
,
entry
)
{
list_remove
(
&
cur
->
entry
);
IUnknown_Release
(
&
cur
->
IUnknown_iface
);
}
DeleteCriticalSection
(
&
stream
->
cs
);
if
(
stream
->
stream
)
IStream_Release
(
stream
->
stream
);
heap_free
(
stream
);
}
return
ref
;
return
ref
count
;
}
static
HRESULT
WINAPI
mf
bytestream_GetCapabilities
(
IMFByteStream
*
iface
,
DWORD
*
capabilities
)
static
HRESULT
WINAPI
bytestream_GetCapabilities
(
IMFByteStream
*
iface
,
DWORD
*
capabilities
)
{
mfbytestream
*
This
=
impl_from_IMFByteStream
(
iface
);
struct
bytestream
*
stream
=
impl_from_IMFByteStream
(
iface
);
FIXME
(
"%p, %p
\n
"
,
This
,
capabilities
);
TRACE
(
"%p, %p.
\n
"
,
iface
,
capabilities
);
return
E_NOTIMPL
;
*
capabilities
=
stream
->
capabilities
;
return
S_OK
;
}
static
HRESULT
WINAPI
mfbytestream_GetLength
(
IMFByteStream
*
iface
,
QWORD
*
length
)
{
mfbytestream
*
This
=
impl_from_IMFByteStream
(
iface
);
FIXME
(
"%p, %p
\n
"
,
This
,
length
);
FIXME
(
"%p, %p.
\n
"
,
iface
,
length
);
return
E_NOTIMPL
;
}
...
...
@@ -1920,23 +2125,23 @@ static HRESULT WINAPI mfbytestream_Read(IMFByteStream *iface, BYTE *data, ULONG
return
E_NOTIMPL
;
}
static
HRESULT
WINAPI
mfbytestream_BeginRead
(
IMFByteStream
*
iface
,
BYTE
*
data
,
ULONG
count
,
IMFAsyncCallback
*
callback
,
IUnknown
*
state
)
static
HRESULT
WINAPI
bytestream_BeginRead
(
IMFByteStream
*
iface
,
BYTE
*
data
,
ULONG
size
,
IMFAsyncCallback
*
callback
,
IUnknown
*
state
)
{
mfbytestream
*
This
=
impl_from_IMFByteStream
(
iface
);
struct
bytestream
*
stream
=
impl_from_IMFByteStream
(
iface
);
FIXME
(
"%p, %p, %u, %p, %p
\n
"
,
This
,
data
,
count
,
callback
,
state
);
TRACE
(
"%p, %p, %u, %p, %p.
\n
"
,
iface
,
data
,
size
,
callback
,
state
);
return
E_NOTIMPL
;
return
bytestream_create_io_request
(
stream
,
ASYNC_STREAM_OP_READ
,
data
,
size
,
callback
,
state
)
;
}
static
HRESULT
WINAPI
mf
bytestream_EndRead
(
IMFByteStream
*
iface
,
IMFAsyncResult
*
result
,
ULONG
*
byte_read
)
static
HRESULT
WINAPI
bytestream_EndRead
(
IMFByteStream
*
iface
,
IMFAsyncResult
*
result
,
ULONG
*
byte_read
)
{
mfbytestream
*
This
=
impl_from_IMFByteStream
(
iface
);
struct
bytestream
*
stream
=
impl_from_IMFByteStream
(
iface
);
FIXME
(
"%p, %p, %p
\n
"
,
This
,
result
,
byte_read
);
TRACE
(
"%p, %p, %p.
\n
"
,
iface
,
result
,
byte_read
);
return
E_NOTIMPL
;
return
bytestream_complete_io_request
(
stream
,
ASYNC_STREAM_OP_READ
,
result
,
byte_read
)
;
}
static
HRESULT
WINAPI
mfbytestream_Write
(
IMFByteStream
*
iface
,
const
BYTE
*
data
,
ULONG
count
,
ULONG
*
written
)
...
...
@@ -1948,23 +2153,46 @@ static HRESULT WINAPI mfbytestream_Write(IMFByteStream *iface, const BYTE *data,
return
E_NOTIMPL
;
}
static
HRESULT
WINAPI
mfbytestream_BeginWrite
(
IMFByteStream
*
iface
,
const
BYTE
*
data
,
ULONG
count
,
IMFAsyncCallback
*
callback
,
IUnknown
*
state
)
static
HRESULT
WINAPI
bytestream_BeginWrite
(
IMFByteStream
*
iface
,
const
BYTE
*
data
,
ULONG
size
,
IMFAsyncCallback
*
callback
,
IUnknown
*
state
)
{
mfbytestream
*
This
=
impl_from_IMFByteStream
(
iface
);
struct
bytestream
*
stream
=
impl_from_IMFByteStream
(
iface
);
struct
async_stream_op
*
op
;
IMFAsyncResult
*
request
;
HRESULT
hr
;
FIXME
(
"%p, %p, %u, %p, %p
\n
"
,
This
,
data
,
count
,
callback
,
state
);
TRACE
(
"%p, %p, %u, %p, %p.
\n
"
,
iface
,
data
,
size
,
callback
,
state
);
return
E_NOTIMPL
;
op
=
heap_alloc
(
sizeof
(
*
op
));
if
(
!
op
)
return
E_OUTOFMEMORY
;
op
->
IUnknown_iface
.
lpVtbl
=
&
async_stream_op_vtbl
;
op
->
refcount
=
1
;
op
->
u
.
src
=
data
;
op
->
requested_length
=
size
;
op
->
type
=
ASYNC_STREAM_OP_WRITE
;
if
(
FAILED
(
hr
=
MFCreateAsyncResult
((
IUnknown
*
)
iface
,
callback
,
state
,
&
op
->
caller
)))
goto
failed
;
if
(
FAILED
(
hr
=
MFCreateAsyncResult
(
&
op
->
IUnknown_iface
,
&
stream
->
write_callback
,
NULL
,
&
request
)))
goto
failed
;
MFPutWorkItemEx
(
MFASYNC_CALLBACK_QUEUE_STANDARD
,
request
);
IMFAsyncResult_Release
(
request
);
failed:
IUnknown_Release
(
&
op
->
IUnknown_iface
);
return
hr
;
}
static
HRESULT
WINAPI
mf
bytestream_EndWrite
(
IMFByteStream
*
iface
,
IMFAsyncResult
*
result
,
ULONG
*
written
)
static
HRESULT
WINAPI
bytestream_EndWrite
(
IMFByteStream
*
iface
,
IMFAsyncResult
*
result
,
ULONG
*
written
)
{
mfbytestream
*
This
=
impl_from_IMFByteStream
(
iface
);
struct
bytestream
*
stream
=
impl_from_IMFByteStream
(
iface
);
FIXME
(
"%p, %p, %p
\n
"
,
This
,
result
,
written
);
TRACE
(
"%p, %p, %p.
\n
"
,
iface
,
result
,
written
);
return
E_NOTIMPL
;
return
bytestream_complete_io_request
(
stream
,
ASYNC_STREAM_OP_WRITE
,
result
,
written
)
;
}
static
HRESULT
WINAPI
mfbytestream_Seek
(
IMFByteStream
*
iface
,
MFBYTESTREAM_SEEK_ORIGIN
seek
,
LONGLONG
offset
,
...
...
@@ -1997,26 +2225,190 @@ static HRESULT WINAPI mfbytestream_Close(IMFByteStream *iface)
static
const
IMFByteStreamVtbl
mfbytestream_vtbl
=
{
mf
bytestream_QueryInterface
,
mf
bytestream_AddRef
,
mf
bytestream_Release
,
mf
bytestream_GetCapabilities
,
bytestream_QueryInterface
,
bytestream_AddRef
,
bytestream_Release
,
bytestream_GetCapabilities
,
mfbytestream_GetLength
,
mfbytestream_SetLength
,
mfbytestream_GetCurrentPosition
,
mfbytestream_SetCurrentPosition
,
mfbytestream_IsEndOfStream
,
mfbytestream_Read
,
mf
bytestream_BeginRead
,
mf
bytestream_EndRead
,
bytestream_BeginRead
,
bytestream_EndRead
,
mfbytestream_Write
,
mf
bytestream_BeginWrite
,
mf
bytestream_EndWrite
,
bytestream_BeginWrite
,
bytestream_EndWrite
,
mfbytestream_Seek
,
mfbytestream_Flush
,
mfbytestream_Close
};
static
HRESULT
WINAPI
bytestream_stream_GetLength
(
IMFByteStream
*
iface
,
QWORD
*
length
)
{
struct
bytestream
*
stream
=
impl_from_IMFByteStream
(
iface
);
STATSTG
statstg
;
HRESULT
hr
;
TRACE
(
"%p, %p.
\n
"
,
iface
,
length
);
if
(
FAILED
(
hr
=
IStream_Stat
(
stream
->
stream
,
&
statstg
,
STATFLAG_NONAME
)))
return
hr
;
*
length
=
statstg
.
cbSize
.
QuadPart
;
return
S_OK
;
}
static
HRESULT
WINAPI
bytestream_stream_SetLength
(
IMFByteStream
*
iface
,
QWORD
length
)
{
struct
bytestream
*
stream
=
impl_from_IMFByteStream
(
iface
);
ULARGE_INTEGER
size
;
TRACE
(
"%p, %s.
\n
"
,
iface
,
wine_dbgstr_longlong
(
length
));
size
.
QuadPart
=
length
;
return
IStream_SetSize
(
stream
->
stream
,
size
);
}
static
HRESULT
WINAPI
bytestream_stream_GetCurrentPosition
(
IMFByteStream
*
iface
,
QWORD
*
position
)
{
struct
bytestream
*
stream
=
impl_from_IMFByteStream
(
iface
);
TRACE
(
"%p, %p.
\n
"
,
iface
,
position
);
*
position
=
stream
->
position
;
return
S_OK
;
}
static
HRESULT
WINAPI
bytestream_stream_SetCurrentPosition
(
IMFByteStream
*
iface
,
QWORD
position
)
{
struct
bytestream
*
stream
=
impl_from_IMFByteStream
(
iface
);
TRACE
(
"%p, %s.
\n
"
,
iface
,
wine_dbgstr_longlong
(
position
));
stream
->
position
=
position
;
return
S_OK
;
}
static
HRESULT
WINAPI
bytestream_stream_IsEndOfStream
(
IMFByteStream
*
iface
,
BOOL
*
ret
)
{
struct
bytestream
*
stream
=
impl_from_IMFByteStream
(
iface
);
STATSTG
statstg
;
HRESULT
hr
;
TRACE
(
"%p, %p.
\n
"
,
iface
,
ret
);
if
(
FAILED
(
hr
=
IStream_Stat
(
stream
->
stream
,
&
statstg
,
STATFLAG_NONAME
)))
return
hr
;
*
ret
=
stream
->
position
>=
statstg
.
cbSize
.
QuadPart
;
return
S_OK
;
}
static
HRESULT
WINAPI
bytestream_stream_Read
(
IMFByteStream
*
iface
,
BYTE
*
buffer
,
ULONG
size
,
ULONG
*
read_len
)
{
struct
bytestream
*
stream
=
impl_from_IMFByteStream
(
iface
);
LARGE_INTEGER
position
;
HRESULT
hr
;
TRACE
(
"%p, %p, %u, %p.
\n
"
,
iface
,
buffer
,
size
,
read_len
);
position
.
QuadPart
=
stream
->
position
;
if
(
FAILED
(
hr
=
IStream_Seek
(
stream
->
stream
,
position
,
STREAM_SEEK_SET
,
NULL
)))
return
hr
;
if
(
SUCCEEDED
(
hr
=
IStream_Read
(
stream
->
stream
,
buffer
,
size
,
read_len
)))
stream
->
position
+=
*
read_len
;
return
hr
;
}
static
HRESULT
WINAPI
bytestream_stream_Write
(
IMFByteStream
*
iface
,
const
BYTE
*
buffer
,
ULONG
size
,
ULONG
*
written
)
{
struct
bytestream
*
stream
=
impl_from_IMFByteStream
(
iface
);
LARGE_INTEGER
position
;
HRESULT
hr
;
TRACE
(
"%p, %p, %u, %p.
\n
"
,
iface
,
buffer
,
size
,
written
);
position
.
QuadPart
=
stream
->
position
;
if
(
FAILED
(
hr
=
IStream_Seek
(
stream
->
stream
,
position
,
STREAM_SEEK_SET
,
NULL
)))
return
hr
;
if
(
SUCCEEDED
(
hr
=
IStream_Write
(
stream
->
stream
,
buffer
,
size
,
written
)))
stream
->
position
+=
*
written
;
return
hr
;
}
static
HRESULT
WINAPI
bytestream_stream_Seek
(
IMFByteStream
*
iface
,
MFBYTESTREAM_SEEK_ORIGIN
origin
,
LONGLONG
offset
,
DWORD
flags
,
QWORD
*
current
)
{
struct
bytestream
*
stream
=
impl_from_IMFByteStream
(
iface
);
TRACE
(
"%p, %u, %s, %#x, %p.
\n
"
,
iface
,
origin
,
wine_dbgstr_longlong
(
offset
),
flags
,
current
);
switch
(
origin
)
{
case
msoBegin
:
stream
->
position
=
offset
;
break
;
case
msoCurrent
:
stream
->
position
+=
offset
;
break
;
default:
WARN
(
"Unknown origin mode %d.
\n
"
,
origin
);
return
E_INVALIDARG
;
}
*
current
=
stream
->
position
;
return
S_OK
;
}
static
HRESULT
WINAPI
bytestream_stream_Flush
(
IMFByteStream
*
iface
)
{
struct
bytestream
*
stream
=
impl_from_IMFByteStream
(
iface
);
TRACE
(
"%p.
\n
"
,
iface
);
return
IStream_Commit
(
stream
->
stream
,
STGC_DEFAULT
);
}
static
HRESULT
WINAPI
bytestream_stream_Close
(
IMFByteStream
*
iface
)
{
TRACE
(
"%p.
\n
"
,
iface
);
return
S_OK
;
}
static
const
IMFByteStreamVtbl
bytestream_stream_vtbl
=
{
bytestream_QueryInterface
,
bytestream_AddRef
,
bytestream_Release
,
bytestream_GetCapabilities
,
bytestream_stream_GetLength
,
bytestream_stream_SetLength
,
bytestream_stream_GetCurrentPosition
,
bytestream_stream_SetCurrentPosition
,
bytestream_stream_IsEndOfStream
,
bytestream_stream_Read
,
bytestream_BeginRead
,
bytestream_EndRead
,
bytestream_stream_Write
,
bytestream_BeginWrite
,
bytestream_EndWrite
,
bytestream_stream_Seek
,
bytestream_stream_Flush
,
bytestream_stream_Close
,
};
static
inline
mfbytestream
*
impl_from_IMFByteStream_IMFAttributes
(
IMFAttributes
*
iface
)
{
return
CONTAINING_RECORD
(
iface
,
mfbytestream
,
attributes
.
IMFAttributes_iface
);
...
...
@@ -2078,15 +2470,99 @@ static const IMFAttributesVtbl mfbytestream_attributes_vtbl =
mfattributes_CopyAllItems
};
static
HRESULT
WINAPI
bytestream_stream_read_callback_Invoke
(
IMFAsyncCallback
*
iface
,
IMFAsyncResult
*
result
)
{
struct
bytestream
*
stream
=
impl_from_read_callback_IMFAsyncCallback
(
iface
);
struct
async_stream_op
*
op
;
LARGE_INTEGER
position
;
IUnknown
*
object
;
HRESULT
hr
;
if
(
FAILED
(
hr
=
IMFAsyncResult_GetObject
(
result
,
&
object
)))
return
hr
;
op
=
impl_async_stream_op_from_IUnknown
(
object
);
position
.
QuadPart
=
stream
->
position
;
if
(
SUCCEEDED
(
hr
=
IStream_Seek
(
stream
->
stream
,
position
,
STREAM_SEEK_SET
,
NULL
)))
{
if
(
SUCCEEDED
(
hr
=
IStream_Read
(
stream
->
stream
,
op
->
u
.
dest
,
op
->
requested_length
,
&
op
->
actual_length
)))
stream
->
position
+=
op
->
actual_length
;
}
IMFAsyncResult_SetStatus
(
op
->
caller
,
hr
);
EnterCriticalSection
(
&
stream
->
cs
);
list_add_tail
(
&
stream
->
pending
,
&
op
->
entry
);
LeaveCriticalSection
(
&
stream
->
cs
);
MFInvokeCallback
(
op
->
caller
);
return
S_OK
;
}
static
HRESULT
WINAPI
bytestream_stream_write_callback_Invoke
(
IMFAsyncCallback
*
iface
,
IMFAsyncResult
*
result
)
{
struct
bytestream
*
stream
=
impl_from_read_callback_IMFAsyncCallback
(
iface
);
struct
async_stream_op
*
op
;
LARGE_INTEGER
position
;
IUnknown
*
object
;
HRESULT
hr
;
if
(
FAILED
(
hr
=
IMFAsyncResult_GetObject
(
result
,
&
object
)))
return
hr
;
op
=
impl_async_stream_op_from_IUnknown
(
object
);
position
.
QuadPart
=
stream
->
position
;
if
(
SUCCEEDED
(
hr
=
IStream_Seek
(
stream
->
stream
,
position
,
STREAM_SEEK_SET
,
NULL
)))
{
if
(
SUCCEEDED
(
hr
=
IStream_Write
(
stream
->
stream
,
op
->
u
.
src
,
op
->
requested_length
,
&
op
->
actual_length
)))
stream
->
position
+=
op
->
actual_length
;
}
IMFAsyncResult_SetStatus
(
op
->
caller
,
hr
);
EnterCriticalSection
(
&
stream
->
cs
);
list_add_tail
(
&
stream
->
pending
,
&
op
->
entry
);
LeaveCriticalSection
(
&
stream
->
cs
);
MFInvokeCallback
(
op
->
caller
);
return
S_OK
;
}
static
const
IMFAsyncCallbackVtbl
bytestream_stream_read_callback_vtbl
=
{
bytestream_callback_QueryInterface
,
bytestream_read_callback_AddRef
,
bytestream_read_callback_Release
,
bytestream_callback_GetParameters
,
bytestream_stream_read_callback_Invoke
,
};
static
const
IMFAsyncCallbackVtbl
bytestream_stream_write_callback_vtbl
=
{
bytestream_callback_QueryInterface
,
bytestream_write_callback_AddRef
,
bytestream_write_callback_Release
,
bytestream_callback_GetParameters
,
bytestream_stream_write_callback_Invoke
,
};
/***********************************************************************
* MFCreateMFByteStreamOnStream (mfplat.@)
*/
HRESULT
WINAPI
MFCreateMFByteStreamOnStream
(
IStream
*
stream
,
IMFByteStream
**
bytestream
)
{
mfbytestream
*
object
;
struct
bytestream
*
object
;
LARGE_INTEGER
position
;
HRESULT
hr
;
TRACE
(
"
(%p, %p): stub
\n
"
,
stream
,
bytestream
);
TRACE
(
"
%p, %p.
\n
"
,
stream
,
bytestream
);
object
=
heap_alloc
(
sizeof
(
*
object
)
);
if
(
!
object
)
object
=
heap_alloc
_zero
(
sizeof
(
*
object
)
);
if
(
!
object
)
return
E_OUTOFMEMORY
;
if
(
FAILED
(
hr
=
init_attributes_object
(
&
object
->
attributes
,
0
)))
...
...
@@ -2094,14 +2570,59 @@ HRESULT WINAPI MFCreateMFByteStreamOnStream(IStream *stream, IMFByteStream **byt
heap_free
(
object
);
return
hr
;
}
object
->
IMFByteStream_iface
.
lpVtbl
=
&
mfbytestream_vtbl
;
object
->
IMFByteStream_iface
.
lpVtbl
=
&
bytestream_stream_vtbl
;
object
->
attributes
.
IMFAttributes_iface
.
lpVtbl
=
&
mfbytestream_attributes_vtbl
;
object
->
read_callback
.
lpVtbl
=
&
bytestream_stream_read_callback_vtbl
;
object
->
write_callback
.
lpVtbl
=
&
bytestream_stream_write_callback_vtbl
;
InitializeCriticalSection
(
&
object
->
cs
);
list_init
(
&
object
->
pending
);
object
->
stream
=
stream
;
IStream_AddRef
(
object
->
stream
);
position
.
QuadPart
=
0
;
IStream_Seek
(
object
->
stream
,
position
,
STREAM_SEEK_SET
,
NULL
);
*
bytestream
=
&
object
->
IMFByteStream_iface
;
return
S_OK
;
}
static
HRESULT
WINAPI
bytestream_file_read_callback_Invoke
(
IMFAsyncCallback
*
iface
,
IMFAsyncResult
*
result
)
{
FIXME
(
"%p, %p.
\n
"
,
iface
,
result
);
return
E_NOTIMPL
;
}
static
HRESULT
WINAPI
bytestream_file_write_callback_Invoke
(
IMFAsyncCallback
*
iface
,
IMFAsyncResult
*
result
)
{
FIXME
(
"%p, %p.
\n
"
,
iface
,
result
);
return
E_NOTIMPL
;
}
static
const
IMFAsyncCallbackVtbl
bytestream_file_read_callback_vtbl
=
{
bytestream_callback_QueryInterface
,
bytestream_read_callback_AddRef
,
bytestream_read_callback_Release
,
bytestream_callback_GetParameters
,
bytestream_file_read_callback_Invoke
,
};
static
const
IMFAsyncCallbackVtbl
bytestream_file_write_callback_vtbl
=
{
bytestream_callback_QueryInterface
,
bytestream_write_callback_AddRef
,
bytestream_write_callback_Release
,
bytestream_callback_GetParameters
,
bytestream_file_write_callback_Invoke
,
};
/***********************************************************************
* MFCreateFile (mfplat.@)
*/
HRESULT
WINAPI
MFCreateFile
(
MF_FILE_ACCESSMODE
accessmode
,
MF_FILE_OPENMODE
openmode
,
MF_FILE_FLAGS
flags
,
LPCWSTR
url
,
IMFByteStream
**
bytestream
)
{
...
...
@@ -2161,8 +2682,8 @@ HRESULT WINAPI MFCreateFile(MF_FILE_ACCESSMODE accessmode, MF_FILE_OPENMODE open
/* Close the file again, since we don't do anything with it yet */
CloseHandle
(
file
);
object
=
heap_alloc
(
sizeof
(
*
object
)
);
if
(
!
object
)
object
=
heap_alloc
_zero
(
sizeof
(
*
object
)
);
if
(
!
object
)
return
E_OUTOFMEMORY
;
if
(
FAILED
(
hr
=
init_attributes_object
(
&
object
->
attributes
,
0
)))
...
...
@@ -2172,6 +2693,10 @@ HRESULT WINAPI MFCreateFile(MF_FILE_ACCESSMODE accessmode, MF_FILE_OPENMODE open
}
object
->
IMFByteStream_iface
.
lpVtbl
=
&
mfbytestream_vtbl
;
object
->
attributes
.
IMFAttributes_iface
.
lpVtbl
=
&
mfbytestream_attributes_vtbl
;
object
->
read_callback
.
lpVtbl
=
&
bytestream_file_read_callback_vtbl
;
object
->
write_callback
.
lpVtbl
=
&
bytestream_file_write_callback_vtbl
;
InitializeCriticalSection
(
&
object
->
cs
);
list_init
(
&
object
->
pending
);
*
bytestream
=
&
object
->
IMFByteStream_iface
;
...
...
dlls/mfplat/tests/mfplat.c
View file @
e8c4c1db
...
...
@@ -1009,9 +1009,10 @@ static void test_MFCreateMFByteStreamOnStream(void)
IMFByteStream
*
bytestream2
;
IStream
*
stream
;
IMFAttributes
*
attributes
=
NULL
;
DWORD
caps
,
written
,
count
;
IUnknown
*
unknown
;
ULONG
ref
,
size
;
HRESULT
hr
;
ULONG
ref
;
if
(
!
pMFCreateMFByteStreamOnStream
)
{
...
...
@@ -1022,6 +1023,10 @@ static void test_MFCreateMFByteStreamOnStream(void)
hr
=
CreateStreamOnHGlobal
(
NULL
,
TRUE
,
&
stream
);
ok
(
hr
==
S_OK
,
"got 0x%08x
\n
"
,
hr
);
caps
=
0xffff0000
;
hr
=
IStream_Write
(
stream
,
&
caps
,
sizeof
(
caps
),
&
written
);
ok
(
hr
==
S_OK
,
"Failed to write, hr %#x.
\n
"
,
hr
);
hr
=
pMFCreateMFByteStreamOnStream
(
stream
,
&
bytestream
);
ok
(
hr
==
S_OK
,
"got 0x%08x
\n
"
,
hr
);
...
...
@@ -1054,6 +1059,9 @@ static void test_MFCreateMFByteStreamOnStream(void)
}
ok
(
attributes
!=
NULL
,
"got NULL
\n
"
);
hr
=
IMFAttributes_GetCount
(
attributes
,
&
count
);
ok
(
hr
==
S_OK
,
"Failed to get attributes count, hr %#x.
\n
"
,
hr
);
ok
(
count
==
0
,
"Unexpected attributes count %u.
\n
"
,
count
);
hr
=
IMFAttributes_QueryInterface
(
attributes
,
&
IID_IUnknown
,
(
void
**
)
&
unknown
);
...
...
@@ -1069,18 +1077,55 @@ static void test_MFCreateMFByteStreamOnStream(void)
ref
=
IMFByteStream_Release
(
bytestream2
);
ok
(
ref
==
2
,
"got %u
\n
"
,
ref
);
hr
=
IMFByteStream_QueryInterface
(
bytestream
,
&
IID_IMFByteStreamBuffering
,
(
void
**
)
&
unknown
);
ok
(
hr
==
E_NOINTERFACE
,
"Unexpected hr %#x.
\n
"
,
hr
);
hr
=
IMFByteStream_QueryInterface
(
bytestream
,
&
IID_IMFByteStreamCacheControl
,
(
void
**
)
&
unknown
);
ok
(
hr
==
E_NOINTERFACE
,
"Unexpected hr %#x.
\n
"
,
hr
);
hr
=
IMFByteStream_QueryInterface
(
bytestream
,
&
IID_IMFMediaEventGenerator
,
(
void
**
)
&
unknown
);
ok
(
hr
==
E_NOINTERFACE
,
"Unexpected hr %#x.
\n
"
,
hr
);
hr
=
IMFByteStream_QueryInterface
(
bytestream
,
&
IID_IMFGetService
,
(
void
**
)
&
unknown
);
ok
(
hr
==
E_NOINTERFACE
,
"Unexpected hr %#x.
\n
"
,
hr
);
hr
=
IMFByteStream_GetCapabilities
(
bytestream
,
&
caps
);
ok
(
hr
==
S_OK
,
"Failed to get stream capabilities, hr %#x.
\n
"
,
hr
);
todo_wine
ok
(
caps
==
(
MFBYTESTREAM_IS_READABLE
|
MFBYTESTREAM_IS_SEEKABLE
),
"Unexpected caps %#x.
\n
"
,
caps
);
hr
=
IMFByteStream_Close
(
bytestream
);
ok
(
hr
==
S_OK
,
"Failed to close, hr %#x.
\n
"
,
hr
);
hr
=
IMFByteStream_Close
(
bytestream
);
ok
(
hr
==
S_OK
,
"Failed to close, hr %#x.
\n
"
,
hr
);
hr
=
IMFByteStream_GetCapabilities
(
bytestream
,
&
caps
);
ok
(
hr
==
S_OK
,
"Failed to get stream capabilities, hr %#x.
\n
"
,
hr
);
todo_wine
ok
(
caps
==
(
MFBYTESTREAM_IS_READABLE
|
MFBYTESTREAM_IS_SEEKABLE
),
"Unexpected caps %#x.
\n
"
,
caps
);
caps
=
0
;
hr
=
IMFByteStream_Read
(
bytestream
,
(
BYTE
*
)
&
caps
,
sizeof
(
caps
),
&
size
);
ok
(
hr
==
S_OK
,
"Failed to read from stream, hr %#x.
\n
"
,
hr
);
ok
(
caps
==
0xffff0000
,
"Unexpected content.
\n
"
);
IMFAttributes_Release
(
attributes
);
IMFByteStream_Release
(
bytestream
);
IStream_Release
(
stream
);
}
static
void
test_
MFCreateFile
(
void
)
static
void
test_
file_stream
(
void
)
{
IMFByteStream
*
bytestream
;
IMFByteStream
*
bytestream2
;
IMFAttributes
*
attributes
=
NULL
;
HRESULT
hr
;
MF_ATTRIBUTE_TYPE
item_type
;
DWORD
caps
,
count
;
WCHAR
*
filename
;
IUnknown
*
unk
;
HRESULT
hr
;
WCHAR
*
str
;
static
const
WCHAR
newfilename
[]
=
{
'n'
,
'e'
,
'w'
,
'.'
,
'm'
,
'p'
,
'4'
,
0
};
...
...
@@ -1093,10 +1138,57 @@ static void test_MFCreateFile(void)
MF_FILEFLAGS_NONE
,
filename
,
&
bytestream
);
ok
(
hr
==
S_OK
,
"got 0x%08x
\n
"
,
hr
);
hr
=
IMFByteStream_QueryInterface
(
bytestream
,
&
IID_IMFByteStreamBuffering
,
(
void
**
)
&
unk
);
ok
(
hr
==
E_NOINTERFACE
,
"Unexpected hr %#x.
\n
"
,
hr
);
hr
=
IMFByteStream_QueryInterface
(
bytestream
,
&
IID_IMFByteStreamCacheControl
,
(
void
**
)
&
unk
);
ok
(
hr
==
E_NOINTERFACE
,
"Unexpected hr %#x.
\n
"
,
hr
);
hr
=
IMFByteStream_QueryInterface
(
bytestream
,
&
IID_IMFMediaEventGenerator
,
(
void
**
)
&
unk
);
ok
(
hr
==
E_NOINTERFACE
,
"Unexpected hr %#x.
\n
"
,
hr
);
hr
=
IMFByteStream_QueryInterface
(
bytestream
,
&
IID_IMFGetService
,
(
void
**
)
&
unk
);
todo_wine
ok
(
hr
==
S_OK
,
"Failed to get interface pointer, hr %#x.
\n
"
,
hr
);
if
(
SUCCEEDED
(
hr
))
IUnknown_Release
(
unk
);
hr
=
IMFByteStream_GetCapabilities
(
bytestream
,
&
caps
);
ok
(
hr
==
S_OK
,
"Failed to get stream capabilities, hr %#x.
\n
"
,
hr
);
if
(
is_win8_plus
)
{
todo_wine
ok
(
caps
==
(
MFBYTESTREAM_IS_READABLE
|
MFBYTESTREAM_IS_SEEKABLE
|
MFBYTESTREAM_DOES_NOT_USE_NETWORK
),
"Unexpected caps %#x.
\n
"
,
caps
);
}
else
ok
(
caps
==
(
MFBYTESTREAM_IS_READABLE
|
MFBYTESTREAM_IS_SEEKABLE
),
"Unexpected caps %#x.
\n
"
,
caps
);
hr
=
IMFByteStream_QueryInterface
(
bytestream
,
&
IID_IMFAttributes
,
(
void
**
)
&
attributes
);
ok
(
hr
==
S_OK
,
"got 0x%08x
\n
"
,
hr
);
ok
(
attributes
!=
NULL
,
"got NULL
\n
"
);
hr
=
IMFAttributes_GetCount
(
attributes
,
&
count
);
ok
(
hr
==
S_OK
,
"Failed to get attributes count, hr %#x.
\n
"
,
hr
);
todo_wine
ok
(
count
==
2
,
"Unexpected attributes count %u.
\n
"
,
count
);
/* Original file name. */
hr
=
IMFAttributes_GetAllocatedString
(
attributes
,
&
MF_BYTESTREAM_ORIGIN_NAME
,
&
str
,
&
count
);
todo_wine
ok
(
hr
==
S_OK
,
"Failed to get attribute, hr %#x.
\n
"
,
hr
);
if
(
SUCCEEDED
(
hr
))
{
ok
(
!
lstrcmpW
(
str
,
filename
),
"Unexpected name %s.
\n
"
,
wine_dbgstr_w
(
str
));
CoTaskMemFree
(
str
);
}
/* Modification time. */
hr
=
IMFAttributes_GetItemType
(
attributes
,
&
MF_BYTESTREAM_LAST_MODIFIED_TIME
,
&
item_type
);
todo_wine
{
ok
(
hr
==
S_OK
,
"Failed to get item type, hr %#x.
\n
"
,
hr
);
ok
(
item_type
==
MF_ATTRIBUTE_BLOB
,
"Unexpected item type.
\n
"
);
}
IMFAttributes_Release
(
attributes
);
hr
=
MFCreateFile
(
MF_ACCESSMODE_READ
,
MF_OPENMODE_FAIL_IF_NOT_EXIST
,
...
...
@@ -2684,7 +2776,7 @@ START_TEST(mfplat)
test_MFCreateMediaEvent
();
test_attributes
();
test_sample
();
test_
MFCreateFile
();
test_
file_stream
();
test_MFCreateMFByteStreamOnStream
();
test_system_memory_buffer
();
test_source_resolver
();
...
...
include/mfidl.idl
View file @
e8c4c1db
...
...
@@ -211,6 +211,48 @@ interface IMFByteStreamHandler : IUnknown
[
out
]
QWORD
*
bytes
)
;
}
typedef
[
public
]
struct
_MF_LEAKY_BUCKET_PAIR
{
DWORD
dwBitrate
;
DWORD
msBufferWindow
;
}
MF_LEAKY_BUCKET_PAIR
;
typedef
[
public
]
struct
_MFBYTESTREAM_BUFFERING_PARAMS
{
QWORD
cbTotalFileSize
;
QWORD
cbPlayableDataSize
;
MF_LEAKY_BUCKET_PAIR
*
prgBuckets
;
DWORD
cBuckets
;
QWORD
qwNetBufferingTime
;
QWORD
qwExtraBufferingTimeDuringSeek
;
QWORD
qwPlayDuration
;
float
dRate
;
}
MFBYTESTREAM_BUFFERING_PARAMS
;
[
object
,
uuid
(
6
d66d782
-
1
d4f
-
4
db7
-
8
c63
-
cb8c77f1ef5e
),
]
interface
IMFByteStreamBuffering
:
IUnknown
{
HRESULT
SetBufferingParams
(
[
in
]
MFBYTESTREAM_BUFFERING_PARAMS
*
params
)
;
HRESULT
EnableBuffering
(
[
in
]
BOOL
enable
)
;
HRESULT
StopBuffering
()
;
}
[
object
,
uuid
(
f5042ea4
-
7
a96
-
4
a75
-
aa7b
-
2b
e1ef7f88d5
),
]
interface
IMFByteStreamCacheControl
:
IUnknown
{
HRESULT
StopBackgroundTransfer
()
;
}
[
object
,
uuid
(
6
d4c7b74
-
52
a0
-
4b
b7
-
b0db
-
55
f29f47a668
),
...
...
include/mfobjects.idl
View file @
e8c4c1db
...
...
@@ -613,6 +613,7 @@ cpp_quote("#define MFBYTESTREAM_IS_DIRECTORY 0x00000080")
cpp_quote
(
"#define MFBYTESTREAM_HAS_SLOW_SEEK 0x00000100"
)
cpp_quote
(
"#define MFBYTESTREAM_IS_PARTIALLY_DOWNLOADED 0x00000200"
)
cpp_quote
(
"#define MFBYTESTREAM_SHARE_WRITE 0x00000400"
)
cpp_quote
(
"#define MFBYTESTREAM_DOES_NOT_USE_NETWORK 0x00000800"
)
cpp_quote
(
"#define MFBYTESTREAM_SEEK_FLAG_CANCEL_PENDING_IO 0x00000001"
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment