上面一节是从使用者的角度看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