我们使用到了Frame3D,在前面写好的。这个效果很酷炫,可以在任何一个面实现视频播放。
VideoIn3D.axaml代码
<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Width="426" Height="354" x:Class="AvaloniaUI.VideoIn3D" xmlns:local="using:AvaloniaUI" Title="VideoIn3D"> <Grid Margin="5" RowDefinitions="*"> <Border BorderBrush="Yellow" BorderThickness="1"> <local:VideoCubeView x:Name="view" Loop="True"/> </Border> </Grid> </Window>
VideoIn3D.axaml.cs代码
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Controls;
using AvaloniaUI.Demos.Book._23.Tools;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using System;
using System.Diagnostics;
using System.Threading;
using Buffer = System.Buffer;
using PixelFormat = OpenTK.Graphics.OpenGL.PixelFormat;
namespace AvaloniaUI;
public sealed class VideoCubeView : OpenGlControlBase
{
public static readonly StyledProperty<Uri?> VideoSourceProperty =
AvaloniaProperty.Register<VideoCubeView, Uri?>(nameof(VideoSource), defaultValue: null);
public Uri? VideoSource
{
get => GetValue(VideoSourceProperty);
set => SetValue(VideoSourceProperty, value);
}
public static readonly StyledProperty<bool> LoopProperty =
AvaloniaProperty.Register<VideoCubeView, bool>(nameof(Loop), defaultValue: false);
public bool Loop
{
get => GetValue(LoopProperty);
set => SetValue(LoopProperty, value);
}
public static readonly StyledProperty<Color> CubeColorProperty =
AvaloniaProperty.Register<VideoCubeView, Color>(nameof(CubeColor), Colors.White);
public Color CubeColor
{
get => GetValue(CubeColorProperty);
set => SetValue(CubeColorProperty, value);
}
private int program;
private int vao;
private int vbo;
private int ebo;
private int indexCount;
private int videoTexture;
private int texWidth;
private int texHeight;
private float time;//保留用作动画
private float rotY;
private long lastTick;
private Frame3D? frame3d;
private byte[] frontRgba = [];
private byte[] backRgba = [];
private int backWidth;
private int backHeight;
private int backStride;
private long backFrameId;
private int newFrameFlag;
static VideoCubeView()
{
VideoSourceProperty.Changed.AddClassHandler<VideoCubeView>((v, e) => v.RestartVideo());
LoopProperty.Changed.AddClassHandler<VideoCubeView>((v, e) => v.ApplyLoop());
}
protected override void OnOpenGlInit(GlInterface gl)
{
GL.LoadBindings(new BindingsContext(gl));
GL.Enable(EnableCap.DepthTest);
GL.DepthFunc(DepthFunction.Lequal);
GL.Enable(EnableCap.CullFace);
GL.CullFace(TriangleFace.Back);
CreateProgram();
CreateCubeBuffers();
RestartVideo();
}
protected override void OnOpenGlDeinit(GlInterface gl)
{
if (frame3d is not null)
{
frame3d.Ready -= OnFrameReady;
frame3d.Dispose();
frame3d = null;
}
if (videoTexture != 0) GL.DeleteTexture(videoTexture);
if (program != 0) GL.DeleteProgram(program);
if (ebo != 0) GL.DeleteBuffer(ebo);
if (vbo != 0) GL.DeleteBuffer(vbo);
if (vao != 0) GL.DeleteVertexArray(vao);
videoTexture = 0;
program = 0;
ebo = 0;
vbo = 0;
vao = 0;
}
protected override void OnOpenGlRender(GlInterface gl, int fb)
{
RequestNextFrameRendering();
GL.BindFramebuffer(FramebufferTarget.Framebuffer, fb);
var width = Math.Max(1, (int)Bounds.Width);
var height = Math.Max(1, (int)Bounds.Height);
GL.Viewport(0, 0, width, height);
UploadFrameIfNeeded();
GL.ClearColor(0.07f, 0.07f, 0.08f, 1f);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
var now = Stopwatch.GetTimestamp();
if (lastTick == 0) lastTick = now;
var dt = (float)((now - lastTick) / (double)Stopwatch.Frequency);
lastTick = now;
if (dt < 0f) dt = 0f;
if (dt > 0.1f) dt = 0.1f;
time += dt;
rotY += 0.6f * dt;
var aspect = width / (float)height;
var model =
Matrix4.CreateTranslation(-5f, -5f, -5f) *
Matrix4.CreateRotationY(rotY);
var eye = new Vector3(-20f, 15f, 25f);
var view =
Matrix4.LookAt(
eye,
Vector3.Zero,
Vector3.UnitY);
var proj =
Matrix4.CreatePerspectiveFieldOfView(
MathHelper.DegreesToRadians(60f),
aspect,
1f,
100f);
var mvp = model * view * proj;
GL.UseProgram(program);
SetMat4("uMvp", mvp);
SetInt("uTex", 0);
SetInt("uHasTex", videoTexture != 0 ? 1 : 0);
SetVec4("uColor", CubeColor);
if (videoTexture != 0)
{
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, videoTexture);
}
GL.BindVertexArray(vao);
GL.DrawElements(PrimitiveType.Triangles, indexCount, DrawElementsType.UnsignedShort, 0);
GL.BindVertexArray(0);
}
private void RestartVideo()
{
if (frame3d is not null)
{
frame3d.Ready -= OnFrameReady;
frame3d.Dispose();
frame3d = null;
}
newFrameFlag = 0;
backFrameId = 0;
if (VideoSource is null)
return;
frame3d = new Frame3D
{
Source = VideoSource,
FlipY = true,
Loop = Loop
};
frame3d.Ready += OnFrameReady;
frame3d.Play();
}
private void ApplyLoop()
{
if (frame3d is null)
return;
frame3d.Loop = Loop;
if (VideoSource is not null)
RestartVideo();
}
private void OnFrameReady(object? sender, FrameReadyEventArgs e)
{
EnsureBuffers(e.Width, e.Height);
Buffer.BlockCopy(e.Rgba, 0, backRgba, 0, e.Width * e.Height * 4);
backWidth = e.Width;
backHeight = e.Height;
backStride = e.Stride;
backFrameId = e.FrameId;
Interlocked.Exchange(ref newFrameFlag, 1);
}
private void EnsureBuffers(int w, int h)
{
var size = w * h * 4;
if (backRgba.Length != size)
backRgba = new byte[size];
if (frontRgba.Length != size)
frontRgba = new byte[size];
}
private void UploadFrameIfNeeded()
{
if (Interlocked.Exchange(ref newFrameFlag, 0) == 0)
return;
(backRgba, frontRgba) = (frontRgba, backRgba);
var w = backWidth;
var h = backHeight;
if (w <= 0 || h <= 0)
return;
if (frontRgba.Length != w * h * 4)
return;
if (videoTexture == 0)
{
videoTexture = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, videoTexture);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
GL.TexImage2D(
TextureTarget.Texture2D,
0,
PixelInternalFormat.Rgba8,
w,
h,
0,
PixelFormat.Rgba,
PixelType.UnsignedByte,
frontRgba);
GL.BindTexture(TextureTarget.Texture2D, 0);
texWidth = w;
texHeight = h;
return;
}
if (texWidth != w || texHeight != h)
{
GL.BindTexture(TextureTarget.Texture2D, videoTexture);
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
GL.TexImage2D(
TextureTarget.Texture2D,
0,
PixelInternalFormat.Rgba8,
w,
h,
0,
PixelFormat.Rgba,
PixelType.UnsignedByte,
frontRgba);
GL.BindTexture(TextureTarget.Texture2D, 0);
texWidth = w;
texHeight = h;
return;
}
GL.BindTexture(TextureTarget.Texture2D, videoTexture);
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
GL.TexSubImage2D(
TextureTarget.Texture2D,
0,
0,
0,
w,
h,
PixelFormat.Rgba,
PixelType.UnsignedByte,
frontRgba);
GL.BindTexture(TextureTarget.Texture2D, 0);
}
private void CreateCubeBuffers()
{
var mesh = Mesh.CreateCube(10f);
indexCount = mesh.indexCount;
vao = GL.GenVertexArray();
vbo = GL.GenBuffer();
ebo = GL.GenBuffer();
GL.BindVertexArray(vao);
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
GL.BufferData(BufferTarget.ArrayBuffer, mesh.vertices.Length * sizeof(float), mesh.vertices, BufferUsageHint.StaticDraw);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, ebo);
GL.BufferData(BufferTarget.ElementArrayBuffer, mesh.indices.Length * sizeof(ushort), mesh.indices, BufferUsageHint.StaticDraw);
var stride = 8 * sizeof(float);
GL.EnableVertexAttribArray(0);
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0);
GL.EnableVertexAttribArray(2);
GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, stride, 6 * sizeof(float));
GL.BindVertexArray(0);
}
private void CreateProgram()
{
var vs = CompileShader(ShaderType.VertexShader, VertexShaderSource);
var fs = CompileShader(ShaderType.FragmentShader, FragmentShaderSource);
program = GL.CreateProgram();
GL.AttachShader(program, vs);
GL.AttachShader(program, fs);
GL.LinkProgram(program);
GL.GetProgram(program, GetProgramParameterName.LinkStatus, out var linked);
if (linked == 0)
throw new InvalidOperationException(GL.GetProgramInfoLog(program));
GL.DeleteShader(vs);
GL.DeleteShader(fs);
}
private static int CompileShader(ShaderType type, string source)
{
var shader = GL.CreateShader(type);
GL.ShaderSource(shader, source);
GL.CompileShader(shader);
GL.GetShader(shader, ShaderParameter.CompileStatus, out var ok);
if (ok == 0)
throw new InvalidOperationException(GL.GetShaderInfoLog(shader));
return shader;
}
private void SetInt(string name, int v)
{
var loc = GL.GetUniformLocation(program, name);
if (loc != -1) GL.Uniform1(loc, v);
}
private void SetMat4(string name, Matrix4 m)
{
var loc = GL.GetUniformLocation(program, name);
if (loc != -1) GL.UniformMatrix4(loc, false, ref m);
}
private void SetVec4(string name, Color c)
{
var loc = GL.GetUniformLocation(program, name);
if (loc == -1)
return;
GL.Uniform4(
loc,
c.R / 255f,
c.G / 255f,
c.B / 255f,
c.A / 255f);
}
private sealed class BindingsContext(GlInterface gl) : IBindingsContext
{
public IntPtr GetProcAddress(string procName) => gl.GetProcAddress(procName);
}
private const string VertexShaderSource = """
#version 300 es
precision highp float;
layout(location=0) in vec3 aPos;
layout(location=2) in vec2 aUv;
uniform mat4 uMvp;
out vec2 vUv;
void main()
{
vUv = aUv;
gl_Position = uMvp * vec4(aPos, 1.0);
}
""";
private const string FragmentShaderSource = """
#version 300 es
precision mediump float;
in vec2 vUv;
out vec4 FragColor;
uniform int uHasTex;
uniform sampler2D uTex;
uniform vec4 uColor;
void main()
{
if (uHasTex == 0)
{
FragColor = uColor;
return;
}
vec3 tex = texture(uTex, vUv).rgb;
FragColor = vec4(tex * uColor.rgb, 1.0);
}
""";
}
public partial class VideoIn3D : Window
{
public VideoIn3D()
{
InitializeComponent();
view.VideoSource = new Uri("avares://AvaloniaUI/Resources/Sounds/test.mpg");
}
public static void Test()
{
var uri = new Uri("avares://AvaloniaUI/Resources/Sounds/test.mpg");
using var frame = new Frame3D
{
Source = uri,
FlipY = true
};
using var got = new ManualResetEventSlim(false);
frame.Ready += (_, e) =>
{
Console.WriteLine($"Frame: {e.Width}x{e.Height} stride={e.Stride} id={e.FrameId}");
if (e.Stride != e.Width * 4)
throw new Exception("Stride mismatch.");
if (e.Rgba.Length != e.Width * e.Height * 4)
throw new Exception("RGBA length mismatch.");
got.Set();
};
if (!got.Wait(TimeSpan.FromSeconds(5)))
throw new Exception("No frame received in 5 seconds.");
Console.WriteLine("Frame3D test passed.");
frame.Stop();
}
}
运行效果

浙公网安备 33010602011771号