| 通过汉化时摸索小有所获, 依旧简要总结下以备忘。
 
 PSP汉化手记有空补
 
 - 字幕内嵌 -
 作为汉化补丁,重新压制视频会带来体积明显的增加,
 100MB的补丁视频就占了90MB(笑
 虽然兼容性强,也是常用方案,但是并不认为是个好办法。
 
 
 - CSRI接口 -
 适用于视频或图像绘制由引擎完全接管时,
 如通过引擎自带解码流程(常见于跨平台作,例爱神餐馆2的SFD视频),
 或等其余需要额外绘制字幕的情况。
 
 CSRI(common subtitle renderer interface),
 目前主要还是在vsfilter上的实现。
 
 虽然libass也支持,
 但编译繁琐,加载字幕时预缓冲字体的过程也过于明显,个人讲Windows下并不推荐使用。
 
 csri.h摘要,稍有改动:
 
 流程:复制代码enum csri_pixfmt {
        CSRI_F_RGBA = 0,
        CSRI_F_ARGB,
        CSRI_F_BGRA,
        CSRI_F_ABGR,
        CSRI_F_RGB_ = 0x100,
        CSRI_F__RGB,
        CSRI_F_BGR_,                        /**< Windows "RGB32" */
        CSRI_F__BGR,
        CSRI_F_RGB  = 0x200,
        CSRI_F_BGR,                        /**< Windows "RGB24" */
        CSRI_F_AYUV = 0x1000,
        CSRI_F_YUVA,
        CSRI_F_YVUA,
        
        CSRI_F_YUY2 = 0x1100,
        CSRI_F_YV12A = 0x2011,                /**< planar YUV 2x2 + alpha plane */
        CSRI_F_YV12 = 0x2111                /**< planar YUV 2x2 */
};
typedef const char *csri_ext_id;
union csri_vardata {
        long lval;
        double dval;
        const char *utf8val;
        void *otherval;
};
struct csri_openflag {
        /** flag name */
        csri_ext_id name;
        /** flag data argument */
        union csri_vardata data;
        /** link to next flag */
        struct csri_openflag *next;
};
struct csri_fmt {
        /** format to be used */
        enum csri_pixfmt pixfmt;
        /** image width, full frame.
         *
         * This should specify the full size of the frame.
         * Specifying the video sub-size (in case of added black
         * borders) is left to an extension.
         */
        unsigned width;
        /** image height */
        unsigned height;
};
typedef void csri_rend;
typedef void csri_inst;
struct csri_frame {
        /** frame format.
         * It is an application bug if this differs from the one
         * passed in struct #csri_fmt to csri_query_fmt()
         */
        enum csri_pixfmt pixfmt;
        /** the frame's data.
         * Packed formats only use planes[0]; planar formats
         * have the data ordered as Y, U, V[, A].
         *
         * Also note that the topmost line always comes first.
         * The Windows biHeight strange-ity is \a NOT duplicated.
         */
        unsigned char *planes[4];
        /** strides for the individual planes.
         * Stride means full byte offset, i.e. do \a not add 
         * frame width
         */
        ptrdiff_t strides[4];
};
typedef int (__cdecl* csri_request_fmt_t)(csri_inst *inst, const struct csri_fmt *fmt);
typedef csri_inst* (__cdecl* csri_open_file_t)(csri_rend *renderer, const char *filename, struct csri_openflag *flags);
typedef csri_inst* (__cdecl* csri_open_mem_t)(csri_rend *renderer, const void *data, size_t length, struct csri_openflag *flags);
typedef void (__cdecl* csri_render_t)(csri_inst *inst, struct csri_frame *frame, double time);
typedef void (__cdecl* csri_close_t)(csri_inst *inst);
csri_open_xxx加载字幕文件后得到实例(?)
 csri_request_fmt设定输出分辨率等
 csri_render将字幕叠加到画面上(RGB32)
 结束后csri_close。
 
 例:
 
 Note:复制代码struct csri_fmt fmt;
fmt.pixfmt = CSRI_F_BGR_;
fmt.width = 640;
fmt.height = 480;
csri_inst * subrenderinst;
subrenderinst = csri_open_file(NULL, "test.ass", NULL);
csri_request_fmt(subrenderinst, &fmt);
unsigned char imgbuf[640 * 480 * 4];
//TODO:
//load picture to imgbuf
struct csri_frame frame;
frame.pixfmt = CSRI_F_BGR_;
frame.planes[0] = imgbuf;
frame.planes[1] = NULL;
frame.planes[2] = NULL;
frame.planes[3] = NULL;
frame.strides[0] = 640 * 4;//RGB32
csri_render(subrenderinst, &frame, 1.0);
//...
csri_close(subrenderinst);
csri_render的time单位是秒
 图左上角为原点
 官版vsfilter CSRI只支持RGB32,但也有YV12的patch
 CSRI效率低,如有复杂的特效建议多预渲染几帧和开多线程
 xy-vsfilter CSRI处理效率比官方版vsfilter更低
 (direct264附带的vsfilter 1.5.3.3698 VS xy-vsfilter 3.0.0.211)
 
 推荐参考:
 Aegisub源码
 
 基础常识:
 DLL在代码中动态加载
 
 
 - 利用vsfilter自动加载 -
 适用于游戏视频为利用dshow渲染的普通格式(wmv,avi...),且未被封包。
 DirectShow渲染会加载vsfilter的DirectVobSub (auto-loading version) Filter,
 其会检查视频文件名在目录是否有对应的同名字幕,并加载。
 虽然方便,
 但vsfilter随各个版本(尤国产各种
 流氓xx影音,xx解码的附带版)或设置不同可能出现各种问题。
 要求自行安装官方版vsfilter也总会被玩家无视。
 
 
 - 强制vsfilter加入渲染链 -
 主要适用视频已被封包情况。
 分析引擎流程,在引擎连接解码器与视频渲染器(Rendering Filter)前,
 把DirectVobSub加入FilterGraph,并连接于解码器与渲染器(VMR7居多)之间。
 再QueryInterface出IDirectVobSub接口后,
 用IDirectVobSub::put_FileName(WCHAR * fn)加载指定字幕。
 
 Note:复制代码// {93A22E7A-5091-45EF-BA61-6DA26156A5D0} 
// 7A2EA2939150EF45BA616DA26156A5D0
DEFINE_GUID(CLSID_DirectVobSub, 
0x93A22E7A, 0x5091, 0x45EF, 0xBA, 0x61, 0x6D, 0xA2, 0x61, 0x56, 0xA5, 0xD0);
// {EBE1FB08-3957-47ca-AF13-5827E5442E56}
// 08FBE1EB5739CA47AF135827E5442E56
DEFINE_GUID(IID_IDirectVobSub, 
0xebe1fb08, 0x3957, 0x47ca, 0xaf, 0x13, 0x58, 0x27, 0xe5, 0x44, 0x2e, 0x56);
稍微熟悉dshow工作流程后,加载quartz的debugsymbol(quartz.pdb),
 播放视频前bpx CoCreateInstance,
 有着symbol和GUID的提示之后F8跟一遍就基本能找到关键位置。
 
 vsfilter的put_FileName时还会做各种FindFirstFileW等操作,
 如使用MoleBox等通用封包封入字幕,还需要对FindFirstFileW等进行Hook。
 当然,还能试试做成URL+Socket,URL+Pipe之类。
 
 推荐参考:
 《DirectShow开发指南》陆其明,2003
 Windows SDK(DirectShow Samples) / MSDN
 DirectShowSpy / GraphStudioNext
 
 基础知识:
 C++编译后汇编结构
 COM接口基本规范
 DirectShow大致工作流程
 MSDN例程Filter连接之类
 https://bbs2.seikuu.com/forum.php?mod=viewthread&tid=138060
 
 - dshow下通用方法 -
 对付更复杂或如使用内嵌WMP的引擎,这时往往更需要通用的方法。
 
 基本思路,
 首先设计为DLL,方便之后引擎来加载与外部控制。
 媒体播放前不论由引擎还是dshow内部,
 必定会调用CFGControl::CImplMediaControl::Run(call [ecx+1c]),
 所以直接修改IMediaControl的虚表实现Hook。(算是IAT Hook?)
 
 Hook断下后,寻找渲染器,断开其上级渲染链,塞入DirectVobSub,
 若已有DirectVobSub则不再次处理;
 之后返回旧IMediaControl::Run。
 
 寻找当前渲染器思路,(是否可靠?)
 EnumFilters枚举所有Filters,输入Pin类型为MEDIATYPE_Video,而且没有输出Pin。
 
 关于加载非物理文件的字幕,DirectVobSub还提供了InputPin加载字幕流(MEDIATYPE_Subtitle),
 分析vsfilter和lavfilter源码,在Filter与DirectVobSub连接时,
 通过Pin的MediaType里FormatData即可完整将字幕传过去,无需AsyncReader,MemAllocator等。
 
 总之需要自制个简单(?)的Filter,只继承(?)一下CBaseFilter,CBaseOutputPin,CEnumMediaTypes就好了。复制代码FormatData大致结构:
DWORD Header_Length
DWORD Header_Lanuage
Char[?]  Header_Title_UTF8
Char[?]  Subtitle_Data
 最后为方便DLL的控制,再导出 字幕加载和播放Callback 的两个函数。
 Callback里,提供IGraphBuilder,或不便使用dshow相关时直接提供视频大小(字节),
 来区分视频特征,以选择字幕。
 
 Note:复制代码// {E487EB08-6B26-4be9-9DD3-993434D313FD}
DEFINE_GUID(MEDIATYPE_Subtitle,
0xe487eb08, 0x6b26, 0x4be9, 0x9d, 0xd3, 0x99, 0x34, 0x34, 0xd3, 0x13, 0xfd);
// {326444F7-686F-47ff-A4B2-C8C96307B4C2}
DEFINE_GUID(MEDIASUBTYPE_ASS,
0x326444f7, 0x686f, 0x47ff, 0xa4, 0xb2, 0xc8, 0xc9, 0x63, 0x07, 0xb4, 0xc2);
// {A33D2F7D-96BC-4337-B23B-A8B9FBC295E9}
DEFINE_GUID(FORMAT_SubtitleInfo,
0xa33d2f7d, 0x96bc, 0x4337, 0xb2, 0x3b, 0xa8, 0xb9, 0xfb, 0xc2, 0x95, 0xe9);
虚表只读,要VirtualProtect
 Hook时注意保存ecx
 MEDIASUBTYPE_ASS,MEDIASUBTYPE_ASS2,MEDIASUBTYPE_SSA,MEDIASUBTYPE_UTF8似乎无区别
 DllMain主线程里CoCreateInstance会导致死锁,CreateThread个新线程即可
 通过DllGetClassObject可直接使用未注册的Filter,原理见《DirectShow开发指南》1.3,
 可参考mplayer-ww源码中实现。
 
 推荐参考:
 同上
 +vsfilter源码
 +lavfilter源码
 +mplayer-ww源码
 
 源码:
 代码风格较为混乱,待整理后公开。
 最终DLL与详细用法附上,使用用途不限,
 141113更新:
 http://www.mediafire.com/downloa ... h/autosub_141113.7z
 http://s000.tinyupload.com/index.php?file_id=08951439806635357436
 加载dll时自动完成初始化,RegisterCallBack非必要
 SetSubtitleData只有支持有BOM的UTF8的ASS字幕
 
 - 结语 -
 C艹。
 |