IT行业是一个踩在巨人肩膀上前进的行业,否则做的事情不一定有意义,所以我也是基于havlenapetr移植的ffmpeg基础上做了些改进,他做的主要贡献有:
1. 移植了ffmpeg并将与媒体相关的结构体在java层重新进行了封装,方便应用程序在java层直接操作ffmpeg API,如各种媒体格式转码及播放,如图1所示
2. 模仿Android的MediaPlayer类实现了ffmpeg的播放接口,如setDataSource(),setDisplay(),start(), stop(),pause()等,缺点是没有实现seek功能。
3. 实现了一个简单播放器功能,抛弃掉ffmpeg自带的ffplay播放器,他重新实现了音视频的分离播放和同步处理等播放器应有的功能。

图1 ffmpeg的java层封装
基于Android移植ffmpeg的意义在于:
1.解决了Android媒体框架OpenCore的多媒体支持不足,虽然说Android平台的软解功耗大,但是从PC机的发展历史看,Android的视频处理以后也会走以硬解为主,软解为辅的路线。
2. 解决Android平台直播的问题,虽然Android支持RTSP/RTP的直播方案,但是这种方案主要是普遍用在电信设备上,基于互联网的海量视频服务提供者还是以http live streaming方案为主,测试时可以用ffmpeg将直播流打包成分段的ts流(如10秒钟),然后组织成m3u8文件实现完整的直播方案,而且互联网的直播内容还有很多是基于mms协议的,视频格式是wmv,要聚集这些内容都是离不开ffmpeg软解的。
移植步骤:
1. 下载havlenaptr移植的ffmpeg(https://github.com/havlenapetr/FFMpeg/zipball/debug).
2. 基于ndk编译下载的ffmpeg,出现的编译问题主要是文件的存放路径不对,修改jni目录下的Android.mk文件,增加头文件目录$(LOCAL_PATH)/../include/android,修改Vector.h文件为:
#include <cutils/log.h>
#include <utils/VectorImpl.h>
#include <utils/TypeHelpers.h>
3.utils目录下缺少TypeHelpers.h,添加该文件:
-
-
-
-
-
-
- #ifndef ANDROID_TYPE_HELPERS_H
- #define ANDROID_TYPE_HELPERS_H
- #include <new>
- #include <stdint.h>
- #include <string.h>
- #include <sys/types.h>
-
- namespace android {
-
-
-
-
- template <typename T> struct trait_trivial_ctor { enum { value = false }; };
- template <typename T> struct trait_trivial_dtor { enum { value = false }; };
- template <typename T> struct trait_trivial_copy { enum { value = false }; };
- template <typename T> struct trait_trivial_assign{ enum { value = false }; };
- template <typename T> struct trait_pointer { enum { value = false }; };
- template <typename T> struct trait_pointer<T*> { enum { value = true }; };
- #define ANDROID_BASIC_TYPES_TRAITS( T ) /
- template<> struct trait_trivial_ctor< T > { enum { value = true }; }; /
- template<> struct trait_trivial_dtor< T > { enum { value = true }; }; /
- template<> struct trait_trivial_copy< T > { enum { value = true }; }; /
- template<> struct trait_trivial_assign< T >{ enum { value = true }; };
- #define ANDROID_TYPE_TRAITS( T, ctor, dtor, copy, assign ) /
- template<> struct trait_trivial_ctor< T > { enum { value = ctor }; }; /
- template<> struct trait_trivial_dtor< T > { enum { value = dtor }; }; /
- template<> struct trait_trivial_copy< T > { enum { value = copy }; }; /
- template<> struct trait_trivial_assign< T >{ enum { value = assign }; };
- template <typename TYPE>
- struct traits {
- enum {
- is_pointer = trait_pointer<TYPE>::value,
- has_trivial_ctor = is_pointer || trait_trivial_ctor<TYPE>::value,
- has_trivial_dtor = is_pointer || trait_trivial_dtor<TYPE>::value,
- has_trivial_copy = is_pointer || trait_trivial_copy<TYPE>::value,
- has_trivial_assign = is_pointer || trait_trivial_assign<TYPE>::value
- };
- };
- template <typename T, typename U>
- struct aggregate_traits {
- enum {
- is_pointer = false,
- has_trivial_ctor = traits<T>::has_trivial_ctor && traits<U>::has_trivial_ctor,
- has_trivial_dtor = traits<T>::has_trivial_dtor && traits<U>::has_trivial_dtor,
- has_trivial_copy = traits<T>::has_trivial_copy && traits<U>::has_trivial_copy,
- has_trivial_assign = traits<T>::has_trivial_assign && traits<U>::has_trivial_assign
- };
- };
-
-
-
-
-
- ANDROID_BASIC_TYPES_TRAITS( void );
- ANDROID_BASIC_TYPES_TRAITS( bool );
- ANDROID_BASIC_TYPES_TRAITS( char );
- ANDROID_BASIC_TYPES_TRAITS( unsigned char );
- ANDROID_BASIC_TYPES_TRAITS( short );
- ANDROID_BASIC_TYPES_TRAITS( unsigned short );
- ANDROID_BASIC_TYPES_TRAITS( int );
- ANDROID_BASIC_TYPES_TRAITS( unsigned int );
- ANDROID_BASIC_TYPES_TRAITS( long );
- ANDROID_BASIC_TYPES_TRAITS( unsigned long );
- ANDROID_BASIC_TYPES_TRAITS( long long );
- ANDROID_BASIC_TYPES_TRAITS( unsigned long long );
- ANDROID_BASIC_TYPES_TRAITS( float );
- ANDROID_BASIC_TYPES_TRAITS( double );
-
-
-
-
-
- template<typename TYPE> inline
- int strictly_order_type(const TYPE& lhs, const TYPE& rhs) {
- return (lhs < rhs) ? 1 : 0;
- }
- template<typename TYPE> inline
- int compare_type(const TYPE& lhs, const TYPE& rhs) {
- return strictly_order_type(rhs, lhs) - strictly_order_type(lhs, rhs);
- }
-
-
-
-
- template<typename TYPE> inline
- void construct_type(TYPE* p, size_t n) {
- if (!traits<TYPE>::has_trivial_ctor) {
- while (n--) {
- new(p++) TYPE;
- }
- }
- }
- template<typename TYPE> inline
- void destroy_type(TYPE* p, size_t n) {
- if (!traits<TYPE>::has_trivial_dtor) {
- while (n--) {
- p->~TYPE();
- p++;
- }
- }
- }
- template<typename TYPE> inline
- void copy_type(TYPE* d, const TYPE* s, size_t n) {
- if (!traits<TYPE>::has_trivial_copy) {
- while (n--) {
- new(d) TYPE(*s);
- d++, s++;
- }
- } else {
- memcpy(d,s,n*sizeof(TYPE));
- }
- }
- template<typename TYPE> inline
- void assign_type(TYPE* d, const TYPE* s, size_t n) {
- if (!traits<TYPE>::has_trivial_assign) {
- while (n--) {
- *d++ = *s++;
- }
- } else {
- memcpy(d,s,n*sizeof(TYPE));
- }
- }
- template<typename TYPE> inline
- void splat_type(TYPE* where, const TYPE* what, size_t n) {
- if (!traits<TYPE>::has_trivial_copy) {
- while (n--) {
- new(where) TYPE(*what);
- where++;
- }
- } else {
- while (n--) {
- *where++ = *what;
- }
- }
- }
- template<typename TYPE> inline
- void move_forward_type(TYPE* d, const TYPE* s, size_t n = 1) {
- if (!traits<TYPE>::has_trivial_copy || !traits<TYPE>::has_trivial_dtor) {
- d += n;
- s += n;
- while (n--) {
- --d, --s;
- if (!traits<TYPE>::has_trivial_copy) {
- new(d) TYPE(*s);
- } else {
- *d = *s;
- }
- if (!traits<TYPE>::has_trivial_dtor) {
- s->~TYPE();
- }
- }
- } else {
- memmove(d,s,n*sizeof(TYPE));
- }
- }
- template<typename TYPE> inline
- void move_backward_type(TYPE* d, const TYPE* s, size_t n = 1) {
- if (!traits<TYPE>::has_trivial_copy || !traits<TYPE>::has_trivial_dtor) {
- while (n--) {
- if (!traits<TYPE>::has_trivial_copy) {
- new(d) TYPE(*s);
- } else {
- *d = *s;
- }
- if (!traits<TYPE>::has_trivial_dtor) {
- s->~TYPE();
- }
- d++, s++;
- }
- } else {
- memmove(d,s,n*sizeof(TYPE));
- }
- }
-
-
-
-
- template <typename KEY, typename VALUE>
- struct key_value_pair_t {
- KEY key;
- VALUE value;
- key_value_pair_t() { }
- key_value_pair_t(const key_value_pair_t& o) : key(o.key), value(o.value) { }
- key_value_pair_t(const KEY& k, const VALUE& v) : key(k), value(v) { }
- key_value_pair_t(const KEY& k) : key(k) { }
- inline bool operator < (const key_value_pair_t& o) const {
- return strictly_order_type(key, o.key);
- }
- };
- template<>
- template <typename K, typename V>
- struct trait_trivial_ctor< key_value_pair_t<K, V> >
- { enum { value = aggregate_traits<K,V>::has_trivial_ctor }; };
- template<>
- template <typename K, typename V>
- struct trait_trivial_dtor< key_value_pair_t<K, V> >
- { enum { value = aggregate_traits<K,V>::has_trivial_dtor }; };
- template<>
- template <typename K, typename V>
- struct trait_trivial_copy< key_value_pair_t<K, V> >
- { enum { value = aggregate_traits<K,V>::has_trivial_copy }; };
- template<>
- template <typename K, typename V>
- struct trait_trivial_assign< key_value_pair_t<K, V> >
- { enum { value = aggregate_traits<K,V>::has_trivial_assign};};
-
- };
-
- #endif // ANDROID_TYPE_HELPERS_H
4.编译中出现 make: *** No rule to make target `/cygdrive/e/workspace/myffmpeg/obj/local/armeabi/libjniaudio.so', needed by `/cygdrive/e/workspace/myffmpeg/obj/local/armeabi/libmediaplayer.a'. Stop. 需要把下载的ffmpeg中的libjniaudio.so和libjnivideo.so放到错误中指定的目录下。
5. 编译成功后运行的结果如下:

当点击媒体文件播放时,发现画面显示不正常,如下所示:

调试后发现MediaPlayer.cpp中没有像Android自带播放器一样实现OnVideoSizeChangedListener的回调函数,当播放视图(SurfaceView)创建后没有根据实际播放的视频大小做调整,而且画面没有居中显示,所以我在此基础上做了如下改进
1. 实现OnVideoSizeChangedListener接口,通知播放界面调整大小
2. 播放界面剧中显示,等比率缩放视频大小。
3. 实现Seek功能
- 3.1 要实现getCurrentPosition(), 因为拖动时传过来的是时间millisecond
- 3.2 把milli second转化成timestamp掉用av_seek...
- 3.3 在IDecoder类里面加入flush函数,实际上就是seek的时候把队列里的AVPacket清除掉
- 3.4 通过av_gettime()函数作为外部参考时钟,实现音视频同步,需要hurryup和实现drop frame的处理。
下面是调整后播放flv(h264+aac)格式(分辨率为320*240,25帧/S)的结果,软解播放达到平均22帧/s

其它wmv,ts流,avi等格式均可播放,但是音质效果需要改进。
最近的进展情况:
1. 实现ffmpeg播放的回调接口,可以通知界面刷新状态,如去掉缓冲提示等。
public interface IFFMpegPlayer {
public void onPlay();
public void onStop();
public void onRelease();
public void onError(String msg, Exception e);
}
2. 定制话播放控制栏界面,效果如下:

替换ffmpeg自带的控制栏界面
如何支持http网络播放,需要config.h里enable相关选项,我的配置参考如下:
FFMpeg自带的播放器ffplay对音视频的处理方法总结:
1. 如果声音是CBR的(也就是固定码率),就以音频的时间戳为基准
2. 如果视频时CBR,就以视频的时间戳为基准
3. 如果都是VBR的,就参考外部时钟,通过av_gettime()获取微妙集的时钟。
ffmpeg通过AVStream结构的time_base(有理数由分子和分母组成)可以获取一个参考时间单位,所有音视频流的timestamp都是基于这个时间单位顺序递增,比如time_base.num=1,time_base.den=90000,表示把1秒分成90000等份,音视频的pts和dts值就表示有多少个1/90000等份,更简单一点假设time_base.num=1,time_base.den=1000,就表示1秒分成1000等份,相当于1毫秒,那时间戳就表示是以毫秒为单位的,再做音视频处理时候,如果解码的速度比按照时间戳显示的速度快,那就简单不用丟帧(Drop Frame)处理,当解码速度很慢时(比如手机设备),就需要丢帧处理,是每两帧丟一帧数据,还是每3帧丟掉一帧数据,就需要根据延时显示程度来计算丢帧的比率
havlenapetr最新的ffmpeg可以直接编译通过,不过有个bug,就是播放完毕后,video_decoder线程不能退出,主要原因是阻塞在packet队列的Queue->get函数里,这里处理逻辑需要考虑两种情况:
1. AVPacket队列里没有音视频帧数据时,解码线程挂起进入等待状态,有新的AVPacket加入队列时激活解码线程
2. 如果文件读取到末尾,Queue->get直接返回-1,解码线程发现队列空时直接退出
转帖:http://blog.csdn.net/moruite/article/details/6305944