Commit 1b28d48c authored by Martin Finkel's avatar Martin Finkel

Sound on Android. Still an issue with JNI array marshalling, needed to...

Sound on Android. Still an issue with JNI array marshalling, needed to complete initialization for video
parent 612f3933
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading; using System.Threading;
using Android.App;
using Android.Graphics; using Android.Graphics;
using Android.OS; using Android.OS;
using Android.Util;
using Android.Views; using Android.Views;
using Java.Interop;
using Java.Lang; using Java.Lang;
using Thread = Java.Lang.Thread; using Thread = Java.Lang.Thread;
...@@ -21,8 +21,8 @@ namespace LibVLCSharp.Android.Sample ...@@ -21,8 +21,8 @@ namespace LibVLCSharp.Android.Sample
class SurfaceHelper class SurfaceHelper
{ {
AWindow _aWindow; readonly AWindow _aWindow;
int _id; readonly int _id;
readonly SurfaceView _surfaceView; readonly SurfaceView _surfaceView;
readonly TextureView _textureView; readonly TextureView _textureView;
static ISurfaceHolder _surfaceHolder; static ISurfaceHolder _surfaceHolder;
...@@ -66,7 +66,7 @@ namespace LibVLCSharp.Android.Sample ...@@ -66,7 +66,7 @@ namespace LibVLCSharp.Android.Sample
_aWindow.OnSurfaceCreated(); _aWindow.OnSurfaceCreated();
} }
} }
void AttachSurfaceView() void AttachSurfaceView()
{ {
_surfaceHolder.AddCallback(_surfaceHolderCallback); _surfaceHolder.AddCallback(_surfaceHolderCallback);
...@@ -183,27 +183,30 @@ namespace LibVLCSharp.Android.Sample ...@@ -183,27 +183,30 @@ namespace LibVLCSharp.Android.Sample
static readonly int SURFACE_STATE_ATTACHED = 1; static readonly int SURFACE_STATE_ATTACHED = 1;
static readonly int SURFACE_STATE_READY = 2; static readonly int SURFACE_STATE_READY = 2;
SurfaceHelper[] _surfaceHelpers; readonly SurfaceHelper[] _surfaceHelpers;
ISurfaceCallback _surfaceCallback; readonly ISurfaceCallback _surfaceCallback;
int _surfaceState = SURFACE_STATE_INIT; int _surfaceState = SURFACE_STATE_INIT;
INewVideoLayoutListener _newVideoLayoutListener; INewVideoLayoutListener _newVideoLayoutListener;
List<ICallback> _voutCallbacks = new List<ICallback>(); readonly List<ICallback> _voutCallbacks = new List<ICallback>();
readonly Handler _handler = new Handler(Looper.MainLooper); readonly Handler _handler = new Handler(Looper.MainLooper);
Surface[] _surfaces; static Surface[] _surfaces;
IntPtr _callbackNativeHandle = IntPtr.Zero; long _callbackNativeHandle;
int _mouseAction, _mouseButton, _mouseX, _mouseY, _windowWidth, _windowHeight = -1; int _mouseAction, _mouseButton, _mouseX, _mouseY, _windowWidth, _windowHeight = -1;
SurfaceTextureThread _surfaceTextureThread = new SurfaceTextureThread(); readonly SurfaceTextureThread mSurfaceTextureThread = new SurfaceTextureThread();
static readonly NativeLock _nativeLock = new NativeLock(); static readonly NativeLock _nativeLock = new NativeLock();
const int AWINDOW_REGISTER_ERROR = 0;
const int AWINDOW_REGISTER_FLAGS_SUCCESS = 0x1;
const int AWINDOW__FLAGS_HAS_VIDEO_LAYOUT_LISTENER = 0x2;
public AWindow(ISurfaceCallback surfaceCallback) public AWindow(ISurfaceCallback surfaceCallback)
{ {
_surfaceCallback = surfaceCallback; _surfaceCallback = surfaceCallback;
_surfaceHelpers = new SurfaceHelper[ID_MAX]; _surfaceHelpers = new SurfaceHelper[ID_MAX];
_surfaces = new Surface[ID_MAX]; _surfaces = new Surface[ID_MAX];
} }
void EnsureInitState() void EnsureInitState()
{ {
if (_surfaceState != SURFACE_STATE_INIT) if (_surfaceState != SURFACE_STATE_INIT)
...@@ -336,10 +339,9 @@ namespace LibVLCSharp.Android.Sample ...@@ -336,10 +339,9 @@ namespace LibVLCSharp.Android.Sample
} }
foreach(var cb in _voutCallbacks) foreach(var cb in _voutCallbacks)
cb.OnSurfacesDestroyed(this); cb.OnSurfacesDestroyed(this);
if(_surfaceCallback != null) _surfaceCallback?.OnSurfacesDestroyed(this);
_surfaceCallback.OnSurfacesDestroyed(this);
if(Build.VERSION.SdkInt >= BuildVersionCodes.JellyBean) if(Build.VERSION.SdkInt >= BuildVersionCodes.JellyBean)
_surfaceTextureThread.Release(); mSurfaceTextureThread.Release();
} }
public bool AreViewsAttached => _surfaceState != SURFACE_STATE_INIT; public bool AreViewsAttached => _surfaceState != SURFACE_STATE_INIT;
...@@ -378,28 +380,115 @@ namespace LibVLCSharp.Android.Sample ...@@ -378,28 +380,115 @@ namespace LibVLCSharp.Android.Sample
public void AddCallback(ICallback callback) public void AddCallback(ICallback callback)
{ {
throw new System.NotImplementedException(); if (_voutCallbacks.Contains(callback)) return;
_voutCallbacks.Add(callback);
} }
public void RemoveCallback(ICallback callback) public void RemoveCallback(ICallback callback)
{ {
throw new System.NotImplementedException(); _voutCallbacks.Remove(callback);
} }
//(long nativeHandle, int action, int button, int x, int y) signature: "(JIIII)V", connector:
//[Register(name: "nativeOnMouseEvent")]
//[Export]
internal virtual void NativeOnMouseEvent(long nativeHandle, int action, int button, int x, int y)
{
System.Diagnostics.Debug.WriteLine("NativeOnMouseEvent");
}
//[Export]
internal virtual void NativeOnWindowSize(long nativeHandle, int width, int height)
{
System.Diagnostics.Debug.WriteLine("NativeOnWindowSize");
}
// used by JNI
// Get the valid Video surface
// return can be null if the surface was destroyed.
// ReSharper disable once UnusedMember.Local
// ReSharper disable once InconsistentNaming
//[Register("getVideoSurface", )]
[Export("getVideoSurface")]
Surface VideoSurface() => GetNativeSurface(ID_VIDEO);
// Get the valid Subtitles surface.
// return can be null if the surface was destroyed.
// ReSharper disable once UnusedMember.Local
// ReSharper disable once InconsistentNaming
[Export("getSubtitlesSurface")]
Surface SubtitlesSurface() => GetNativeSurface(ID_SUBTITLES);
public void SetWindowSize(int width, int height) public void SetWindowSize(int width, int height)
{ {
throw new System.NotImplementedException(); lock (_nativeLock)
{
if (_callbackNativeHandle != 0 && (_windowHeight != height || _windowWidth != width))
{
NativeOnWindowSize(_callbackNativeHandle, width, height);
}
_windowHeight = height;
_windowWidth = width;
}
}
[Export("registerNative")]
int RegisterNative(long handle)
{
//if(handle == IntPtr.Zero)
if(handle == 0)
throw new IllegalArgumentException("handle is null");
lock (_nativeLock)
{
if (_callbackNativeHandle != 0)
return AWINDOW_REGISTER_ERROR;
_callbackNativeHandle = handle;
if (_mouseAction != -1)
{
NativeOnMouseEvent(_callbackNativeHandle, _mouseAction, _mouseButton, _mouseX, _mouseY);
}
if (_windowWidth != -1 && _windowHeight != -1)
{
NativeOnWindowSize(_callbackNativeHandle, _windowWidth, _windowHeight);
}
var flags = AWINDOW_REGISTER_FLAGS_SUCCESS;
if (_newVideoLayoutListener != null)
flags |= AWINDOW__FLAGS_HAS_VIDEO_LAYOUT_LISTENER;
return flags;
}
} }
[Export("unregisterNative")]
void UnregisterNative()
{
lock (_nativeLock)
{
if(_callbackNativeHandle == 0)
throw new IllegalArgumentException("unregister called when not registered");
_callbackNativeHandle = 0;
}
}
[Export("setBuffersGeometry")]
bool SetBuffersGeometry(Surface surface, int width, int height, int format)
{
// TODO: add implementation if build <= ICS. Not used on newer versions.
return false;
}
Surface GetNativeSurface(int id) Surface GetNativeSurface(int id)
{ {
lock (_nativeLock) lock (_nativeLock)
{ {
return _surfaces[id]; return _surfaces[id];
} }
} }
...@@ -411,6 +500,180 @@ namespace LibVLCSharp.Android.Sample ...@@ -411,6 +500,180 @@ namespace LibVLCSharp.Android.Sample
} }
} }
[Export("setVideoLayout")]
void SetVideoLayout(int width, int height, int visibleWidth, int visibleHeight, int sarNum, int sarDen)
{
_handler.Post(() =>
{
_newVideoLayoutListener?.OnNewVideoLayout(this, width, height, visibleWidth, visibleHeight, sarNum,
sarDen);
});
}
class SurfaceTextureThread : Java.Lang.Object, IRunnable, SurfaceTexture.IOnFrameAvailableListener
{
SurfaceTexture _surfaceTexture;
Surface _surface;
bool _frameAvailable;
Looper _looper;
Thread _thread;
bool _isAttached;
bool _doRelease;
internal bool AttachToGLContext(int texName)
{
if (_surfaceTexture == null)
{
_thread = new Thread(this);
_thread.Start();
while (_surfaceTexture == null)
{
try
{
_thread.Wait();
}
catch (InterruptedException)
{
return false;
}
}
_surface = new Surface(_surfaceTexture);
}
_surfaceTexture.AttachToGLContext(texName);
_frameAvailable = false;
_isAttached = true;
return true;
}
public void OnFrameAvailable(SurfaceTexture surfaceTexture)
{
if (surfaceTexture == _surfaceTexture)
{
if (_frameAvailable)
throw new IllegalStateException("An available frame was not updated");
_frameAvailable = true;
_thread.Notify();
}
}
readonly object _locker = new object();
public void Run()
{
Looper.Prepare();
lock (_locker)
{
_looper = Looper.MyLooper();
_surfaceTexture = new SurfaceTexture(0);
_surfaceTexture.DetachFromGLContext();
_surfaceTexture.SetOnFrameAvailableListener(this);
_thread.Notify();
}
Looper.Loop();
}
internal void DetachFromGLContext()
{
if (!_doRelease)
{
_surfaceTexture.DetachFromGLContext();
_isAttached = false;
return;
}
_looper.Quit();
_looper = null;
try
{
_thread.Join();
}
catch (InterruptedException) { }
_thread = null;
_surface.Release();
_surface = null;
_surfaceTexture.Release();
_surfaceTexture = null;
_doRelease = false;
_isAttached = false;
}
internal bool WaitAndUpdateTexImage(float[] transformMatrix)
{
lock (_locker)
{
while (!_frameAvailable)
{
try
{
_thread.Wait(500);
if (!_frameAvailable)
return false;
}
catch (InterruptedException) { }
}
_frameAvailable = false;
}
_surfaceTexture.UpdateTexImage();
_surfaceTexture.GetTransformMatrix(transformMatrix);
return true;
}
internal Surface Surface => _surface;
internal void Release()
{
if (_surfaceTexture != null)
{
if (_isAttached)
_doRelease = true;
else
{
_surface.Release();
_surface = null;
_surfaceTexture.Release();
_surfaceTexture = null;
}
}
}
}
/// <summary>
/// Attach the SurfaceTexture to the OpenGL ES context that is current on the calling thread.
/// </summary>
/// <param name="texName">the OpenGL texture object name (e.g. generated via glGenTextures)</param>
/// <returns>true in case of success</returns>
[Export]
bool SurfaceTexture_attachToGLContext(int texName) => /*AndroidUtil.isJellyBeanOrLater &&*/ mSurfaceTextureThread.AttachToGLContext(texName);
/// <summary>
/// Detach the SurfaceTexture from the OpenGL ES context that owns the OpenGL ES texture object.
/// </summary>
[Export]
void SurfaceTexture_detachFromGLContext() => mSurfaceTextureThread.DetachFromGLContext();
/// <summary>
/// Wait for a frame and update the TexImage
/// </summary>
/// <param name="transformMatrix"></param>
/// <returns>true on success, false on error or timeout</returns>
//[Export]
bool SurfaceTexture_waitAndUpdateTexImage(float[] transformMatrix)
{
return mSurfaceTextureThread.WaitAndUpdateTexImage(transformMatrix);
}
/// <summary>
/// Get a Surface from the SurfaceTexture
/// </summary>
/// <returns></returns>
[Export]
Surface SurfaceTexture_getSurface() => mSurfaceTextureThread.Surface;
} }
public interface IVLCVout public interface IVLCVout
...@@ -527,147 +790,6 @@ namespace LibVLCSharp.Android.Sample ...@@ -527,147 +790,6 @@ namespace LibVLCSharp.Android.Sample
void OnSurfacesDestroyed(AWindow vout); void OnSurfacesDestroyed(AWindow vout);
} }
class SurfaceTextureThread : Java.Lang.Object, IRunnable, SurfaceTexture.IOnFrameAvailableListener
{
SurfaceTexture _surfaceTexture;
Surface _surface;
bool _frameAvailable;
Looper _looper;
Thread _thread;
bool _isAttached;
bool _doRelease;
bool AttachToGLContext(int texName)
{
if (_surfaceTexture == null)
{
_thread = new Thread(this);
_thread.Start();
while (_surfaceTexture == null)
{
try
{
_thread.Wait();
}
catch (InterruptedException)
{
return false;
}
}
_surface = new Surface(_surfaceTexture);
}
_surfaceTexture.AttachToGLContext(texName);
_frameAvailable = false;
_isAttached = true;
return true;
}
public void Dispose()
{
}
public IntPtr Handle { get; }
public void OnFrameAvailable(SurfaceTexture surfaceTexture)
{
if (surfaceTexture == _surfaceTexture)
{
if(_frameAvailable)
throw new IllegalStateException("An available frame was not updated");
_frameAvailable = true;
_thread.Notify();
}
}
readonly object _locker = new object();
public void Run()
{
Looper.Prepare();
lock (_locker)
{
_looper = Looper.MyLooper();
_surfaceTexture = new SurfaceTexture(0);
_surfaceTexture.DetachFromGLContext();
_surfaceTexture.SetOnFrameAvailableListener(this);
_thread.Notify();
}
Looper.Loop();
}
void DetachFromGLContext()
{
if (!_doRelease)
{
_surfaceTexture.DetachFromGLContext();
_isAttached = false;
return;
}
_looper.Quit();
_looper = null;
try
{
_thread.Join();
}
catch (InterruptedException) { }
_thread = null;
_surface.Release();
_surface = null;
_surfaceTexture.Release();
_surfaceTexture = null;
_doRelease = false;
_isAttached = false;
}
bool WaitAndUpdateTexImage(float[] transformMatrix)
{
lock (_locker)
{
while (!_frameAvailable)
{
try
{
_thread.Wait(500);
if (!_frameAvailable)
return false;
}
catch (InterruptedException) { }
}
_frameAvailable = false;
}
_surfaceTexture.UpdateTexImage();
_surfaceTexture.GetTransformMatrix(transformMatrix);
return true;
}
Surface Surface => _surface;
internal void Release()
{
if (_surfaceTexture != null)
{
if (_isAttached)
_doRelease = true;
else
{
_surface.Release();
_surface = null;
_surfaceTexture.Release();
_surfaceTexture = null;
}
}
}
}
class NativeLock class NativeLock
{ {
public bool BuffersGeometryConfigured; public bool BuffersGeometryConfigured;
......
<?xml version="1.0" encoding="utf-8"?>
<Weavers>
</Weavers>
\ No newline at end of file
...@@ -17,22 +17,28 @@ ...@@ -17,22 +17,28 @@
<AndroidResgenClass>Resource</AndroidResgenClass> <AndroidResgenClass>Resource</AndroidResgenClass>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies> <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>True</AndroidUseLatestPlatformSdk> <AndroidUseLatestPlatformSdk>True</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v7.1</TargetFrameworkVersion> <TargetFrameworkVersion>v8.0</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest> <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix> <MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix> <MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
<DebugType>Full</DebugType> <DebugType>Full</DebugType>
<Optimize>False</Optimize> <Optimize>False</Optimize>
<OutputPath>bin\Debug\</OutputPath> <OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>TRACE;DEBUG;__ANDROID__</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime> <AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
<AndroidLinkMode>None</AndroidLinkMode> <AndroidLinkMode>None</AndroidLinkMode>
<EmbedAssembliesIntoApk>False</EmbedAssembliesIntoApk> <EmbedAssembliesIntoApk>False</EmbedAssembliesIntoApk>
<AotAssemblies>false</AotAssemblies>
<EnableLLVM>false</EnableLLVM>
<BundleAssemblies>false</BundleAssemblies>
<AndroidSupportedAbis>armeabi-v7a</AndroidSupportedAbis>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
...@@ -48,6 +54,10 @@ ...@@ -48,6 +54,10 @@
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk> <EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Cauldron.Interception, Version=2.0.0.29, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Cauldron.Interception.Fody.2.0.29\lib\netstandard2.0\Cauldron.Interception.dll</HintPath>
</Reference>
<Reference Include="Mono.Android.Export" />
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
...@@ -60,7 +70,7 @@ ...@@ -60,7 +70,7 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="GettingStarted.Xamarin" /> <None Include="packages.config" />
<None Include="Resources\AboutResources.txt" /> <None Include="Resources\AboutResources.txt" />
<None Include="Properties\AndroidManifest.xml" /> <None Include="Properties\AndroidManifest.xml" />
<None Include="Assets\AboutAssets.txt" /> <None Include="Assets\AboutAssets.txt" />
...@@ -80,7 +90,27 @@ ...@@ -80,7 +90,27 @@
<Folder Include="Resources\mipmap-xxhdpi\" /> <Folder Include="Resources\mipmap-xxhdpi\" />
<Folder Include="Resources\mipmap-xxxhdpi\" /> <Folder Include="Resources\mipmap-xxxhdpi\" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LibVLCSharp.Android\LibVLCSharp.Android.csproj">
<Project>{d549b545-66fe-42db-a7f3-34b67f626fb6}</Project>
<Name>LibVLCSharp.Android</Name>
</ProjectReference>
<ProjectReference Include="..\LibVLCSharp\LibVLCSharp.csproj">
<Project>{d1c3b7c4-713b-46b2-b33a-e9298c819921}</Project>
<Name>LibVLCSharp</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="FodyWeavers.xml" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<Import Project="..\packages\Fody.2.3.3\build\netstandard1.0\Fody.targets" Condition="Exists('..\packages\Fody.2.3.3\build\netstandard1.0\Fody.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Fody.2.3.3\build\netstandard1.0\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.2.3.3\build\netstandard1.0\Fody.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">
......
using Android.App; using System;
using System.Runtime.InteropServices;
using System.Security;
using Android.App;
using Android.Graphics;
using Android.OS; using Android.OS;
using Android.Views;
using Java.Interop;
using VideoLAN.LibVLC;
namespace LibVLCSharp.Android.Sample namespace LibVLCSharp.Android.Sample
{ {
[Activity(Label = "LibVLCSharp.Android.Sample", MainLauncher = true)] [Activity(Label = "LibVLCSharp.Android.Sample", MainLauncher = true)]
public class MainActivity : Activity public class MainActivity : Activity, ICallback