上面一節是從使用者的角度看Protocol ,這一節從Protocol提供者的角度來看Protocol。
作為提供者,我們要了解三個問題:
1. Protocol是什么?
2. Protocol安裝到什么地方。
3. 怎么安裝Protocol。
前兩個問題上一節已經講述,那么我們現在看第三個問題。
BootService 提供了InstallProtocolInterface幫我們把Protocol安裝到Controller Handle上。
EFI_STATUS
InstallProtocolInterface (
IN OUT EFI_HANDLE *Handle, // Protocol將安裝到這兒
IN EFI_GUID *Protocol, // GUID
IN EFI_INTERFACE_TYPE InterfaceType, // 通常為EFI_NATIVE_INTERFACE
IN VOID *Interface // Protocol實例
);
我們希望我們的Protocol能常駐內存以提供服務。 我們知道Application 是不能常駐內存的,只有Driver可以常駐內存。那么我們就要用driver的形式來提供服務。但我們的服務與通常的driver不同,driver需要特定硬件支持,而我們的服務不需要。這就使得我們的driver變的簡單,driver需要安裝到特定的controller上,我們的服務安裝到任何controller都可以。
那么還有一個不是問題的問題,何時安裝Protocol。作為驅動的Protocol有特定的規范,將在下一節講述。通常一個driver被load到內存后會執行
InstallProtocolInterface 將 Driver Binding Protocol 和Component Name Protocol安裝到自身Handle(或者其它handle)上。 相比driver我們的服務要簡單許多,我們在Image初始化的時候將Protocol安裝到自身Handle即可。
通過上面的分析,我們決定使用driver來提供服務,Image初始化的時候安裝Protocol。具體到我們將要提供的視頻解碼服務,我們還要分析一下我們需要提供哪些函數。我們需要OpenVideo來打開視頻,QueryFrame取得一幀,CloseFrame關閉視頻,還需要一個函數來獲取視頻信息。下面讓我們一步步來產生這個Protocol吧。
頭文件ffdecoder.h首先我們要定義我們Protocol的GUID
#define EFI_FFDECODER_PROTOCOL_GUID \
{ \
0xce345171, 0xabcd, 0x11d2, {0x8e, 0x4f, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b } \
}
//
///// Protocol GUID name defined in EFI1.1.
//
#define FFDECODER_PROTOCOL EFI_FFDECODER_PROTOCOL_GUID
然后定義Protocol里服務的函數原型
/**
Open the video
@param This Indicates a pointer to the calling context.
@param FileName File name of the video under current dir.
@retval EFI_SUCCESS The video is opened successfully.
@retval EFI_NOT_FOUND There is no such file.
**/
typedef EFI_STATUS(EFIAPI* EFI_OPEN_VIDEO)( IN EFI_FFDECODER_PROTOCOL* This, IN CHAR16* FileName );
/**
Close the video
@param This Indicates a pointer to the calling context.
@retval EFI_SUCCESS The video is closed successfully.
**/
typedef EFI_STATUS(EFIAPI* EFI_CLOSE_VIDEO)( IN EFI_FFDECODER_PROTOCOL* This );
/**
Query a frame from the video.
@param This Indicates a pointer to the calling context.
@param pFrame Points to the current Frame.
@retval EFI_SUCCESS The video is opened successfully.
@retval EFI_NO_MEDIA There is no opened video.
@retval EFI_END_OF_MEDIA There is no more Frame.
**/
typedef EFI_STATUS(EFIAPI* EFI_QUARY_FRAME)( IN EFI_FFDECODER_PROTOCOL *This, OUT AVFrame **pFrame );
/**
Query the Width and height of a frame.
@param This Indicates a pointer to the calling context.
@param Width Width(in pixels) of a frame.
@param Height Height(in pixels) of a frame.
@retval EFI_SUCCESS Return the Size successfully.
@retval EFI_NO_MEDIA There is no opened video.
**/
typedef EFI_STATUS(EFIAPI* EFI_QUARY_FRAME_SIZE)( IN EFI_FFDECODER_PROTOCOL *This, OUT UINT32 *Width, OUT UINT32 *Height );下面就要定義Protocol本身了, 按照EDK2的規則,我們的Protocol取名為
EFI_FFDECODER_PROTOCOL .
struct _EFI_FFDECODER_PROTOCOL{
UINT64 Revision;
EFI_OPEN_VIDEO OpenVideo;
EFI_CLOSE_VIDEO CloseVideo;
EFI_QUARY_FRAME QueryFrame;
EFI_QUARY_FRAME_SIZE QueryFrameSize;
};
typedef struct _EFI_FFDECODER_PROTOCOL EFI_FFDECODER_PROTOCOL;
typedef EFI_FFDECODER_PROTOCOL EFI_FFDECODER;
頭文件ffdecoder.h最后要提供給用戶使用。下面進入
EFI_FFDECODER_PROTOCOL 的實現部分ffdecoder.c
ffdecoder.c中我們要提供
EFI_FFDECODER_PROTOCOL 的四個成員函數,以及一個Image初始化函數, 一個Private數據結構,用于存放
EFI_FFDECODER_PROTOCOL 的上下文。
首先看Private數據結構,如果你對ffmpeg比較熟悉,那么你很快就會明白我們需要在Private中存放什么
#define FFDECODER_PRIVATE_DATA_SIGNATURE SIGNATURE_32 ('V', 'I', 'D', 'O')
/**
@member Signature The signature of the Protocol Context
@member FFDecoder The EFI_FFDECODER_PROTOCOL
@member pFormatCtx Video Format Context
@member videoStream The index of Video Stream in all the streams.
@member pCodecCtx Codec context
@member pFrame The yuv Frame
@member pFrameRGBA The RGBA Frame
@member buffer internal used
@member img_convert_ctx The Context of converting from yuv to rgba.
**/
typedef struct {
UINTN Signature;
EFI_FFDECODER_PROTOCOL FFDecoder;
AVFormatContext *pFormatCtx;
int videoStream;
AVCodecContext *pCodecCtx;
AVFrame *pFrame;
AVFrame *pFrameRGBA;
uint8_t *buffer;
struct SwsContext *img_convert_ctx ;
} FFDECODER_PRIVATE_DATA;
static FFDECODER_PRIVATE_DATA gFFDecoderPrivate;
我們還定義了一個變量
gFFDecoderPrivate, 同時我們就得到了
EFI_FFDECODER_PROTOCOL 的一個實例。下面我們要定義4個函數,對應
EFI_FFDECODER_PROTOCOL 的四個成員函數。
EFI_STATUS
OpenVideo(
IN EFI_FFDECODER_PROTOCOL* This,
IN CHAR16* FileName
)
{
FFDECODER_PRIVATE_DATA* Private;
Private = FFDECODER_PRIVATE_DATA_FROM_THIS(This);
...
}
EFI_STATUS
CloseVideo(
IN EFI_FFDECODER_PROTOCOL* This
)
{
FFDECODER_PRIVATE_DATA* Private;
Private = FFDECODER_PRIVATE_DATA_FROM_THIS(This);
...
}
EFI_STATUS
QueryFrame(
IN EFI_FFDECODER_PROTOCOL *This,
OUT AVFrame **ppFrame
)
{
FFDECODER_PRIVATE_DATA* Private;
Private = FFDECODER_PRIVATE_DATA_FROM_THIS(This);
...
}
EFI_STATUS
QueryFrameSize(
IN EFI_FFDECODER_PROTOCOL *This,
OUT UINT32 *Width,
OUT UINT32 *Height
)
{
FFDECODER_PRIVATE_DATA* Private;
Private = FFDECODER_PRIVATE_DATA_FROM_THIS(This);
...
}
宏FFDECODER_PRIVATE_DATA_FROM_THIS(This) 用于根據Protocol指針取得Protocol的上下文, 定義如下:
#define FFDECODER_PRIVATE_DATA_FROM_THIS(a) CR (a, FFDECODER_PRIVATE_DATA, FFDecoder, FFDECODER_PRIVATE_DATA_SIGNATURE)
本篇重點講述Protocol,所以這個四個函數細節不再詳述,感興趣的話可以看附件中的源碼。
下面我們看最重要的部分,Image的初始化函數,回憶一下,我們會記得,在初始化函數中我們將要安裝Protocol。
EFI_STATUS
EFIAPI
InitFFdecoder (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
FFDECODER_PRIVATE_DATA* Private = &gFFDecoderPrivate;
//初始化ShellProtocol
ShellLibConstructorWorker2(NULL,NULL,NULL,NULL);
//
初始化StdLib (
void) DriverInitMain(0, NULL);
//設置Protocol上下文的Signature.
//初始化Protocol,設置Protocol成員函數。
Private-> Signature= FFDECODER_PRIVATE_DATA_SIGNATURE ;
Private->FFDecoder.OpenVideo = OpenVideo;
Private->FFDecoder.CloseVideo = CloseVideo;
Private->FFDecoder.QueryFrame= QueryFrame;
Private->FFDecoder.QueryFrameSize= QueryFrameSize;
//將
EFI_FFDECODER_PROTOCOL的實例
&(Private->FFDecoder )安裝到自身Handle中。
Status = gBS->InstallProtocolInterface (
&ImageHandle,
&gEfiFFDecoderProtocolGUID ,
EFI_NATIVE_INTERFACE,
&Private->FFDecoder
);
}
在.inf文件中我們把
InitFFdecoder 設為Entrypoint,當Image被load到內存后
InitFFdecoder 會自動執行。
[Defines]
INF_VERSION = 0x00010006
BASE_NAME = ffdecoder
FILE_GUID = 33a97c46-7491-4dfd-b442-74798713ce5f
#ENTRY_POINT = ShellCEntryLib
#MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 0.1
MODULE_TYPE = UEFI_DRIVER
ENTRY_POINT = InitFFdecoder
#
# VALID_ARCHITECTURES = IA32 X64 IPF
#
[Sources]
ffdecoder.c
math.c
InitShell.c
[Packages]
StdLib/StdLib.dec
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec
ShellPkg/ShellPkg.dec
ffmpeg/ffmpeg.dec
StdLibPrivateInternalFiles/DoNotUse.dec
[LibraryClasses]
UefiDriverEntryPoint
LibC
LibStdio
LibMath
LibString
BsdSocketLib
EfiSocketLib
UseSocketDxe
DevShell
zlib
libavcodec
libavutil
libswscale
libavformat
LibUefi
LibNetUtil
啊哈,編譯得到ffdecoder.efi. 使用命令 load ffdecoder.efi 加載之后就可以像使用其它Protocol一樣使用
EFI_FFDECODER_PROTOCOL了。
下面是一個簡單的例子 fplayer.c 用于播放視頻
#ifdef __cplusplus
extern "C"{
#endif
#include <Uefi.h>
#include <Base.h>
#include <Library/DebugLib.h>
#include <Library/PrintLib.h>
#include <Protocol/GraphicsOutput.h>
EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput;
#ifdef __cplusplus
}
#endif
#include "ffdecoder.h"
EFI_STATUS LocateGraphicsOutput()
{
EFI_STATUS Status = gBS->LocateProtocol(
&gEfiGraphicsOutputProtocolGuid,
NULL,
(VOID **)&GraphicsOutput);
if (EFI_ERROR(Status)) {
Print(L"LocateProtocol %r\n", Status);
}
return Status;
}
void ShowFrame(AVFrame *pFrame, int width, int height, int iFrame)
{
if(GraphicsOutput)
GraphicsOutput->Blt(
GraphicsOutput,
(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)pFrame->data[0],
EfiBltBufferToVideo,
0,0,
0,0,
width, height,
0
);
}
int main(int argc, char *argv[])
{
AVFrame *pFrame;
EFI_GUID gEfiFFDecoderProtocolGUID = EFI_FFDECODER_PROTOCOL_GUID ;
EFI_FFDECODER_PROTOCOL *FFDecoder;
UINT32 Width, Height;
CHAR16* FileName = 0;
// Locate the Protocol
EFI_STATUS Status = gBS->LocateProtocol(
&gEfiFFDecoderProtocolGUID ,
NULL,
(VOID **)&FFDecoder );
if (EFI_ERROR(Status)) {
Print(L"LocateProtocol %r\n", Status);
return Status;
}
LocateGraphicsOutput();
// Open Video
Status = gBS->AllocatePool(EfiLoaderData, AsciiStrLen(argv[1]) *2 + 2, (VOID**)&FileName );
AsciiStrToUnicodeStr(argv[1], FileName);
Status = FFDecoder -> OpenVideo( FFDecoder, FileName);
(void) gBS->FreePool ( FileName);
if (EFI_ERROR(Status)) {
Print(L"Open %r\n", Status);
return Status;
}
// Query Frame Size(Width height)
Status = FFDecoder-> QueryFrameSize(FFDecoder, &Width, &Height);
// Query Frame
while( !EFI_ERROR( FFDecoder-> QueryFrame(FFDecoder, &pFrame)))
{
ShowFrame(pFrame, Width, Height, 0);
}
// Close Video
Status = FFDecoder -> CloseVideo(FFDecoder );
return 0;
}
在shell里面執行
f0:/>ffplayer test.avi 就可以播放視頻了
下載EFI_FFDECODER_PROTOCOL 附近包含了32-bit的ffdecoder.efi 以及ffdecoder.c fplayer.c