由于工作需要,自己封装的基于ffmpeg的视频编解码库,显示采用了SDL库。可以播放本地文件或网络流,支持多端口播放,支持文字叠加,截图、视频录制等等。
头文件代码:
#pragma once
#ifdef __DLLEXPORT
#define __DLL_EXP _declspec(dllexport)
#else
#define __DLL_EXP _declspec(dllimport)
#endif
#include <stdio.h>
#include <string>
using namespace std;
//录像状态
enum RecordStatus
{
Idle,
Recording,
Stop
};
//视频存储格式
enum VideoFormat
{
MP4,
AVI,
};
//图片存储格式
enum PictureFormat
{
JPEG,
PNG,
BMP,
};
//播放器状态
enum PlayStatus
{
Close,
Playing,
Pause
};
//滤镜种类
enum FilterType
{
Time,
Reticle,
Text,
Other
};
//颜色
struct RGBAColor
{
BYTE R;
BYTE G;
BYTE B;
BYTE A;
};
//OSD字体属性结构体
struct OSDParameter
{
char* text;
int fontSize; //汉字字号
int fontFamily; //0=黑体,1=楷体,2=宋体 3:Arial
RGBAColor fontColor; //字体颜色 rgb表示
RGBAColor backColor; //字体背景色 rgb表示
float x; //字体坐标 x:宽度百分比0-1
float y; //字体坐标 y:高度百分比0-1
bool visible; //是否可见
};
//格式
enum FrameFormat
{
AUDIO16,
RGB32,
YV12,
UVVY,
YUVJ420P
};
struct FrameInfo
{
int width; //画面宽,单位像素。如果是音频数据则为0;
int height; //画面高。如果是音频数据则为0;
int stamp; //时标信息,单位毫秒。
FrameFormat format; //数据类型,0:AUDIO16,1:RGB32,2:(YUV420P)YV12,3:YUVJ420P
int frameRate; //实时帧率。
};
/**
* 功能描述:打开视频
* @param nPort:播放通道号[0,31]
* @param url:视频文件或视频流地址
* @param data:图像控件句柄,后台录制时,赋值NULL
*
* @return 成功/失败
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_OpenVideo(int nPort, const char* url, const void* hWnd);
/**
* 功能描述: 开始播放
* @param nPort:播放通道号[0,31]
* @param isRealTimePlay:实时播放,在需要实时播放时开启,如播放摄像头流。为false时,将按照帧率播放。
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_StartPlay(int nPort,bool isRealTimePlay);
/**
* 功能描述: 暂停播放
* @param nPort:播放通道号[0,31]
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_PausePlay(int nPort);
/**
* 功能描述:关闭视频释放资源
* @param nPort:播放通道号[0,31]
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_CloseVideo(int nPort);
/**
* 功能描述:设置文件播放指针的相对位置(百分比)
* @param nPort:播放通道号[0,31]
* @param pos:播放时间位置 0-100
* @param fReleatePos:0-100
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_SetPlayPos(int nPort, float fReleatePos);
/**
* 功能描述:播放速度控制
* @param nPort:播放通道号[0,31]
* @param speed:速度 0:正常播放 1:1.25倍速 2:1.5倍速 3:1.75倍速 4:2倍速 5:0.75倍速 6:0.5倍速 7:0.25倍速
*/
extern "C" __declspec(dllexport) void _stdcall VideoPlayer_ChangeSpeed(int nPort, int speed);
/**
* 功能描述:获取当前播放时间
* @param nPort:播放通道号[0,31]
* @return 播放时间 单位:秒
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_GetPlayedTime(int nPort);
/**
* 功能描述:播放窗口缩放
* @param nPort:播放通道号[0,31]
* @return 成功/失败
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_ResizeWindow(int nPort);
/**
* 功能描述:设置录制参数
* @param nPort:播放通道号[0,31]
* @param fps:视频帧率
* @param quality:图像质量(0-10) 越高质量越好,占用内存越大,设置为0时,为自动码率
* @param duration:单个文件时长(min),默认60分钟
* @param frameExtractionMode:抽帧模式 0:不抽帧,以fps进行编码;1:1/2 帧率,以fps/2进行抽帧(保证fps/2为整数,否则会出现丢帧情况)
* @param format:存储格式,默认为mp4
*
* @return 成功/失败
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_SetRecordParameter(int nPort,int fps, float quality = 5, int duration = 60, int frameExtractionMode = 0, VideoFormat format= VideoFormat::MP4);
/**
* 功能描述:开始录制
* @param nPort:播放通道号[0,31]
* @param path:录像保存路径包含文件名
*
* @return 成功/失败
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_StartRecord(int nPort, const char* path);
/**
* 功能描述:结束录制
* @param nPort:播放通道号[0,31]
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_StopRecord(int nPort);
/**
* 功能描述:视频截图
* @param nPort:播放通道号[0,31]
* @param path: 图像保存的路径,不含后缀名
* @param format:图像格式,默认为jpeg
*
* @return 成功/失败
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_ShotImage(int nPort, const char* path, PictureFormat format= PictureFormat::JPEG);
/**
* 功能描述:设置某一行的OSD叠加文本
* @param nPort:播放通道号[0,31]
* @param osdID:OSD文本序号
* @param para:osd属性
*
* @return 成功/失败
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_SetOSDText(int nPort, int osdID, OSDParameter osdParam);
/**
* 功能描述:设置所有的OSD文本(总共20行)
* @param nPort:播放通道号[0,31]
* @param para:osd属性
*
* @return 成功/失败
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_SetAllOSDTexts(int nPort, OSDParameter osdParams[]);
/**
* 功能描述:清除所有OSD文本
* @param nPort:播放通道号[0,31]
*
* @return 成功/失败
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_ClearOSDText(int nPort);
/**
* 功能描述:设置解码缓存队列大小
* @param nPort:播放通道号[0,31]
* @param size:默认为10,设置过小会丢帧,设置过大可能会导致画面延迟
*
* @return 成功/失败
*/
extern "C" __declspec(dllexport) void _stdcall VideoPlayer_SetDecodedBufferSize(int nPort, int size);
/**
* 功能描述:单帧往后播放
* @param nPort:播放通道号[0,31]
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_NextFrame(int nPort);
/**
* 功能描述:单帧往前播放
* @param nPort:播放通道号[0,31]
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_PrevFrame(int nPort);
/**
* 功能描述:跳转视频到某一时间位置
* @param nPort:播放通道号[0,31]
* @param pos:视频位置百分比
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_SetPlayPos(int nPort,float pos);
/**
* 功能描述:显示/隐藏当前系统时间
* @param nPort:播放通道号[0,31]
* @param visible:是否显示
* @param xPos:坐标位置X,宽度百分比
* @param yPos:坐标位置Y,高度百分比
* @param fontSize:字体大小,默认50
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_SetDateTime(int nPort,bool visible, float xPos, float yPos,int fontSize=50);
/**
* 功能描述:当前解码完成数据回调
* @param nPort:播放通道号[0,31]
* @param pBuf:yuv数据指针
* @param nSize:数据大小
* @param pFrameInfo:帧信息
*/
typedef void(_stdcall* DecodeCallBack)(int nPort, const unsigned char* pBuf, int nSize, FrameInfo& pFrameInfo);
/**
* 功能描述:设置解码回调函数
* @param nPort:播放通道号[0,31]
* @param pDecodeProc:回调函数
*/
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_SetDecCallBack(int nPort, DecodeCallBack pDecodeProc);
/**
* 功能描述:在显示句柄窗口中绘制文本、图形等
* @param nPort:播放通道号[0,31]
* @param hWnd:视频窗口句柄,用于创建GDI
* @param width:视频窗口宽度
* @param height:视频窗口高度
*/
typedef void(_stdcall* DrawCallBack)(int nPort, const void* hWnd,int width,int height);
extern "C" __declspec(dllexport) bool _stdcall VideoPlayer_SetDrawCallBack(int nPort, DrawCallBack pDrawProc);
库支持C++,C#调用,以下是C#调用的方法:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using VideoPlayDemo.Onvif;
using static VideoPlayDemo.MYVideoPlay;
namespace VideoPlayDemo
{
public partial class Form1 : Form
{
const int PLAY_PORT1 = 0;
const int PLAY_PORT2 = 1;
public event MYVideoPlay.DecodeCallBack decodeCallBack;
public event MYVideoPlay.DrawCallBack drawCallBack;
bool[] isRecording = new bool[2];
bool[] isOpened = new bool[2];
MYVideoPlay.OSDParameter[] OSDParameters1 = new MYVideoPlay.OSDParameter[20];
MYVideoPlay.OSDParameter[] OSDParameters2 = new MYVideoPlay.OSDParameter[20];
public Form1()
{
InitializeComponent();
Timer timer = new Timer();
timer.Interval = 100;
timer.Enabled = true;
timer.Tick += Timer_Tick;
decodeCallBack += new MYVideoPlay.DecodeCallBack(Form1_decodeCallBack);
drawCallBack += new MYVideoPlay.DrawCallBack(Form1_drawCallBack);
//初始化
for (int i = 0; i < 20; i++)
{
OSDParameters1[i] = new MYVideoPlay.OSDParameter();
OSDParameters1[i].visible = false;
OSDParameters1[i].text = "test中文"+i.ToString();
OSDParameters1[i].fontSize = 100;
OSDParameters1[i].fontFamily = 0;
OSDParameters1[i].fontColor = new MYVideoPlay.RGBAColor(Color.White);
OSDParameters1[i].backColor = new MYVideoPlay.RGBAColor(Color.Transparent);
OSDParameters2[i] = new MYVideoPlay.OSDParameter();
OSDParameters2[i].visible = false;
OSDParameters2[i].text = "test中文" + i.ToString();
OSDParameters2[i].fontSize = 100;
OSDParameters2[i].fontFamily = 0;
OSDParameters2[i].fontColor = new MYVideoPlay.RGBAColor(Color.Yellow);
OSDParameters2[i].backColor = new MYVideoPlay.RGBAColor(Color.Transparent);
}
}
#region 视频播放控制方法
/// <summary>
/// 打开视频
/// </summary>
/// <param name="port"></param>
public bool OpenVideo(int port,string url,IntPtr intPtr)
{
bool ret = false;
ret = MYVideoPlay.VideoPlayer_SetDecCallBack(port, decodeCallBack);
if (!ret)
{
MessageBox.Show("设置解码回调函数失败!");
return ret;
}
ret = MYVideoPlay.VideoPlayer_SetDrawCallBack(port, drawCallBack);
if (!ret)
{
MessageBox.Show("设置绘图回调函数失败!");
return ret;
}
ret = MYVideoPlay.VideoPlayer_OpenVideo(port, url, intPtr);
if (!ret)
{
MessageBox.Show("打开视频失败!");
return ret;
}
ret = MYVideoPlay.VideoPlayer_StartPlay(port,false);
if (!ret)
{
MessageBox.Show("播放失败!");
return ret;
}
//叠加时间
// ret = MYVideoPlay.VideoPlayer_SetDateTime(port, true, 0.01f, 0.01f,70);
return ret;
}
/// <summary>
/// 关闭视频
/// </summary>
/// <param name="port"></param>
private bool CloseVideo(int port)
{
return MYVideoPlay.VideoPlayer_CloseVideo(port);
}
/// <summary>
/// 开始录制 帧率60
/// </summary>
/// <param name="port"></param>
/// <param name="halfRate"></param>
private bool StartRecord(int port,bool halfRate)
{
bool ret = false;
if(halfRate)
ret = MYVideoPlay.VideoPlayer_SetRecordParameter(port, 60, 5, 60,1);
else
ret = MYVideoPlay.VideoPlayer_SetRecordParameter(port, 60, 5, 60, 0);
string path = string.Format("d:\\video_{0}", port);
ret = MYVideoPlay.VideoPlayer_StartRecord(port, path);
if (!ret)
MessageBox.Show("开启录制失败!");
return ret;
}
/// <summary>
/// 停止录制
/// </summary>
/// <param name="port"></param>
private bool StopRecord(int port)
{
bool ret = MYVideoPlay.VideoPlayer_StopRecord(port);
return ret;
}
/// <summary>
/// 截图
/// </summary>
/// <param name="port"></param>
/// <returns></returns>
private bool ShotImage(int port)
{
bool ret = MYVideoPlay.VideoPlayer_ShotImage(port, "d:\\" + port.ToString(), MYVideoPlay.PictureFormat.JPEG);
return ret;
}
int[] frameRates = new int[4];
private void Form1_decodeCallBack(int nPort, IntPtr pBuf, int nSize, ref MYVideoPlay.FrameInfo pFrameInfo)
{
frameRates[nPort] = pFrameInfo.frameRate;
this.Invoke((Action)delegate
{
this.lbFPS1.Text = frameRates[0].ToString()+"fps";
this.lbFPS2.Text = frameRates[1].ToString()+"fps";
});
}
private void Form1_drawCallBack(int nPort, IntPtr hWnd, int width, int height)
{
}
#endregion
/// <summary>
/// 打开视频
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnOpenVideo_Click(object sender, EventArgs e)
{
Button btn = sender as Button;
int n = int.Parse(btn.Tag.ToString());
if (isOpened[n])
return;
string url="";
IntPtr intPtr= IntPtr.Zero;
switch (n)
{
case 0: url = textBox1.Text;intPtr = pnlVideo1.Handle; break;
case 1: url = textBox2.Text;intPtr = pnlVideo2.Handle; break;
}
Task task = new Task(() =>
{
//网络直播流(非相机流)时设置更大的buffer进行缓存,否则出现马赛克
MYVideoPlay.VideoPlayer_SetDecodedBufferSize(n, 250);
isOpened[n] = OpenVideo(n,url,intPtr);
});
task.Start();
}
/// <summary>
/// 关闭视频
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnCloseVideo_Click(object sender, EventArgs e)
{
Button btn = sender as Button;
int n = int.Parse(btn.Tag.ToString());
if (!isOpened[n])
return;
bool ret = CloseVideo(n);
isOpened[n] = !ret;
}
/// <summary>
/// 开始录制
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnStartRecord_Click(object sender, EventArgs e)
{
Button btn = sender as Button;
int n = int.Parse(btn.Tag.ToString());
if (isRecording[n] || !isOpened[n])
return;
if (n == 0)
isRecording[n] = StartRecord(n, true);
else
isRecording[n] = StartRecord(n, false);
}
/// <summary>
/// 停止录制
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnStopRecord_Click(object sender, EventArgs e)
{
Button btn = sender as Button;
int n = int.Parse(btn.Tag.ToString());
if (!isRecording[n] || !isOpened[n])
return;
bool ret = StopRecord(n);
isRecording[n] = !ret;
}
/// <summary>
/// 截图
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnShotImage_Click(object sender, EventArgs e)
{
Button btn = sender as Button;
int n = int.Parse(btn.Tag.ToString());
ShotImage(n);
}
/// <summary>
/// 叠加字符测试
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Timer_Tick(object sender, EventArgs e)
{
Random rd = new Random();
bool ret = false;
if (isOpened[PLAY_PORT1])
{
OSDParameters1[0].visible = OSDParameters1[1].visible = true;
OSDParameters1[0].x = (float)rd.NextDouble();
OSDParameters1[0].y = (float)rd.NextDouble();
OSDParameters1[1].x = (float)rd.NextDouble();
OSDParameters1[1].y = (float)rd.NextDouble();
//ZYVideoPlay.VideoPlayer_SetAllOSDTexts(PLAY_PORT1, OSDParameters1);
}
if (isOpened[PLAY_PORT2])
{
OSDParameters2[0].visible = OSDParameters2[1].visible = true;
OSDParameters2[0].x = (float)rd.NextDouble();
OSDParameters2[0].y = (float)rd.NextDouble();
OSDParameters2[1].x = (float)rd.NextDouble();
OSDParameters2[1].y = (float)rd.NextDouble();
// ret = ZYVideoPlay.VideoPlayer_SetAllOSDTexts(PLAY_PORT2, OSDParameters2);
}
}
private void timer1_Tick(object sender, EventArgs e)
{
if (isRecording[PLAY_PORT1])
{
if (DateTime.Now.Millisecond < 500)
lbRec1.Visible = true;
else
lbRec1.Visible = false;
}
else
lbRec1.Visible = false;
if (isRecording[PLAY_PORT2])
{
if (DateTime.Now.Millisecond < 500)
lbRec2.Visible = true;
else
lbRec2.Visible = false;
}
else
lbRec2.Visible = false;
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
for(int i=0;i<2;i++)
{
if (isOpened[i])
CloseVideo(i);
}
}
}
}