Premiere&After Effects的实时预览插件开发
一、介绍
Adobe Premiere和After Effects在影视编辑、渲染领域已经得到广泛应用。全景视频在相应工具拼接好后也可以导入Premiere/After Effects后也可进行剪辑、渲染。但由于全景视频存在畸变、视角、拼接技术等因素,即使平铺时也无法很好的查看场景细节。这对于视频剪辑带来一定的不变。如果能一边剪辑视频一边在全景播放器中查看效果,那便再好不过了。gopro旗下的Kolor eye视频播放器就实现了这样的一种功能。实际上这个功能做起来并不难,其实就是基于Adobe Premiere Transmitter插件实现的。当然,Kolor Eye播放器插件也不例外。
二、插件开发
下面就聊聊如何开发吧。Adobe Premiere插件开发使用C++语言,并且依赖官方提供的开发包。因此在正式动手前需要下载好Adobe Plugin SDK。在SDK中的Projects目录下即可打开Demo工程:
在TransmitterPlugin.h文件中,我们先把插件名称修改成自己需要的名字:
#define PLUGIN_DISPLAY_NAME L"Demo Preview"
其他地方保持原样。然后打开对应的cpp文件进行修改。这里要实现两个功能:
- 在恰当的时候启动外部全景播放器。
- 将视频流持续转发给外部全景播放器。
那么应该怎么做呢。TransmitterPlugin.cpp文件中主要注意两个方法即可:StartPlaybackClock()及PushVideo()方法。StartPlaybackClock()方法在即将播放视频的时候调用,我们选择在这个时候启动外部播放器是再自然不过了。这里通过进程枚举来判断外部播放器是否启动了。如果安装了外部播放器且没有启动,则启动播放器;否则直接利用已启动的播放器进行播放。
tmResult TransmitInstance::StartPlaybackClock(
const tmStdParms* inStdParms,
const tmInstance* inInstance,
const tmPlaybackClock* inClock)
{
...
frameTimeInSeconds = (float)inClock->inStartTime / mTicksPerSecond;
// If not yet playing, and called to play,
// then register our UpdateClock function that calls the audio callback asynchronously during playback
// Note that StartPlaybackClock can be called multiple times without a StopPlaybackClock,
// for example if changing playback speed in the timeline.
// If already playing, we the callbackContext doesn't change, and we let the current clock continue.
if (!mPlaying && inClock->inPlayMode == playmode_Playing)
{
mPlaying = kPrTrue;
if (installFlag && !FindProcessByName(wcsrchr(mLocation, L'/') + 1))
{
HINSTANCE hInstance;
hInstance = ShellExecute(NULL, TEXT("open"), mLocation, TEXT("previewplugin 2048 1024"), NULL, SW_SHOWNORMAL);
LOGINFO(L"ShellExecute returns %d", (int)hInstance);
}
// Initialize the ClockInstanceData that the UpdateClock function will need
// We allocate the data here, and the data will be disposed at the end of the UpdateClock function
...
}
return tmResult_Success;
}
而PushVideo()根据字面意思就可以知道,是用来转发视频帧数据的,这也是为啥工程名叫Transmitter的原因。接下来,如何将视频帧数据传递给外部播放器呢?这里选择了Windows平台的内存共享技术。
tmResult TransmitInstance::PushVideo(
const tmStdParms* inStdParms,
const tmInstance* inInstance,
const tmPushVideo* inPushVideo)
{
....
frameTimeInSeconds = (float)inPushVideo->inTime / mTicksPerSecond;
mSuites.PPixSuite->GetBounds(inPushVideo->inFrames[0].inFrame, &frameBounds);
videoSize[0] = (frameBounds.right - frameBounds.left);
videoSize[1] = (frameBounds.bottom - frameBounds.top);
// Since we have ARGB color space mode.
mSuites.PPixSuite->GetPixelAspectRatio(inPushVideo->inFrames[0].inFrame, &parNum, &parDen);
mSuites.PPixSuite->GetPixelFormat(inPushVideo->inFrames[0].inFrame, &pixelFormat);
mSuites.SequenceInfoSuite->GetZeroPoint(inInstance->inTimelineID, &zeroPointTime);
mSuites.SequenceInfoSuite->GetTimecodeDropFrame(inInstance->inTimelineID, &dropFrame);
mSuites.PPixSuite->GetPixels(inPushVideo->inFrames[0].inFrame, PrPPixBufferAccess_ReadWrite, &pixelsBuffer);
if (videoSize[0] <= 0 || videoSize[1] <= 0)
{
// Dispose of the PPix(es) when done!
for (int i = 0; i < inPushVideo->inFrameCount; i++)
{
mSuites.PPixSuite->Dispose(inPushVideo->inFrames[i].inFrame);
}
return tmResult_Success;
}
resizePixels((unsigned int*)pixelsBuffer, videoSize[0], videoSize[1], SCALED_WIDTH, SCALED_HEIGHT);
if (!startupFlag)
{
startupFlag = 1;
// read registry and launch the player
HKEY hKey;
DWORD dwSize = MAX_PATH;
DWORD dwType = REG_SZ;
LPCTSTR studioPath = TEXT("studio");
LPCTSTR playerPath = TEXT("player");
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CLASSES_ROOT, studioPath, 0, KEY_READ, &hKey) ||
ERROR_SUCCESS == RegOpenKeyEx(HKEY_CLASSES_ROOT, playerPath, 0, KEY_READ, &hKey))
{
if (ERROR_SUCCESS == RegQueryValueEx(hKey, TEXT("install_location"), 0, &dwType, (LPBYTE)&mLocation, &dwSize))
{
installFlag = 1;
}
RegCloseKey(hKey);
}
else
{
ret = MessageBox(NULL, TEXT("We failed to find your Studio/Player installation。"), TEXT("Information"), MB_ICONINFORMATION | MB_OKCANCEL);
}
if (installFlag)
{
HINSTANCE hInstance;
hInstance = ShellExecute(NULL, TEXT("open"), mLocation, TEXT("previewplugin 2048 1024"), NULL, SW_SHOWNORMAL);
LOGINFO(L"ShellExecute returns %d", (int)hInstance);
}
}
// get memory file mapping for pixels buffer.
if (hPixelsMappingFile == NULL)
{
hPixelsMappingFile = CreateFileMapping(INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0, RESIZED_BUFFER_SIZE,
TEXT("pixels_buffer"));
pbPixelsFile = (void*)MapViewOfFile(hPixelsMappingFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);
}
if (hPixelsMappingFile != NULL && pbPixelsFile != NULL)
{
if (videoSize[0] / videoSize[1] == 2)
{
CopyMemory(pbPixelsFile, resizedBuffer, RESIZED_BUFFER_SIZE);
FlushViewOfFile(pbPixelsFile, RESIZED_BUFFER_SIZE);
}
}
...
return tmResult_Success;
}
通过查找注册表判断是否安装外部全景播放器。如果已安装则通过安装路径直接启动,否则提示用户。每一帧的数据通过内存共享暴露给外部全景播放器。播放器只需读取这块共享内存即可。至此编码工作完成,简单的不能再简单。将编译好的插件复制到Premiere的插件目录即可查看效果。
三、注意事项
- 依赖库。如果插件依赖外部程序库,在安装的时候也要复制到插件安装目录,或者是windows系统目录,否则插件是无法正常加载的。要查看插件依赖哪些外部程序库,可以使用VS附带的dumpbin命令:dumpbin /imports。有的时候安装可能会混淆32位和64位程序,那么还阔以通过dumpbin /headers查看程序库的版本。
- Premiere/After Effects使用的是ARGB颜色模型。因此在利用外部程序库处理时,可能需要进行适当的转换。
- 权限问题。在高版本的windows上,VS调试系统盘的程序时需要以管理员权限运行打开工程,否则是无法启动程序调试的。
四、参考链接
1. https://forums.adobe.com/thread/1661575
2. http://www.kolor.com/kolor-eyes/
- 【Go 语言社区】go 学习中遇到一些语法问题
- Elasticsearch全文检索实战小结——复盘我带的第二个项目
- 当12C PDB遇上JDBC (r10笔记第59天)
- 微信公众平台开发获取用户基本信息--转
- Elasticsearch检索分类深入详解—基础篇
- 通过Java程序测试数据库连接信息 (r10笔记第64天)
- GoldenGate安装简记(r10笔记第78天)
- 【Go 语言社区】各种变量的声明
- 【Go 语言社区】Golang 高效字符串拼接
- 实战 | Elasticsearch实现类Google高级检索
- Golang中time包用法--转
- 干货 | Elasticsearch 集群健康值红色终极解决方案
- Go语言interface的value.(type)使用小技巧-转
- 干货 | Elasticsearch5.X Mapping万能模板
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- thinkphp5实现微信扫码支付
- laravel框架实现后台登录、退出功能示例
- Laravel5.1 框架数据库操作DB运行原生SQL的方法分析
- Linux查看进程的所有信息的办法示例
- PHP配合fiddler抓包抓取微信指数小程序数据的实现方法分析
- Python基于traceback模块获取异常信息
- PHP实现微信商户支付企业付款到零钱功能
- PHP调用微博接口实现微博登录的办法示例
- thinkphp3.2同时连接两个数据库的简单方法
- php实现微信企业付款到个人零钱功能
- php中对象引用和复制实例分析
- php中上传文件的的解决方案
- PHP实现与java 通信的插件使用教程
- thinkPHP5框架接口写法简单示例
- php实现数组重复数字统计实例