C# 调用LibVLC库,采用句柄、回调函数两种方式播放视频
最近做个项目,需要使用C#实现播放视频,找来找去还是觉得使用LibVLC库来实现比较靠谱,结合网上朋友提供的信息,已经官网的函数描述,终于成功实现了既可以句柄播放,也可以回调函数方式播放,为啥要舍近求远、费劲巴拉的用回调函数来实现播放了?主要是因为句柄方式播放的话,就无法实现一个视频显示两个播放界面(至少我目前还不知道怎么实现,因为传递的参数太多太复杂了,官方的文档也没说怎么用,试了大部分都没法实现,譬如播放过程中如何插入参数);
废话少说吧,简单描述下两种实现的方式:
1、先定义相关的变量:
private MediaPlayer mediaPlayer;
private Media media;
private LibVLC libVLC;
private string[] arguments;
2、然后实例化
arguments = GetArguments();//数组函数
libVLC = new LibVLC(arguments);
mediaPlayer = new MediaPlayer(libVLC);
mediaPlayer.SnapshotTaken += SnapshotTakenEvent; //截图回调函数
mediaPlayer.TimeChanged += EventTimeChanged; // 时间改变回调函数
//数组函数如下:
private string[] GetArguments()
{
return new string[]{
"-I", "dummy", "--ignore-config",
//"--no-audio", //ok
/*"--no-video",*/ //ok
//"--grayscale", //faild 灰度模式(黑白)输出视频
"--fullscreen", //faild 全屏视频输出
"--no-embedded-video", //faild
};
}
3、句柄播放
edia = new Media(libVLC, @textBox2.Text/*openDialog.FileName*/, FromType.FromPath);
//赋值播放的句柄
mediaPlayer.Hwnd = this.panel1.Handle;
mediaPlayer.Play(media);
4、回调函数播放
// 先声明下 回调函数时需要的变量:
private const int _width = 960;
private const int _height = 720;
private const int _pixelBytes = 4;
private const int _pitch = _width * _pixelBytes;
private IntPtr _buff = IntPtr.Zero;
回调播放
media = new Media(libVLC, @textBox2.Text/*openDialog.FileName*/, FromType.FromPath);
_buff = Marshal.AllocHGlobal(_pitch * _height);
//回调函数播放时,需要调用的参数,
mediaPlayer.SetVideoFormat("RV32", _width, _height, _pitch);
//VideoLockCallBack不能为null,后两个可以为null
//VideoLockCallBack回调函数获取帧数据,DisplayVideo显示帧数据
mediaPlayer.SetVideoCallbacks(VideoLockCallBack, null/*VideoUnlockCallBack*/, DisplayVideo);
callbackCount = 0;
mediaPlayer.Play(media);
5、Demo的界面
6、此单元文件的所有源码:
using LibVLCSharp.Shared;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows;
namespace LibVLCDemo
{
public partial class Form1 : Form
{
[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
private static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length);
private delegate void DelegateShowText(object obj, string text);
private DelegateShowText mDelegateShowText;
private MediaPlayer mediaPlayer;
private Media media;
private LibVLC libVLC;
private static string pluginPath = System.Environment.CurrentDirectory;
private static string plugin_arg = "--plugin-path=" + pluginPath;
private string[] arguments;
//视频播放回调时使用
private Bitmap _bitmap;
private byte[] _bitmapBuffer;
/// <summary>
/// 视频的播放宽度和高度,从配置参数中获取
/// </summary>
private int _videoWidth, _videoHeight;
private Graphics _graphics;
//锁定一个图片缓冲区
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]//声明对c语言的约束
public delegate IntPtr VideoLockCB(IntPtr opaque, IntPtr planes);
//解锁一个图片缓冲区
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void VideoUnlockCB(IntPtr opaque, IntPtr picture, IntPtr planes);
//显示图片
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void VideoDisplayCB(IntPtr opaque, IntPtr picture);
private VideoLockCB _videoLockCB;
private VideoUnlockCB _videoUnlockCB;
private VideoDisplayCB _videoDisplayCB;
public Form1()
{
InitializeComponent();
mDelegateShowText = new DelegateShowText(ShowTextEvent);
}
bool obj = false;
private void Lock()
{
obj = true;
}
private void Unlock()
{
obj = false;
}
private bool Islock()
{
return obj;
}
private DateTime _start;
private void Form1_Load(object sender, EventArgs e)
{
//var libDirectory = new DirectoryInfo(Path.Combine(System.Environment.CurrentDirectory, "libvlc", IntPtr.Size == 4 ? "win-x86" : "win-x64"));
arguments = GetArguments();
libVLC = new LibVLC(arguments);
mediaPlayer = new MediaPlayer(libVLC);
mediaPlayer.SnapshotTaken += SnapshotTakenEvent;
mediaPlayer.TimeChanged += EventTimeChanged;
}
private const int _width = 960;
private const int _height = 720;
private const int _pixelBytes = 4;
private const int _pitch = _width * _pixelBytes;
private IntPtr _buff = IntPtr.Zero;
private IntPtr VideoLockCallBack(IntPtr opaque, IntPtr planes)
{
Lock();
//_buff = Marshal.AllocHGlobal(_pitch * _height);
Marshal.WriteIntPtr(planes, _buff);//初始化
return opaque/*IntPtr.Zero*/;
}
private IntPtr LockVideo(IntPtr userData, IntPtr planes)
{
Marshal.WriteIntPtr(planes, userData);
return userData;
}
private void VideoUnlockCallBack(IntPtr opaque, IntPtr picture, IntPtr planes)
{
Marshal.FreeHGlobal(_buff);//释放缓冲区
Unlock();
}
private void DisplayVideo(IntPtr userData, IntPtr picture)
{
//及时处于暂停状态,回调函数依然一直在回调
//暂停的话,就不要刷新图片了
if (mediaPlayer.State == VLCState.Paused)
return;
if (Islock())
{
//if (DateTime.Now.Subtract(_start).TotalSeconds > 2)
{
using (Bitmap bmp = new Bitmap(_width, _height, PixelFormat.Format32bppPArgb))
{
Rectangle rect = new Rectangle(0, 0, _width, _height);
BitmapData bp = bmp.LockBits(rect, ImageLockMode.WriteOnly, bmp.PixelFormat);
CopyMemory(bp.Scan0, _buff, (uint)(_height * _pitch));
bmp.UnlockBits(bp);
//bmp.Save("d:\\vlc.bmp");
Bitmap bitmap = (Bitmap)bmp.Clone();
//if (this.pictureBox1.Image != null)
// this.pictureBox1.Image.Dispose();
this.pictureBox1.Image = bitmap;
callbackCount++;
ShowText(label7, "回调计数:" + callbackCount.ToString());
}
}
}
}
private void ShowText(object obj, string text)
{
if (obj is Label)
((Label)obj).Invoke(mDelegateShowText, obj, text);
if (obj is Button)
((Button)obj).Invoke(mDelegateShowText, obj, text);
if (obj is TextBox)
((TextBox)obj).Invoke(mDelegateShowText, obj, text);
else if (obj is RichTextBox)
((RichTextBox)obj).Invoke(mDelegateShowText, obj, text);
}
private void ShowTextEvent(object obj, string text)
{
if (obj is Label)
((Label)obj).Text = text;
if (obj is Button)
((Button)obj).Text = text;
if (obj is TextBox)
((TextBox)obj).Text = text;
else if (obj is RichTextBox)
((RichTextBox)obj).Text = text;
}
private string[] GetArguments()
{
return new string[]{
"-I", "dummy", "--ignore-config",
//"--no-audio", //ok
/*"--no-video",*/ //ok
//"--grayscale", //faild 灰度模式(黑白)输出视频
"--fullscreen", //faild 全屏视频输出
"--no-embedded-video", //faild
//"--no-snapshot-preview", //ok 禁用截图预览
//"--snapshot-prefix=qianzui", //ok 视频截图文件前缀
//"--snapshot-format=jpg", //faild 视频截图格式 --snapshot-format={png,jpg,tiff} faild
//"--snapshot-sequential", //ok 使用顺序编号代替时间戳
//"--snapshot-width=125", //faild 截图宽度
//************** 前提条件:视频不能嵌入到界面
//"--video-title=videotitle", //OK 前提条件:视频不能嵌入到界面,注:标题如果是汉字,显示乱码
//"--width=800", //OK 视频宽度 前提条件:视频不能嵌入到界面 即:mediaPlayer.Hwnd 不能赋值
//"--height=300", //OK 视频高度 前提条件:视频不能嵌入到界面
//"--no-autoscale", //ok 禁止视频自动缩放
//****************
//"--monitor-par=4:3", //ok 显示器像素宽高比
//"--align=1", //ok 视频对齐 强制视频在其窗口中对齐的位置 --align={0 (居中), 1 (左), 2 (右), 4 (上), 8 (下), 5 (左上), 6 (右上), 9 (左下), 10 (右下)}
//"--sub-file=xian", //faild 使用字幕文件 载入该字幕文件。在自动检测未能检测到字幕文件时使用。
//"--sub-margin=100,200", //faild <整数> 强制字幕位置
//"--rate=1.0", //ok 定义回放的速度 (额定速度为 1.0)。 可以在播放过程中,通过mediaPlayer.SetRate函数设置
//"-R",
"--repeat", //faild 循环播放当前的项目
//"--drawable-hwnd=" + panel2.Handle, //faild 嵌入窗口视频 (drawable) 视频将嵌入在指定的现有窗口。如果为零,则将创建新窗口。
"--mirror-split=0", //faild 定义镜像切割的方向。 可以是垂直或水平 {0 (垂直), 1 (水平)}
"--croppadd-croptop=100", //faild 视频裁剪滤镜 (croppadd) 从图像顶部开始裁剪的像素数量。 从顶部开始裁剪的像素
"--croppadd-cropleft=200", //faild 从图像顶部开始裁剪的像素数量。 从左侧开始裁剪的像素
"--canvas-width=120", //faild 画布视频滤镜 (canvas) 输出 (画布) 图像宽度
"--marq-marquee=showtext", //faild 滚动文字显示 (marq) 在视频上面显示文本
"--marq-x=500", //faild 从屏幕左边缘开始的 X 偏移。
"--marq-y=40", //faild 从屏幕顶部向下的 Y 偏移。
"--marq-position=6", //faild 强制指定滚动文字在视频上的位置,marq-position={0 (居中), 1 (左), 2 (右), 4 (上), 8 (下), 5 (左上), 6 (右上), 9 (左下), 10 (右下)}
// 0=居中、1=左、2=右、4=上、8=下,您也可以组合使用这些值,例如 6=4+2 表示右上
plugin_arg };
}
private void SnapshotTakenEvent(object sender, MediaPlayerSnapshotTakenEventArgs e)
{
//截图的时候,响应的事件
}
private void button1_Click(object sender, EventArgs e)
{
//OpenFileDialog openDialog = new OpenFileDialog();
//openDialog.FileName = "d:\test.mpg";
//if(openDialog.ShowDialog() == DialogResult.OK)
{
media = new Media(libVLC, @textBox2.Text/*openDialog.FileName*/, FromType.FromPath);
//赋值播放的句柄
mediaPlayer.Hwnd = this.panel1.Handle;
mediaPlayer.Play(media);
}
}
float totalTime = -1;
float currentTime = 0;
int totalFrame = -1;
private void EventTimeChanged(object sender, MediaPlayerTimeChangedEventArgs e)
{
if(mediaPlayer.State == VLCState.Playing ||
mediaPlayer.State == VLCState.Paused ||
mediaPlayer.State == VLCState.Ended)
{
//总时长
if (totalTime == -1)
{
totalTime = (float)mediaPlayer.Length / 1000;
ShowText(label1, "总时长:" + totalTime.ToString());
totalFrame = (int)(totalTime * 25);
ShowText(label3,"总帧数:" + totalFrame); //视频为25帧/秒
}
if (e.Time >= 0)
{
currentTime = (float)e.Time / 1000;
ShowText(label2, "当前时长:" + currentTime.ToString());
ShowText(label4, "当前帧:" + (int)(currentTime * 25));
}
}
}
private void button2_Click(object sender, EventArgs e)
{
//句柄播放的时候,调用的截图函数
//如果回调函数截图,则可以直接在回调函数里面保存图片,
//第二个参数,可以直接传入保存的完整图片路径及名称,也可以只传入路径@"d:"
mediaPlayer.TakeSnapshot(0, @"d:\capture.png", 0, 0);
}
private void button3_Click(object sender, EventArgs e)
{
//mediaPlayer.Pause();
//如果处于暂停状态的时候,跳转,虽然已经成功了,但是因为处于暂停状态,
//显示的画面还是之前的,继续开始play的话,才刷新到正确的画面
try
{
float rec = mediaPlayer.Position = float.Parse(textBox1.Text) / (float)totalFrame;
if(rec == -1)
{
ShowText(richTextBox1, "SetPosition失败");
}
}
finally
{
}
//mediaPlayer.Play();
}
private int callbackCount = 0;
private void button5_Click(object sender, EventArgs e)
{
_start = DateTime.Now;
media = new Media(libVLC, @textBox2.Text/*openDialog.FileName*/, FromType.FromPath);
_buff = Marshal.AllocHGlobal(_pitch * _height);
//回调函数播放时,需要调用的参数,
mediaPlayer.SetVideoFormat("RV32", _width, _height, _pitch);
//VideoLockCallBack不能为null,后两个可以为null
//VideoLockCallBack回调函数获取帧数据,DisplayVideo显示帧数据
mediaPlayer.SetVideoCallbacks(VideoLockCallBack, null/*VideoUnlockCallBack*/, DisplayVideo);
callbackCount = 0;
mediaPlayer.Play(media);
}
private void button4_Click(object sender, EventArgs e)
{
if (mediaPlayer.State == VLCState.Playing)
{
mediaPlayer.Pause();
ShowText(button4, "Continue");
}
else if (mediaPlayer.State == VLCState.Paused)
{
mediaPlayer.Play();
ShowText(button4, "Pause");
}
}
private void button6_Click(object sender, EventArgs e)
{
if(mediaPlayer.State == VLCState.Playing || mediaPlayer.State == VLCState.Paused)
mediaPlayer.Stop();
}
}
}
里面有当时写的测试信息,可直接忽略。
7、nuget安装的包:
8、整个Demo的源码地址:
https://download.csdn.net/download/njlcfyh/88691747
写此文章,主要是记录下这段时间,对于我一个小白来说,查找C#实现视频播放的辛苦,也同时提供给像我这样如此需求的小白一个参考,大海捞针确实太辛苦了,你好我好大家好。
**