Closed DeadSix27 closed 5 years ago
The struct definition looks fine, though it should probably be marked with a StructLayoutAttribute like this:
[StructLayout(LayoutKind.Sequential)]
struct mpv_event
{
mpv_event_id event_id;
int error;
UInt64 reply_userdata;
IntPtr data;
}
I think the problem might be with the definition of mpv_wait_event. This returns a pointer to an mpv_event, rather than the mpv_event structure itself, so the type should be something like this:
private delegate IntPtr mpv_wait_event(IntPtr mpvHandle, double timeout);
Then the untyped pointer can be marshalled to a C# struct with Marshal.PtrToStructure:
IntPtr ptr = _mpv_wait_event(_mpvHandle, -1);
mpv_event evt = (mpv_event)Marshal.PtrToStructure(ptr, typeof(mpv_event));
At least, I think this is correct. I haven't tested it yet.
This can also be done with real typed pointers (C# totally supports raw pointers in an unsafe context,) but that's a different can of worms.
If there is a better place to ask this, I'd like to know that as well.
I'm not sure. It's hard to find info about C# P/Invoke on the internet, especially for marshalling complicated data structures. I'm happy to answer questions about C# libmpv bindings in this issue tracker.
EDIT: Offtopic Question: "Error parsing option script (option could not be parsed)" Any idea why this happends? I loaded a conf through libmpv in which i set (tried all of them): script="test.lua" | script="scripts/test.lua" | script="G:/...fullpath/scripts/test.lua" with quotes and without I also tried: script=scripts/test.lua,scripts/test2.lua
Worked with your examples and made it function just as I wanted:
loop... {
IntPtr ptr = _mpv_wait_event(_mpvHandle, -1);
mpv_event evt = (mpv_event)Marshal.PtrToStructure(ptr, typeof(mpv_event));
Console.WriteLine(evt.event_id);
switch (evt.event_id)
{
case mpv_event_id.MPV_EVENT_LOG_MESSAGE:
IntPtr iD = evt.data;
mpv_event_log_message data = (mpv_event_log_message)Marshal.PtrToStructure(iD, typeof(mpv_event_log_message));
Console.WriteLine(data.text);
break;
}
}
Output:
MPV_EVENT_LOG_MESSAGE
AO: [wasapi] 96000Hz stereo 2ch float
....
MPV_EVENT_LOG_MESSAGE
Using hardware decoding (dxva2-copy).
MPV_EVENT_VIDEO_RECONFIG
Renders fine too:
EDIT: Also added this struct and enum since they were needed:
public enum mpv_log_level
{
MPV_LOG_LEVEL_NONE = 0, /// "no" - disable absolutely all messages
MPV_LOG_LEVEL_FATAL = 10, /// "fatal" - critical/aborting errors
MPV_LOG_LEVEL_ERROR = 20, /// "error" - simple errors
MPV_LOG_LEVEL_WARN = 30, /// "warn" - possible problems
MPV_LOG_LEVEL_INFO = 40, /// "info" - informational message
MPV_LOG_LEVEL_V = 50, /// "v" - noisy informational message
MPV_LOG_LEVEL_DEBUG = 60, /// "debug" - very noisy technical information
MPV_LOG_LEVEL_TRACE = 70, /// "trace" - extremely noisy
}
[StructLayout(LayoutKind.Sequential)]
public struct mpv_event_log_message
{
public string prefix;
public string level;
public string text;
public mpv_log_level log_level;
}
Great. I'm glad it worked. I've been meaning to improve the libmpv C# example to do things like this, but I haven't gotten around to it yet.
As for the second question, I'm not sure what's wrong there. The script option with full Windows paths (eg. script="C:\full\path\script.lua") works on my machine. Do other options work in the .conf file when loaded through libmpv?
EDIT:
Btw, do you know how to properly manage other formats than String in this:
_mpvSetProperty(_mpvHandle, GetUtf8Bytes("pause"), mpv_format.MPV_FORMAT_STRING, ref bytes);
This code works properly, however
var bin = BitConverter.GetBytes(doub); // (double doub = 0.5;)
_mpvSetProperty(_mpvHandle, GetUtf8Bytes("ao-volume"), mpv_format.MPV_FORMAT_DOUBLE, ref bin);
Sets the volume always to 0 in Windows Audio Mixer
Both use:
...
_mpvSetProperty = (MpvSetProperty)GetDllType(typeof(MpvSetProperty), "mpv_set_property");
...
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int MpvSetProperty(IntPtr mpvHandle, byte[] name, mpv_format format, ref byte[] data);
private MpvSetProperty _mpvSetProperty;
@rossy Do other options work in the .conf file when loaded through libmpv?
Just checked, other options work fine, best proof, is "input-ipc-server=mpvpipe" which is working as intended
However, even a proper script option like: script="C:\Users*\Documents..........\test\test.lua" just shows: Error parsing option script (option could not be parsed) Could "load-scripts=yes" be breaking it?
That BitConverter.GetBytes() method looks correct to me. I think the range of ao-volume is 1-100, so it's possible that 0.5 is rounded down to 0. Try setting it to 50.
@rossy Tried some options, see below:
//SetVolume(double doub)
var bin = BitConverter.GetBytes(doub);
_mpvSetProperty(_mpvHandle, GetUtf8Bytes("ao-volume"), mpv_format.MPV_FORMAT_DOUBLE, ref bin);
......
mpv.SetVolume(90);
Console.WriteLine(mpv.GetVolumeNative());
mpv.SetVolume(90.0);
Console.WriteLine(mpv.GetVolumeNative());
mpv.SetVolume(50.0);
Console.WriteLine(mpv.GetVolumeNative());
mpv.SetVolume(100);
Console.WriteLine(mpv.GetVolumeNative());
mpv.SetVolume(1.0);
Console.WriteLine(mpv.GetVolumeNative());
OUTPUT:
0
0
0
0
0
(Get(/Set)VolumeNative is a waveOutGetVolume-winmm.dll Import I use as work-around until I find out why the libmpv one doesn't work)
mpv doesn't control the system volume.
@wm4 Well, that just makes the debug up there wrong, however the audio is still 0 (I can't hear it if I set it to 100, so I assume it always sets it 0), or do you mean ao-volume simply will never work? And I have to use my own code for that?
In fact, if i do the ao-volume option it sets it to 0 INSIDE windows, so it does control the system volume... (does it not?)
By system volume I mean the slider specifically for the host of libmpv e.g my program in this case, NOT the master volume. (Master is not what I want to control anyway)
Oh right, ao-volume is only available if audio playback is initialized.
@wm4 Just loaded a .flac to test that, same issue, sets it to 0 regardless.
@rossy Been a while since I posted this (2016...) anyway I came back to this recently and got stuck at getting properties of libmpv with specific formats, e.g:
//name = "duration"
var buffer = IntPtr.Zero;
_mpvGetProperty(_mpvHandle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_DOUBLE, ref buffer);
double outputDouble = (double)Marshal.PtrToStructure(buffer, typeof(double));
_mpvFree(buffer);
return outputDouble;
which crashes with access violation, if I do the same thing with MPV_FORMAT_STRING
, which according to client.h: "and access using MPV_FORMAT_STRING usually invokes a string formatter.
" invokes a string formatter, and it works fine.
Is duration not a double? Afaik it should be? or is my pointer to structure incorrect, altho the same PtrToStructure code works fine in MPV_EVENT_PROPERTY_CHANGE
events.
Using double as type in the unmanaged function pointer also resolves this, however that would require a function for each type..
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int mpv_get_property(IntPtr mpvHandle, byte[] name, mpv_format format, ref double data);
private mpv_get_property _mpv_get_property;
I think your code might be using the wrong level of indirection. It's using two levels of pointer indirection: It passes a reference to the IntPtr (in C, this would be a double**
argument.) libmpv treats the argument as a pointer to double (in C: double*
, in C#: ref double
) so it will write the double value directly to storage pointed to by the argument.
It's different for MPV_FORMAT_STRING
because strings can be variable length, so libmpv allocates storage for them on the heap. With MPV_FORMAT_DOUBLE
, the storage is always 8-bytes long, so libmpv writes the double value directly to caller-provided storage on the stack (this also means the _mpvFree() call isn't needed for MPV_FORMAT_DOUBLE
.)
Using double as type in the unmanaged function pointer also resolves this, however that would require a function for each type..
IMO this isn't too bad. It's probably the easiest solution. Another solution is to always use MPV_FORMAT_NODE
and do the type-conversion on the C# side.
@rossy I'll most likely use the separate function way ye.. considering its afaik only a few main types anyway.
I still don't understand the issue with double too:
libmpv writes the double value directly to caller-provided storage on the stack
so instead of referencing a pointer i should reference a type, but I can not reference an unknown type (e.g Object
) afaik (I tried) (Passing invalid VARIANTs to the CLR can cause unexpected exceptions, corruption or data loss.'
)
So how would that work?
Also just as reference I gave my best at trying to convert the NODE structs to C# too. But I can't get them to work.. Also saw someone else needs help on NODE_ARRAY, so this could help him a little too: https://github.com/mpv-player/mpv/issues/4380
[StructLayout(LayoutKind.Explicit)]
struct mpv_node
{
[FieldOffset(4)] public string string_; /* valid if format==MPV_FORMAT_STRING */
[FieldOffset(4)] public int flag; /* valid if format==MPV_FORMAT_FLAG */
[FieldOffset(4)] public Int64 int64; /* valid if format==MPV_FORMAT_INT64 */
[FieldOffset(4)] public double double_; /* valid if format==MPV_FORMAT_DOUBLE */
[FieldOffset(4)]
[MarshalAs(UnmanagedType.Struct)]
public mpv_node_list list; /* format==MPV_FORMAT_NODE_MAP,format==MPV_FORMAT_NODE_ARRAY */
[FieldOffset(4)]
[MarshalAs(UnmanagedType.Struct)]
public mpv_byte_array ba; /* format==MPV_FORMAT_NODE_MAP,format==MPV_FORMAT_NODE_ARRAY */
[FieldOffset(0)] public mpv_format format;
}
[StructLayout(LayoutKind.Sequential)]
struct mpv_byte_array
{
public IntPtr data;
public UIntPtr size;
}
[StructLayout(LayoutKind.Sequential)]
struct mpv_node_list
{
public int num;
public IntPtr values;
public string keys;
}
IntPtr testptr = IntPtr.Zero;
MpvGetPropertyNode(mpvHandle, GetUtf8Bytes("chapter-list"), mpv_format.MPV_FORMAT_NODE, ref testptr);
mpv_node testlist = (mpv_node)Marshal.PtrToStructure(ptr, typeof(mpv_node));
(Sorry for the close-reopen, I accidentally hit Enter before I even began writing the issue and didn't want a unfinished issue to hover in the issue tracker)
I'm currently trying to hook into _mpv_wait_event from c#.
But that's a little over my head, especially since I've never worked much with Invokes from c++ dlls
That's as far as I got so far:
Most of the code is based on the chsharp example, I simply added the event structs of c++ But honestly, I barely have an idea how to convert C++ structs into c# code, so pretty much any help at all is very appreciated.
(If there is a better place to ask this, I'd like to know that as well.)
Crashes with System.AccessViolationException most likely because my attempt is probably completely wrong.