<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>空雪小屋 &#187; 多媒体</title>
	<atom:link href="http://blog.sorayuki.net/?cat=22&#038;feed=rss2" rel="self" type="application/rss+xml" />
	<link>http://blog.sorayuki.net</link>
	<description>现在域名是 blog.sorayuki.net ~</description>
	<lastBuildDate>Tue, 29 Nov 2022 08:27:41 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=4.0</generator>
	<item>
		<title>FFMS2读取多媒体文件中的声音的Bug</title>
		<link>http://blog.sorayuki.net/?p=623</link>
		<comments>http://blog.sorayuki.net/?p=623#comments</comments>
		<pubDate>Mon, 28 Aug 2017 15:10:08 +0000</pubDate>
		<dc:creator><![CDATA[空雪梦见]]></dc:creator>
				<category><![CDATA[多媒体]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[问题解决]]></category>

		<guid isPermaLink="false">http://blog.sorayuki.net/?p=623</guid>
		<description><![CDATA[FFMS2是AviSynth的一个来源插件，以FFMPEG为支撑，能读入各种各样的多媒体文件。但是今天在使用它 &#8230; <a href="http://blog.sorayuki.net/?p=623" class="more-link">继续阅读<span class="screen-reader-text">FFMS2读取多媒体文件中的声音的Bug</span> <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>FFMS2是AviSynth的一个来源插件，以FFMPEG为支撑，能读入各种各样的多媒体文件。但是今天在使用它的时候，遇到了Bug。</p>
<p><span id="more-623"></span>
<p>这个Bug的具体表现是，如果我有一个MP4视频文件，我用某些切割工具对文件进行过切割，因为切割的时候不一定是在关键帧处切割的，而切割过程中又没有重新进行编码，有的工具在这样切割之后生成的文件，音频和视频的第一帧（关键帧）的时间戳都是小于0。小于一点点貌似没事，我遇到Bug的时候，这个时间戳我记得是负的4秒多。</p>
<p>对于这样的文件，我再用FFMS2在AviSynth里载入（记得要包含声音），载入之后用类似 last+last 这样的语句把它们连接起来。这样的AviSynth脚本送到播放器里面播放的话，到了两个片段的连接处，就会发现播放器死了。不是概率性出现而是每次必现。</p>
<p>我从GitHub下载了FFMS2的源代码（<a title="https://github.com/FFMS/ffms2" href="https://github.com/FFMS/ffms2">https://github.com/FFMS/ffms2</a>），在MSYS2环境中执行它那个 build-win-deps.sh 下载 ffmpeg 和编译（因为opencore-amr我用不到，为了节省时间，我就把它注释和屏蔽掉了）。之后用 VisualStudio 打开它那个工程，编译了FFMS2.dll。不论是Debug版本还是Release版本，自己编译的也还是会出现同样的问题。</p>
<p>但是因为是自己编译的，所以用 Debug 版本调试起来就很方便。在卡死的时候中断程序运行，会发现卡死的代码在 audiosource.cpp 的 FFMS_AudioSource::GetAudio 函数里。它不断在一个循环里打转，也不进什么函数，也不出去。很是郁闷。</p>
<p>我重新运行了程序，试图在它将要变成这个样子的临界点用条件断点（判断 Start 变量的值）断了下来。跟踪跟进去，发现和正常的时候相比，此时有个地方不太一样：它在正常的时候，是在数据不够的时候进 DecodeNextBlock 函数，然后 avcodec_decode_audio4 解码一个包，把解码结果 cache 起来。但是在我刚才所说的情况中，因为进了下一个片段，它会先 Seek 当前进度到文件开头，然后再调用 DecodeNextBlock。我观察到的结果是，它还没 Seek 的时候，avcodec_decode_audio4 函数运行的都没什么问题；但是它对这样的文件进行 Seek 之后，读取出来的 Packet 就再也没办法在 avcodec_decode_audio4 里解码后，通过 GotFrame 参数返回 1 了。</p>
<p>也就是说，经过这样跟踪下来，可以认为问题出在 FFMPEG 上，或者调用 FFMPEG 的方式有问题。</p>
<p>我是先考虑了 FFMPEG 可能有问题。因为 build-win-deps.sh 脚本是直接拉的 FFMPEG （git://source.ffmpeg.org/ffmpeg.git） 的 master 分支的代码。我尝试把它替换成去链接官网下载的带正式版本号的 FFMPEG （<a title="http://ffmpeg.zeranoe.com/builds/" href="http://ffmpeg.zeranoe.com/builds/">http://ffmpeg.zeranoe.com/builds/</a>） 的 DLL。具体方法是 build-win-deps.sh 脚本执行完成之后会产生一个 deps 文件夹，在里面可以找到一堆 libav*********.a 这样的文件。因为编译器用的是VC，所以这些 .a 文件其实是 .lib 文件。把它换成官网下的 FFMPEG 的 dev 压缩包里解压出来的文件名差不多的 lib 文件，然后再编译FFMS2工程，能得到一个比较小的 FFMS2.DLL （因为是动态链接到 FFMPEG）。配上 FFMPEG 官网下的 shared 那个压缩包里解压出来的 DLL 文件进行测试，会卡死的问题消失了。因此我判断是 FFMS2 拉的 FFMPEG 的 master 分支可能有什么问题，或者静态链接情况下会有什么问题、在动态链接的时候会消失的。</p>
<p>这样编译出来的 FFMS2 还有一个问题，它在 FFMS2_Init 函数里面，会对 FFMPEG 设置几个回调函数去拦截日志输出。如果用动态链接版的 FFMPEG 对 Avisynth 脚本进行转码，此时 FFMS2 和 FFMPEG.EXE 会用的同一套 FFMPEG 的DLL文件（AV****.DLL 那些）。这样在 Avisynth 加载 FFMS2 插件的时候，这个回调函数的设置会洗掉 FFMPEG.EXE 设置的回调，导致你转码过程中看不到任何进度信息。因为大概看了一下，FFMS2 也并没有想把 FFMPEG 的日志拿来干嘛用，所以我就直接把 FFMS2_Init 里几个注册日志回调的代码给注释掉了。再重新构建 FFMS2.DLL，然后拿一个用了 FFMS2 的 Avisynth 脚本去用 FFMPEG.EXE 转码，没有问题了。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.sorayuki.net/?feed=rss2&#038;p=623</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>视频直播开发的技能树</title>
		<link>http://blog.sorayuki.net/?p=601</link>
		<comments>http://blog.sorayuki.net/?p=601#comments</comments>
		<pubDate>Mon, 20 Mar 2017 16:43:33 +0000</pubDate>
		<dc:creator><![CDATA[空雪梦见]]></dc:creator>
				<category><![CDATA[多媒体]]></category>

		<guid isPermaLink="false">http://blog.sorayuki.net/?p=601</guid>
		<description><![CDATA[现在直播什么的很火，还有拍短视频之类。很多人在开发这方面的软件。我在一个这方面开发的讨论群里看了一段时间以后， &#8230; <a href="http://blog.sorayuki.net/?p=601" class="more-link">继续阅读<span class="screen-reader-text">视频直播开发的技能树</span> <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>现在直播什么的很火，还有拍短视频之类。很多人在开发这方面的软件。我在一个这方面开发的讨论群里看了一段时间以后，有这种感觉。就是很多人是对多媒体方面的知识不太够，但是公司又要做，只能硬着头皮上。找资料的时候就会遇到这样一个问题，一些基础知识不会，看文章就看得有点迷糊或者看不懂。找别人写好的函数库来调用的话，只能实现一些简单的需求。所以我在想，我以前在字幕组搞过一段时间视频处理，这方面的知识能不能组织一下列出来，并不是全都要会才能做，里面很多我也不熟，只是在遇到问题时候好有个方向，知道要去查什么。</p>
<p>括号里的英文是用来方便搜索的。因为中文互联网里的资料搜起来很多实在不堪入目，搜英语资料虽然看起来比较累，不过根据经验确实是能少踩点坑</p>
<p>有想到的不完整的地方可能还会来补充。</p>
<p><span id="more-601"></span>
<pre>多媒体数据的表示方法
	视频的基本参数
		分辨率（resolution）
			常说的480p、720p、1080p指的是扫描线数量，也就是宽和高里的高。
		逐行扫描（progressive）/基于帧（frame-based）/隔行扫描（interlaced）/基于场（field-based）
			电脑上的视频大多是逐行扫描，但DVD等的视频是隔行扫描的。很多摄像机录出来的视频也是隔行扫描的。
		帧速率（frame rate）
			手机拍摄的视频帧率常常不稳定，是可变的。DVD里常见视频是30FPS隔行扫描但实际是24FPS逐行扫描的电影格式。
		像素比例/画面比例（sample aspect，display aspect等）
			电脑显示器和手机屏幕的像素高宽比例是1:1，但有些视频例如DVD不是这样。
		采样格式/像素格式/色彩空间，RGB，YUV，NV12等，BT.709和BT.601范围，不同像素格式之间转换
			虽然光的三原色是RGB，但因为一些历史原因（从黑白电视机发展而来）视频里常用的是YUV。
			可以参考Avisynth的帮助文档，在Advanced Topics的Colorspace Conversions里面有相关公式和参数。
			可以参考维基百科，里面有数据的具体存储顺序。（基于Planes，或者交错保存）
			BT.709和BT.601因为数值范围不同，弄错了会造成画面颜色偏灰、太浅或者太深等
	音频的基本参数
		采样格式
			有整数有浮点数，其中整数有大端优先（big-endian）和小端优先（little-endian）两种。
			MP3有使用16位整数、24位整数的，AAC使用32位浮点数。保存方式也有按声道分开保存和两个声道交叉保存的。
		采样率
			常见的有22050、32000、44100（CD）、48000（DVD）等，每秒有多少个采样的意思。
		声道数
			单声道（mono），立体声（stereo）。单声道并不一定是只有一边耳朵会响的意思。
多媒体数据的处理
	视频的处理
		OpenGL ES
			（安卓上可以通过OpenGL把要送去编码的视频画面渲染到一个Surface上，
			苹果可以渲染好画面再把数据读回内存送去编码。
			因为手机性能有限，使用显卡加速就变得特别重要）
			OpenGL渲染线程
				使用单独一个线程来跑OpenGL操作是一个常用手段。
			安卓的TextureView控件
			苹果手机的GLKView控件
			Windows上的Angle函数库
				可以用于在Windows上调用OpenGL ES函数，Angle库转成Direct3D执行具体操作。
				因为可以单步步入到函数里，所以用于调试OpenGL ES代码比手机真机更好用。
			Vertex Shader的编写
				视频上叠加一张图片，视频上叠加一串文字等，需要用它计算定位
			Fragment Shader的编写
				摄像头美颜效果，画面的亮度调整等，需要用它计算颜色。
				用于YUV三个通道合成成RGB，播放器要用到
			内存数据加载到OpenGL纹理（Texture）
			OpenGL纹理读回到内存
				DirectTexture：安卓上执行这种操作的性能优化手段
				TextureCache：苹果手机上执行这种操作的性能优化手段
			OES纹理
				安卓上OpenGL ES的扩展里有一种带有OES标识的纹理。
				对于屏幕映射到Surface要输送到Texture里的情况，要使用这种Texture才行
			画布上贴图
				最基本操作，用于显示一个Texture，
				或者一个Texture到另一个Texture的复制，
				一个Texture上叠加另外一些什么东西，等等很多
				贴的时候因为经过了两个Shader的处理，所以可以实现很多效果
			共享的OpenGL上下文组（shared opengl context）
				画面处理的同时如果还要在屏幕上预览效果，
				那么会需要共享的上下文使得一个纹理在不同上下文中使用
			Surface和Texutre的关系，和Framebuffer的关系
				Framebuffer更靠近OpenGL，Surface更靠近操作系统实现。
				安卓上有SurfaceTexture提供桥梁
			安卓的Grafika示例代码
				这个工程里有大段代码可以抄……里面的EGLCore类也是很好用。发扬拿来主义（
			苹果手机的GPUImage库
				很多拿来就用的省事的东西。避免自己造轮子的坑。
	音频的处理
		混音（audio mixing）
			两路不同的音频采样流合并成一个。
			算平均值不一定是个好的办法，有个 A+B-AB 的我用着感觉还不错。
			（根据A和B的符号不同有不同处理，具体带上audio mixing关键字用搜索引擎可以搜出来）
		改变音量
			根据音量大小乘以系数会发现听感上不对。因为这东西不是线性的，要根据“分贝”的概念去调整大小。
多媒体数据的编码
	常用视频编码：H.264
		帧类型
			关键帧、I帧，P帧，B帧
			帧内分片编码（麒麟芯片的手机硬件编码）
		NALU（网络抽象层单元）
			NALU的分割，start code，裸流表示方式，MP4封装时的表示方式
		帧保存顺序/解码顺序
			B帧因为双向参考，依赖前面的帧和后面的帧，需要两边都解码完了才能解码出来
			所以有B帧的情况下帧的保存顺序、解码顺序和播放顺序不同
		编码参数
			码率，码率控制方式（可变码率，固定码率。或者abr vbr crf等），量化值
			不同帧类型间的量化值比例
			关键帧间隔，GOP（Open GOP和Close GOP，I帧和IDR帧），从特定时间开始播放（Seek，只能从I帧开始解码）
			运动预测范围（merange）
			参考帧数量（reference frame count）
			CABAC，CAVLC
			编码复杂度（Profile：baseline，main，high。Level）
		SEI
			一些额外附加数据，可以放自己用来调试或者确认故障用的信息等
		解码参数（SPS，PPS）
			也是NALU。没有这些参数将无法解码。
		常见编解码器
			软件（用CPU编码） OpenH264，x264，Libavcodec(FFMPEG)
			PC硬件（显卡） Nvidia encoder，Intel QuickSync，DXVA，DXVA2
			安卓（专用芯片） MediaCodec，OpenMax，stagefright
			苹果手机 VideoToolbox
	常用音频编码：AAC（音频方面我特别不熟，所以没什么东西可以写）
		码率（比特率）
		音频的帧（一帧包含若干多个采样，比如1024个）
		两种音频编码格式：LC-AAC，HE-AAC
多媒体数据的封装
	帧（frame）、包（packet）的概念，多个流封装在一起（mux）的概念
	直播常用封装格式（FLV，HLS）
		FLV的OnMetaData信息
		HLS的请求方式、播放流程
	视频封装
		SPS和PPS
			如果直播推流过程中断开重连了这些编码器最开始输出的参数信息要重发。
			听说有数据流每个I帧之前都自带SPS和PPS。这种应该是不需要再专门发吧？
		时间戳（播放时间戳pts，解码时间戳dts）
			安卓硬件编码的解码时间戳
				使用安卓硬件编码时，编码器给出的数据只带有播放时间戳pts不带解码时间戳dts。
				有些播放器，例如Html5用的FLV.js，
				在视频流的pts们和dts们不同属于一个离散数值的集合的情况下
				（简单说就是dts出现过的数值，也会出现在pts里），会出现音画不同步。
				dts是单调递增的，但因为手机编码的帧率不稳定性
				导致dts不容易“在保证一定出现在pts数值的集合”的要求下计算出来
				需要耍一些手段把它坑出来……
		低延迟和秒开优化
			视频只能从I帧开始解码，
			I帧间隔越长又会导致编码效率越低（同等数据量下画面更糊）
			所以对于FLV格式的直播视频流，
			有一种手段就是把最开头一堆视频帧的时间戳都调成0，或者帧间隔时间非常非常小。
			让解码器快速解码完这一段，跟上直播的进度。
		画面方向
			MP4文件中保存了一个变换矩阵，用于表示画面的方向。
			比如一个1280x720的视频，可能播放的时候是720x1280的。
			用视频的四个角的坐标乘以变换矩阵，就能知道它是表示什么方向了。
		播放时间
			苹果手机有一种把第一张画面的时间戳调成负的若干秒的手段实现媒体文件切割。
			这种方式FFMPEG不认，转码后那一段又出来了。
		按照关键帧剪辑
			去除不要的GOP、调整所需部分的时间戳，就能做到不重新转码而剪辑视频。
		安卓的MediaMuxer
			虽然MediaExtractor能支持B帧，但是这个MediaMuxer是不支持B帧的。
			如果想要支持B帧，工程里要用一些其他什么第三方库替代掉它。
	RTMP推流
视频音频捕捉
	Windows平台（可以参考开源项目OBS-Studio）
		桌面捕捉
			桌面DC对内存DC复制
			Desktop Duplicator
		窗口捕捉
			窗口DC对内存DC复制
		游戏捕捉
			注入DLL，挂钩Direct3D或者OpenGL的API。跨进程共享一块内存或者共享一个纹理
			多显卡笔记本如果共享纹理会遇到捕捉进程和游戏进程在不同显卡的资源上运行而抓不到画的问题
		摄像头捕捉
			用VFW或者DirectShow可以获取摄像头数据
		声音捕捉
			用WASAPI捕捉系统声音，API调用起来比较麻烦的样子，可以参考OBS-Studio这个软件怎么调用的。
			麦克风声音就有超多手段可以做到，这里不赘述……
	安卓
		屏幕捕捉
			用Virtual Display来映射屏幕到一个Surface上，
			可以通过Surface定期到Texture再渲染到编码器的Surface的方式来稳定帧率。
			这里的Texture要使用OpenGL ES的扩展里，带有OES标识的那种，否则会不出东西。
		摄像头捕捉和声音捕捉我不会
		目前为止还没有公开的API能不影响用户使用的情况下获取系统声音的样子。
	苹果手机
		屏幕捕捉
			使用ReplayKit接口能获取画面，但是需要App主动支持
		系统声音捕捉
			ReplayKit接口送进来的声音数据是大端优先（Big-endian）的，不同于麦克风的小端优先（little-endian）。
			如果不加处理会听到很大的噪音
		摄像头捕捉和麦克风捕捉我不会
可能有帮助的程序和库
	ijkplayer
		哔哩哔哩弹幕视频网的开源播放器。基于FFMPEG里的FFPlay开发，
		对硬件解码支持更好，在安卓和苹果手机上使用，
		很有名，有很多大公司的项目也在用。
		在Windows下补上几个Windows下没有的函数把ijkmedia那个部分编译过的话，
		自己实现一下声音播放（比如接入PortAudio库）（SDL_Aout），视频交给Angle（SDL_Vout），也能使用。
		没实现的情况下直接跑一下代码打开一个视频文件，看它崩哪里，能大概猜到要实现一个什么。
	MPV
		基于MPlayer开发的播放器。
		在Windows上通过调用它的EXE程序的方法，可以当作自己的播放器核心用，
		具体可以参考SMPlayer看看它是怎么送参数调用MPV的。
		是GPL协议。
	FFMPEG
		从视频文件的拆分装到解码到滤镜处理一应俱全的多媒体库，
		硬件软件编解码都有，在Windows、安卓、苹果手机上都能用。
		根据编译参数不同可以是LGPL协议或者GPL协议。
	GPUImage
		苹果手机上用显卡进行视频处理的库。除了处理也包含了保存成视频文件或者录摄像头什么的功能。
		有一些坑容易掉进去，不过因为使用的人多，网上资料好找，具体要怎么修这些坑的资料也好找。
		有人用Java写了安卓的克隆版。不过功能没苹果的全。
	LFLiveKit
		来疯直播的推流库。在我上次给项目里改它代码的时候它还有些地方做得不好比如打时间戳等。
		不过好在源代码都有，问题可以自己修修。
	Grafika
		说是谷歌官方的示例代码。拿来编译运行就能跑，里面大段代码可以参考。
		不过代码功能比较有限，基本还是要自己根据其他地方的知识结合起来才好使。
	Kickflip
		这个库其实我没用过不过部门里的开发有在用。听说现成的功能挺多的，然而同时坑也挺多的。
		我没用过我就不说什么感受了。
	OpenCV
		CV是计算机视觉的缩写。识别个人脸在哪里、什么方向什么角度，是这个的范畴。
		放这里凑个数……因为其实我也没用过它（捂脸
	OBS-Studio
		这是一个跨平台的从捕捉到直播场景编辑到推流甚至还有简单导播功能一应俱全的软件。
		有不知道怎么实现的地方可以看看他是怎么实现的，代码里做了什么，
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.sorayuki.net/?feed=rss2&#038;p=601</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MPV播放器</title>
		<link>http://blog.sorayuki.net/?p=598</link>
		<comments>http://blog.sorayuki.net/?p=598#comments</comments>
		<pubDate>Sun, 19 Mar 2017 13:36:46 +0000</pubDate>
		<dc:creator><![CDATA[空雪梦见]]></dc:creator>
				<category><![CDATA[函数库]]></category>
		<category><![CDATA[多媒体]]></category>

		<guid isPermaLink="false">http://blog.sorayuki.net/?p=598</guid>
		<description><![CDATA[这个播放器挺神奇的，要说不带界面也不是不行。可以在有做播放器需求的时候，把它拿来套个壳，当作播放器内核来使用。 &#8230; <a href="http://blog.sorayuki.net/?p=598" class="more-link">继续阅读<span class="screen-reader-text">MPV播放器</span> <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>这个播放器挺神奇的，要说不带界面也不是不行。可以在有做播放器需求的时候，把它拿来套个壳，当作播放器内核来使用。</p>
<p><span id="more-598"></span></p>
<p>最近有一个需求，要做个播放视频的功能。但是这东西，有音画同步，有缓冲，什么什么的各种要做的。要搞的东西挺多也比较复杂，讲真我是很不想去弄。但是需求还是要做的，就找各种各样现成的东西来试试看能不能套进去用。</p>
<p>我最初从文件到解码后的视频数据和音频数据这一部分，调用FFMPEG来做，它提供了从读取文件、拆分文件中的数据流、解码数据流整个过程中所需要的功能。但是得到解码后的数据之后，想要播放它还是要做缓冲和音画同步这类事情，在这个时候，我感到里面的不确定因素有点多，需要经验，可能会有卡个很久才能搞定的坑；这个坑自己挖有点太大了，就希望能有更直接的、套上去就能用的东西……</p>
<p>然后这个时候就想起了这么件事。很早以前大概高中的时候吧，用过一个叫MPlayer的播放器。它给我印象比较深的原因，是我用Windows下其他的播放器（当时用的那些播放器都是DirectShow的，然后FFDShow来解码）播放720p XviD的动画片，机器带不动，声音和画面不同步，画面越来越慢。但是MPlayer可以音画同步地播放。但是MPlayer的界面不太好看和方便，后来也用了个叫SMPlayer的播放器，这个播放器和普通的播放器长得差不多，但是它却是调用单独的exe程序来播放，这个播放器做了个图形界面。但是它却能把播放的视频的画面显示在自己的界面上面。界面的exe和播放器核心的exe之间是怎么通信的，以前也没有注意或者留意过，现在有这样需求了，我就琢磨着是不是可以学它做，用来实现这个做播放器的需求。</p>
<p>从网上下了一个SMPlayer以后，启动起来以后打开一个视频文件，然后就开了ProcessExplorer工具来看进程，果然看到了SMPlayer起了一个进程……但是叫MPV.exe而不是MPlayer.exe。（想起之前要做播放器的时候，问过隔壁部门的人。得到的建议是用MPV，不过在那会儿，我的理解是要找MPV的代码来修改或者什么的拿来用，没想到还有直接起exe这种方便的方法……）</p>
<p>ProcessExplorer里面看到SMPlayer执行的MPV的进程的参数列表里，看到了好多东西。有一个 --wid= 在网上查了之后，得知这个参数是指定把画面显示在哪个窗口上。我开了个记事本，用Spy++工具得到窗口句柄的值，然后把值通过这个参数送进去启动MPV，果然画面在记事本的窗口里播出了视频画面……不过在Windows的声音混合器里，看到实际发出声音的是MPV.exe进程而不是Notepad.exe进程。</p>
<p>然后就是怎么控制播放状态的问题。MPV.exe带有参数 --input-file=/dev/stdin （在Windows下也是这么写）的时候，控制是通过标准输入stdin给它送命令，比如 cycle pause up 可以控制视频的播放和暂停，seek可以控制跳转到哪里，这些命令后面带上换行 （\n）送进stdin就可以了。MPV可以把播放过程中的一些状态信息输出到标准错误输出（stderr）。要指定让MPV输出一些视频信息，可以通过 --term-status-msg 这样的参数给它一个输出格式。具体的说明可以在官网这里 https://mpv.io/manual/stable 找到。</p>
<p>所以最后，我在要播放的时候，用 --wid= 指定自己界面上一个空的窗口的句柄给它，然后用匿名管道（通过CreatePipe创建，在STARTUPINFO里面指定）重定向它的标准输入和标准错误输出，把它标准错误输出的文本分析之后显示在界面上，然后把界面上的例如拖动进度条、点击播放暂停这类的操作，用给它标准输入写入字符串的方式来控制它。就简单地做了个“播放器”。</p>
<p>具体的一些参数如果看说明还不太了解的，可以模仿SMPlayer的命令行来写。</p>
<p>最后说一下，这个播放器是GPL协议的。对于GPL协议的东西，如果只是通过运行exe的方式来调用它，不知道能允许做到什么样、是不是强制自己的程序要开源。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.sorayuki.net/?feed=rss2&#038;p=598</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>用OpenCL造GPU加速的AviSynth滤镜</title>
		<link>http://blog.sorayuki.net/?p=584</link>
		<comments>http://blog.sorayuki.net/?p=584#comments</comments>
		<pubDate>Thu, 20 Oct 2016 07:41:26 +0000</pubDate>
		<dc:creator><![CDATA[空雪梦见]]></dc:creator>
				<category><![CDATA[多媒体]]></category>

		<guid isPermaLink="false">http://blog.sorayuki.net/?p=584</guid>
		<description><![CDATA[这一季度有个叫《月曜日のたわわ》的动画片。原作是比村奇石的漫画，这个作者的漫画有一个特点，和其他用黑白来表现亮 &#8230; <a href="http://blog.sorayuki.net/?p=584" class="more-link">继续阅读<span class="screen-reader-text">用OpenCL造GPU加速的AviSynth滤镜</span> <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>这一季度有个叫《月曜日のたわわ》的动画片。原作是比村奇石的漫画，这个作者的漫画有一个特点，和其他用黑白来表现亮度的漫画不同，比村用的是蓝白色调表现。看到这个动画的时候，我在考虑，能不能用一种处理方式，把动画做成漫画那种色调的效果。</p>
<p>思路是很简单的，先拿比村奇石的漫画原作来分析，假设表示同一亮度的像素的 RGB 值是一样的，这样就能得到一个从 YUV 的 Y 通道到 RGB 的映射。结果大概是这样（Y 和 RGB 的范围都是 0 到 255）：</p>
<p>R = Y &gt; 85 ? ((Y - 85) / 255 * 340) : 0;<br />
G = Y;<br />
B = Y &gt; 135 ? 255 : Y + 120;</p>
<p>然后要对每个像素都处理。也就是一个很大的循环，然后 1920x1080 个像素全部按照上面的算一次。只是解码然后转成 RGB 进行上面的处理再转回 YV12（不包含拿去编码），速度不到30帧每秒（没有使用 SIMD 指令集）。</p>
<p>因为处理过程每个像素互不影响，如果能以非常多的线程同时运行，那么可以大大加快速度。这种事情是GPU擅长的，所以就思考着用 OpenCL 做这样的运算，看看能有多块的速度。</p>
<p>OpenCL 原始接口实在是太麻烦了，看着就让人提不起干劲。于是往上搜了一圈，找了一个叫做 EasyCL 的库来。它对 OpenCL 原始接口进行封装，然后就能以简洁得多的方式来调用 OpenCL。EasyCL 在编译过程中需要 OpenCL 的头文件和库。到 Intel、Nvidia、AMD 网站看了一圈，感觉 SDK 都好大好麻烦。然而我在自己系统文件夹下看到了 OpenCL.DLL，它的导出函数也和头文件里能对应上。于是就到 GitHub 上直接找 KhronosGroup 发的 OpenCL 的头文件（这里：https://github.com/KhronosGroup/OpenCL-Headers），下下来以后编译能过了。LIB 文件就通过链接的时候报错信息中找不到的那些符号，自己造一个（用<a title="编辑VC生成的导入库文件" href="http://blog.sorayuki.net/?p=383">这个文章</a>里提到的自制工具）。通过了链接以后，得到了 EasyCL.DLL 和 EasyCL.LIB。</p>
<p>然后利用以下简单的函数就能调用 OpenCL 进行运算了：</p>
<p>EasyCL::isOpenCLAvailable() 可以判断系统能不能调用 OpenCL<br />
EasyCL::createForFirstGpu() 可以创建一个 CL 对象<br />
EasyCL::buildKernelFromString() 可以从源代码字符串创建一个 CL 程序，需要指定入口函数名，Option 是干嘛用的不知道不过可以送一个空字符串进去<br />
CLKernel::in() 可以送从内存拷到显存的参数进去<br />
CLKernel::out() 可以送从显存拷到内存的参数进去，这两个送参数的函数，有些类型是不支持的。不过既然都是指针，可以转成它认的那种指针送进去（数组大小记得也要根据类型的变化而变化）<br />
CLKernel::run_1d() 可以运行程序。两个参数分别是一共有多少任务，然后一次运行多少个任务。<strong>前者要是后者的倍数否则它要报错</strong>，一次最多能运行多少任务可以通过 EasyCL::getMaxWorkgroupSize() 来获取。</p>
<p>解释一下什么叫“一共多少任务、一次运行多少任务”。比如这个视频是 1920x1080 的，那么一共有 2073600 个像素点，每个像素处理一次的话就一共有 2073600 个任务。通过调用函数得知一次能运行 1024 个任务，那么就可以送 2073600 和 1024 作为参数到 run_1d 函数里去（因为 2073600÷1024 能整除、没有余数）。然后在 OpenCL 代码里用 get_global_id(0) 获取当前是第几个任务，根据任务的编号来决定要做什么任务。（在本文中，编号就是“第几个像素”）</p>
<p>这个库在出现错误的时候，会抛异常给你而不是返回错误码或者什么的。所以要做好异常捕捉，不然异常抛出来程序没捕捉、程序就崩了。</p>
<p>改成用 GPU 处理之后，处理速度从原来的每秒 30 帧不到变成了现在接近 90 帧。效果还是很明显的。</p>
<p>P.S. 从QQ群内聊天得知，微软有个 DirectCompute 能做类似的事情。还有个关键字是 C++ AMP，可以直接在编译时生成拿到 GPU 上跑的代码，需要 DirectX 11 支持。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.sorayuki.net/?feed=rss2&#038;p=584</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
