一、介紹
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/
