wenfengsun

jmap-jmap是java自带的jvm内存工具,可以通过在服务器执行:jmap -dump:format=b,file=heap.bin pid 将jvm内存dump到一个文件里。
这里可以通过jhat-也是一个java自带的内存分析工具 ,来分析dump出来的heap.bin,它会启动一个7000端口的服务,你可以在浏览器访问这个端口,获取堆栈信息;命令: jhat -J-Xmx512m heap.bin
另外一个工具就是mat:http://www.eclipse.org/mat/ 可以在eclipse上安装这个插件,通过eclipse打开这个文件就可以分析jvm的内存情况。如下图:
另外一个分析jvm中native stack的分析工具是google的perftools,这个没有深入研究,先mark一下。
分析jvm行为的工具,btrace,可以通过编写源码的方式来观察jvm中方法调用等一些行为。

Posted on: April 9, 2012

Highly Scalable Blog

NoSQL databases are often compared by various non-functional criteria, such as scalability, performance, and consistency. This aspect of NoSQL is well-studied both in practice and theory because specific non-functional properties are often the main justification for NoSQL usage and fundamental results on distributed systems like the CAP theorem apply well to NoSQL systems.  At the same time, NoSQL data modeling is not so well studied and lacks the systematic theory found in relational databases. In this article I provide a short comparison of NoSQL system families from the data modeling point of view and digest several common modeling techniques.

I would like to thank Daniel Kirkdorffer who reviewed the article and cleaned up the grammar.

To  explore data modeling techniques, we have to start with a more or less systematic view of NoSQL data models that preferably reveals trends and interconnections. The following figure depicts imaginary “evolution” of the major NoSQL…

View original post 3,581 more words

JNI:Java Native Interface,它允许Java代码和其他语言写的代码进行交互。
问题描述:
以JNI方式实现的jmagick在调用imagemagick的接口时,一直出现高内存消耗,最高达到10G内存。但是无论以jmx方式启动后,用jconsole监控Java Heap内存状况,还是用jmap-jhat方式dump并察看java Heap内存状况,都发现java Heap只占用了几百M内存。
这让人联想到了JNI的内存占用机制,首先,JVM内存结构如下图:

这个结构中,Nativeinterface组件:
与nativelibraries交互,是其它编程语言交互的接口。当调用native方法的时候,就进入了一个全新的并且不再受虚拟机限制的世界,所以也很容易出现JVM无法控制的nativeheapOutOfMemory。
好,至此,我们大约知道了,JNI这种native方式产生的内存消耗是位于NativeMethodStack中,而不是Heap中,这也就解释了为何Heap内存消耗很小,而整个JVM占用内存很大的原因。
再次,来分析一下JNI编程时的实现方式:
Local Reference 和 Local Reference 表
理解 Local Reference 表的存在是理解 JNI Local Reference 的关键。
JNI Local Reference 的生命期是在 native method 的执行期(从 Java 程序切换到 native code 环境时开始创建,或者在 native method 执行时调用 JNI function 创建),在 native method 执行完毕切换回 Java 程序时,所有 JNI Local Reference 被删除,生命期结束(调用 JNI function 可以提前结束其生命期)。
实际上,每当线程从 Java 环境切换到 native code 上下文时(J2N),JVM 会分配一块内存,创建一个 Local Reference 表,这个表用来存放本次 native method 执行中创建的所有的 Local Reference。每当在 native code 中引用到一个 Java 对象时,JVM 就会在这个表中创建一个 Local Reference。比如,实例 1 中我们调用 NewStringUTF() 在 Java Heap 中创建一个 String 对象后,在 Local Reference 表中就会相应新增一个 Local Reference。

⑴运行 native method 的线程的堆栈记录着 Local Reference 表的内存位置(指针 p)。
⑵ Local Reference 表中存放 JNI Local Reference,实现 Local Reference 到 Java 对象的映射。
⑶ native method 代码间接访问 Java 对象(java obj1,java obj2)。通过指针 p 定位相应的 Local Reference 的位置,然后通过相应的 Local Reference 映射到 Java 对象。
⑷当 native method 引用一个 Java 对象时,会在 Local Reference 表中创建一个新 Local Reference。在 Local Reference 结构中写入内容,实现 Local Reference 到 Java 对象的映射。
⑸ native method 调用 DeleteLocalRef() 释放某个 JNI Local Reference 时,首先通过指针 p 定位相应的 Local Reference 在 Local Ref 表中的位置,然后从 Local Ref 表中删除该 Local Reference,也就取消了对相应 Java 对象的引用(Ref count 减 1)。
⑹当越来越多的 Local Reference 被创建,这些 Local Reference 会在 Local Ref 表中占据越来越多内存。当 Local Reference 太多以至于 Local Ref 表的空间被用光,JVM 会抛出异常,从而导致 JVM 的崩溃。
结论:
在每次调用JNI创建对象,并且执行完操作后,执行destroy方法释放回收native mem中的对象和内存,重新发布程序后,问题解决。

上周末到了杰科HD300 高清播放机+2T WD绿盘,准备一边当高清播放机,一边transmission下载。
可是令人恼火的是,杰科的linux系统并未开放ssh,ftp和telent端口,这让人一筹莫展。
后来经过摆弄菜单,发现在网络播放里,有一个BT选项,点进去就是transmission控制,可以设置简单的存储目的,速度限制等三个选项。但是通过上传PT种子下载,只能开始两个任务,这非常令人难以接受。
用windows在运行中访问网上邻居:\\192.168.x.x(你的高清机IP),可以进入内置硬盘中,我这里是D盘。
发现里面有transmission文件夹,进入,编辑settings.json,将rpc-whitelist修改为你所在的局域网,例如:192.168.1.*,*表示通配符。
此时,在浏览器输入:http://your_ip:9091,惊奇的发现进入了transmission的管理界面,这里可以开始多个任务。
至此OK,可以通过管理界面进行PT下载了。

首先,你需要一个vpn的帐号,来通过Ubuntu的vpn进行连接。连接就在网络菜单的vpn连接。
但是,通过vpn默认会将整个vpn配置为默认路由。这个可以在命令行模式下察看,点开term,在命令行输入route,会显示默认路由:
内核 IP 路由表
目标 网关 子网掩码 标志 跃点 引用 使用 接口
default 192.168.1.1 0.0.0.0 UG 0 0 0 wlan0
如果拨号vpn之后,默认路由变化了,则说明vpn抢走了你默认路由,这样所有的网络都会通过vpn访问,这不是我们的目的。
我们想那写墙了的网站,通过vpn访问,于是,我们先修改vpn不抢占默认路由。
选择路由,配置,选定对应的配置文件,点击编辑,弹出的窗口点击路由,弹出的新窗口,选中下方两个checkbox
1. 忽略自动获取的路由
2.仅将此连接用于相对应的网络上的资源
重新连接,发现vpn不再抢占默认路由,随后,我们想让google的访问走我们指定的vpn连接:
我们可以先ping一下google的ip地址:
># ping http://www.google.com.hk
PING www-hk.l.google.com (74.125.128.94) 56(84) bytes of data.
64 bytes from hg-in-f94.1e100.net (74.125.128.94): icmp_req=1 ttl=44 time=233 ms
看到这个ip地址是:74.125.128.94
通过设置路由来指定这个ip地址的访问通过vpn访问:
首先,vpn连接后,出现的新路由接口是ppp0
192.168.3.1 0.0.0.0 255.255.255.255 UH 0 0 0 ppp0
可以在命令行先行测试:
输入:># route add -net 74.125.128.0 netmask 255.255.255.0 dev ppp0
再次执行route -n,可以看到新配置的路由已经生效:
74.125.128.0 0.0.0.0 255.255.255.0 U 0 0 0 ppp0
如果你的google已经被墙,这时再访问,惊奇的发现又可以访问了。
关于你的ip和子网掩码需要对应配置,例如ip:74.125.128.0,子网掩码最后一个段也需要是0,表示0-255范围。
现在我们可以通过ubuntu的配置界面配置这些路由。
打开网络菜单下的路由配置,选中你的路由配置,点编辑选项,弹出窗口中选择路由,
将74.125.128.0填入第一个字段,255.255.255.0填入第二个,然后勾选最下方的两个checkbox,保存。
重新连接vpn,这时你通过route -n可以发现新配置的路由已经生效了。
好,基本方法有了,如果你需要访问twitter,你需要首先收集twitter的所有host信息,加入/etc/hosts,然后将配置的host中的所有网段都加入到vpn的路由策略中,重新连接路由,就可以访问twitter.com了。

1. 安装PCRE(Perl Compatible Regular Expressions)
地址:http://sourceforge.net/projects/pcre/files/pcre/8.30/pcre-8.30.tar.gz/download
执行:./configure make&make install
此模块用于正则表达式,nginx rewrite
2. 安装zlib(用于gzip模块)
地址:http://www.linuxidc.com/upload/2008_09/08091520581351.tar
执行:./configure make&make install
3. 安装nginx(1.0.13)
地址:http://nginx.org/download/nginx-1.0.13.tar.gz
./configure make&make install
4. 启动nginx,可能会报错:
[root@sever sbin]# ./nginx
./nginx: error while loading shared libraries: libpcre.so.1: cannot open shared object file: No such file or directory
切换到/usr目录,执行:ln -s /usr/local/lib/libpcre.so.1 /lib
在/lib中添加一个软链接。
再次启动,OK。

curl –compressed -o /dev/null -w %{time_connect}:%{time_starttransfer}:%{time_total} -s http://s1.t.itc.cn/mblog/pic/20122_3_12/13470602121109497.jpg
打印连接时间,开始传输时间和总耗时,并不显示curl获取的结果。
gdb 可以进入c调试模式,
#>gdb curl
GNU gdb Fedora (6.8-27.el5)
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type “show copying”
and “show warranty” for details.
This GDB was configured as “x86_64-redhat-linux-gnu”.
(gdb) r –compressed -o /dev/null -w %{time_connect}:%{time_starttransfer}:%{time_total} -s http://s1.t.itc.cn/mblog/pic/20122_3_12/13470602121109497.jpg
可以打印其中的调试信息;如果停止响应,^c可以查看停止在何处;bt可以查看更详细的信息。
—————
shell命令行快捷键:
1. ctrl+a:回到行首
2. ctrl+e:回到行尾
3. crtl+r:在历史中查找
4. esc+b:光标回退一个单词
5. esc+f:光标前进一个单词
6. ctrl+u:删除到行尾

vi命令快捷键:
dw:删除单词
dd:删除整行
dG:删除到文件末尾
gg:回到文件开头
G:回到文件末尾
A:行标移动到行末尾,并切换为insert状态
h:向前一个字符
l:向后一个字符
j:向上一行
k:向下一行
ctrl+f:向下翻页
ctrl+b:向上翻页

linux配置ssh公钥:
首先在本机生成公钥:ssh-keygen -t rsa
一路回车,在~/.ssh里有is_rsa和id_rsa.pub
将公钥的内容:id_rsa.pub的内容拷贝到对应机器的~/.ssh中的authorized_keys文件中即可。

alias ll=’ls -lrt’ 表示为别名ll设置实际操作命令;
awk ‘{sum+=$0}END{print sum/NR}’ 求平均值

用户故事是从用户的角度来描述用户渴望得到的功能。一个好的用户故事包括三个要素:
1.角色:谁要使用这个功能。
2.活动:需要完成什么样的功能。
3.商业价值:为什么需要这个功能,这个功能带来什么样的价值。
用户故事通常按照如下的格式来表达:
英文:
As a , I want to , so that .
中文:
作为一个, 我想要, 以便于
举例:
作为一个“网站管理员”,我想要“统计每天有多少人访问了我的网站”,以便于“我的赞助商了解我的网站会给他们带来什么收益。”
需要注意的是用户故事不能够使用技术语言来描述,要使用用户可以理解的业务语言来描述。
Ron Jeffries的3个C
关于用户故事,Ron Jeffries用3个C来描述它:
卡片(Card) – 用户故事一般写在小的记事卡片上。卡片上可能会写上故事的简短描述,工作量估算等。
交谈(Conversation)- 用户故事背后的细节来源于和客户或者产品负责人的交流沟通。
确认(Confirmation)- 通过验收测试确认用户故事被正确完成。
用户故事的六个特性- INVEST
INVEST = Independent, Negotiable, Valuable, Estimable, Small, Testable
一个好的用户故事应该遵循INVEST原则。
独立性(Independent)— 要尽可能的让一个用户故事独立于其他的用户故事。用户故事之间的依赖使得制定计划,确定优先级,工作量估算都变得很困难。通常我们可以通过组合用户故事和分解用户故事来减少依赖性。
可协商性(Negotiable)— 一个用户故事的内容要是可以协商的,用户故事不是合同。一个用户故事卡片上只是对用户故事的一个简短的描述,不包括太多的细节。具体的细节在沟通阶段产出。一个用户故事卡带有了太多的细节,实际上限制了和用户的沟通。
有价值(Valuable)— 每个故事必须对客户具有价值(无论是用户还是购买方)。一个让用户故事有价值的好方法是让客户来写下它们。一旦一个客户意识到这是一个用户故事并不是一个契约而且可以进行协商的时候,他们将非常乐意写下故事。
可以估算性(Estimable)—开发团队需要去估计一个用户故事以便确定优先级,工作量,安排计划。但是让开发者难以估计故事的问题来自:对于领域知识的缺乏(这种情况下需要更多的沟通),或者故事太大了(这时需要把故事切分成小些的)。
短小(Small)— 一个好的故事在工作量上要尽量短小,最好不要超过10个理想人/天的工作量,至少要确保的是在一个迭代或Sprint中能够完成。用户故事越大,在安排计划,工作量估算等方面的风险就会越大。
可测试性(Testable)—一个用户故事要是可以测试的,以便于确认它是可以完成的。如果一个用户故事不能够测试,那么你就无法知道它什么时候可以完成。一个不可测试的用户故事例子:软件应该是易于使用的。

facebook将他们的thrift开源,thrift是一个用于不同系统间通信的协议,它大大提高了系统间数据通信的效率。more about thrift

thrift为linux和windows都提供了相应的gen命令,安装之后只需要调用thrift命令就可以生产对应语言的程序。
thrift生成的java源文件主要是描述object和interface,实现接口的代码需要自己实现;
下面是一个简单的例子,通过定义一个message,和一个获取message对象的接口,来实现client获取数据对象的结果。
首先要定义thrift脚本,thrift的基本介绍:

基本名词

  • Types: 为了满足多语言平台的要求,需要提供基本数据类型来进行转换。比如在C++的Map和Python的Dict之间能够相互转换。
  • Transport: 对于每一种语言,都应该有一个抽象的公共层来完成对应的双向数据传输。
  • Protocal: 数据需要有一种方式来使用对应的传输层的code,而不用关心传输层的具体实现细节。
  • Versioning:数据需要有自己的版本号来实现对应的健壮性。
  • Processing : 产生code来完成RPC调用。

类型
1. Goals:

  • 1. 使用最基本的数据类型,不管上层使用怎么样的编程语言。
  • 2. 不使用动态数据类型,也不需要开发者写对象序列化或者传输的代码(已经封装)。
  • 3. IDL 用来告诉代码生成器如何在多语言之上安全地传输数据。

2. Base Types:

  • 1. 选取了大多数语言中都存在的基本数据类型,忽略了一些语言中特别的类型。(无符号类型), 所有的基本
  • 类型都是有符号的。
  • 2. 基本类型包括bool, byte,i16,i32,i64,double,string。分别对应java中的boolean,byte,short,int,long,double,String

3. Structs:

  • 1. 提供了一种common的对象来代表面向对象语言中的对象类型,每一个filed都有一个标志符。
  • 2. 每个filed必须有一个标志符和一个给定的默认值。标志符如果没有给定的话,会自动分配一个,但是强烈建议给定标志符,为了方便版本控制。

4. Container:

  • 1. List, set, Map。其中List被翻译成STL中的vector, 或者Java中的ArrayList,set和map类似。
  • 2. 要求对应的基本语言类型支持对应的Thrift的基本数据类型。
  • 3. 在每一种目标语言中,对于每一种定义的类型,生成两个接口,read和write,用于序列化和传输对应的数据结构。

5. Exceptions:

  • 1. 使用Exception关键字来代替对应的structs关键字,得到对应的基本类。
  • 2. 生成的对象继承自每一种语言中的对应的Exception的基类。

6. Services:

  • 1. 提供了服务端的接口调用,通过代码生成器会生成Client端和Server端的桩函数接口,共上层调用使用。
  • 2. 在每一个函数前面可以加上async关键字,这个关键字能够实现对应的一部方法。
  • 3. 一个纯void函数也会返回一个response,用来保证该操作在服务端已经被执行。
  • 4. async call只保证消息在传输层是没有问题的,因此,在使用async的时候需要保证当前的使用环境对于丢包是可以接受的。

3. Transport
1. 接口

  • 1. 传输层用来促进数据传输。一个关键的设计是Thrift将数据传输层和代码生成器分开。
  • 2. 由于抽象的IO炒作产生的tradoff相比起实际的IO操作显得微不足道。
  • 3. 需要满足的基本功能:如何读写数据,不管是在网络中,还是共享内存,还是从磁盘文件中读写。
  • 4. 提供的接口如下:open, close,isOpen, read, write, flush。还有一些用于batch操作的接口
  • 5. 对于TServerTransport,还有接口用来创建对应的Transport对象。如open, listen, accept, close。

2. 实现

  • 1. TSocket 类对于TCP/IP的流式套接字实现了一个通用,简单的接口。
  • 2. TFileTransport类实现了一个抽象接口,用于将磁盘文件导入到数据流中。

3. Utilities

  • 1. 网络传输层设计的支持简单的扩展,比如组合,TBufferdTransport实现在传输层实现了buffer的功能,TFrameTransport实现了数据头定长大小,用于Noblocking传输。TMemoryBuffer允许读和写直接在进程的堆顶完成。

4. Protocal
1. 接口

  • 1. 另外一块主要的部分是thrift中将数据结构和传输层分离,thrift定义了一些传输时的基本类型,但是没有强制定义需要实现的编码格式,只要能被代码生成器识别就好。
  • 2. thrift协议的接口简单明了,支持两种功能:双向有序的消息,基本类型,容器,结构的编码。

2. 结构

  • 1. Thrift中默认使用流式结构来传输对应的协议,这样避免了因为需要分片或者在发送前整体计算带来的性能损失。当有的场景需要使用分片,或者分片的性能优势更加明显的时候,可以使用TFramedTransport这个抽象接口类。

3. 实现

  • 1. Thrift的实现了一个空间高效的二进制协议,所有的数据都以一个扁平的二进制格式存在,整形被转换为网络字节序,字符串在头上加上它的长度,所有的消息和field 头都用它的整数序列号,忽略这些filed中的字符串名字。
  • 2. 我们没有做过多的性能优化,为了代码的整洁和高效。如果有需要,可以加入这些优化方案。

5.Versioning
1. 域标志符

  • 1. Thrift在版本控制上面是健壮的,它可以接受旧客户端过来的请求并正确处理。
  • 2. Thrift中的版本控制是通过域标志符来确定的,thrift结构中的每一项前面都有一个对应的标志符,标志符和数据类型唯一化开item。
  • 3. 为了避免冲突,自动分配的标志符从-1开始递减,人工定义的标志符只能为整数。
  • 4. 在数据是自定义的前提下,当反序列化的时候,thrift的code 生成器可以根据标识符来判断读取到的数据是否对齐,也可以跳过不能识别的域,保证兼容性。
  • 5. 域标志符也可以在参数列表中声。

2. Isset

  • 1. 用来标注必须存在于结构体中的域,如果必须存在,标注为true,这样可以当该域不存在的时候,可以通知上层调用者。

3. 场景分析
1. 4种场景,加减域/新旧client或者server。
4. Protocal/Transport Versioning
1. 实现了TProtocal的抽象接口,用来让协议的实现自己来管理版本。
6. RPC 实现
1. TProcessor
1. 核心类,抽象出client和server的基类,核心功能是处理input和output。
2. Generated code
2. 自动生成client端和server端的调用接口。
3. TServer
1. Tserver 可以有多种实现,来满足不同的需求。如单线程的TSimpleServer, 每个连接一个线程的TThreadedServer, 还有基于线程库的TThreadPoolServer.
2. 开发者可以实现自己的TServer。
下面是message的简单例子:
1. 定义message.thrift
namespace java com.company.thrift.model
struct Message {
1: i64 id,
2: string body,
3: string content,
4: i64 userId,
5: string picpath,
}

2. 定义service
namespace java com.company.thrift
include "message.thrift"
service MessageService{
message.Message getMessage(1: i64 id);
}

3. 执行生成命令
# thrift –gen java message.thrift
# thrift –gen java messageService.thrift
将生成的Message.java和messageService.java拷贝到你的工程中。
4. 编写接口实现:
public class MessageServiceImpl implements MessageService.Iface{

@Override
public Message getMessage(long id) throws TException {
Message message = new Message();
message.setId(id);
message.setBody(“Test body value”);
message.setContent(“”);
message.setPicpath(“http://www.google.com.hk/images/nav_logo86.png”);
message.setUserId(1001);
return message;
}

}
5. 编写服务器端调用
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TBinaryProtocol.Factory;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.server.TThreadPoolServer.Args;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;

import com.conpany.thrift.MessageService.Processor;

public class Server {
public void startServer() {
try {
TServerSocket serverTransport = new TServerSocket(1234);
MessageService.Processor process = new Processor(new MessageServiceImpl());
Factory portFactory = new TBinaryProtocol.Factory(true, true);
Args args = new Args(serverTransport);
args.processor(process);
args.protocolFactory(portFactory);
TServer server = new TThreadPoolServer(args);
server.serve();
} catch (TTransportException e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
Server server = new Server();
server.startServer();
}
}
6. 编写客户端代码:
public class Client {
public void startClient() {
TTransport transport;
try {
transport = new TSocket("localhost", 1234);
TProtocol protocol = new TBinaryProtocol(transport);
MessageService.Client client = new MessageService.Client(protocol);
transport.open();
Message msg = client.getMessage(1020);
System.out.println(msg);
transport.close();
} catch (TTransportException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
Client client = new Client();
client.startClient();
}
}
7. 启动Server,并执行客户端代码,可以看到客户端会打印一行服务器生成的message数据

Message(id:1020, body:Test body value, content:, userId:1001, picpath:http://www.google.com.hk/images/nav_logo86.png)

ios平台拍摄视频是有方向信息的,当手持设备在朝向非正常的方向拍摄时,会造成视频上传后处于颠倒的状态。(默认的正确方向是横向,home键在右侧)
做到自动旋转,需要两步:
1. 获取视频文件的方向信息;
2. 根据方向信息旋转;
解决这两个问题可以通过两个开源软件完成:mediainfo(http://mediainfo.sourceforge.net)和ffmpeg(http://www.ffmpeg.org)
一、安装mediainfo
下载libzen0,libmediainfo0和mediainfo三个文件,注意选择redhat的操作系统版本。
1. 安装libzen0: rpm -i libzen0-0.4.20-1.x86_64.RHEL_5.rpm
2. 安装libmediainfo0: rpm -i libmediainfo0-0.7.48-1.x86_64.RHEL_5.rpm
3. 安装mediainfo: rpm -i mediainfo-0.7.48-1.x86_64.RHEL_5.rpm
查看已安装的信息可以通过rpm -q libzen0来查看。
此时可以通过mediainfo来查看视频文件的扩展信息:
例如:mediainfo -f iphone_mov.MOV |grep Rotation
Rotation : 90.000
Rotation : 90?
这里就获取到了视频的偏转信息;
二、安装ffmpeg
可以通过svn方式直接下载源码:
svn co svn://svn.mplayerhq.hu/ffmpeg/trunk ffmpeg
然后切换到ffmpeg目录下:执行./configure –enable-gpl –enable-shared –disable-ffserver –disable-yasm 进行安装
再执行make
make install
最后运行ffmpeg,此时可能会有error信息:
ffmpeg: error while loading shared libraries: libavdevice.so.52: cannot open shared object file: No
这个是因为lib未加入默认目录。
切换到目录:
# cd /etc/ld.so.conf.d
# vi ffmpeg.conf
加入目录/usr/local/lib
执行ldconfig
再次执行ffmpeg,运行OK。
执行命令旋转:ffmpeg -vf “transpose=1” -i iphone_mov.MOV ouptup.MOV
下载到电脑运行,OK,视频已经旋转。


  • None
  • No comments yet

Categories