1. Android 2.3用ffmpeg替代stagefright自带的swdecoders && 2. Android2.3为FFMPEG编写Extractor
2014-10-23 17:34
801 查看
http://blog.csdn.net/woker/article/details/7631834
FFMPEG源文件放在$TOP/external/ffmpeg中, 编译成几个静态库libavcodec/libavformat/libavutil/...,以后再改成动态库。照网上的,写几个Android.mk和一个av.mk,可以 搞定。写了一个脚本调用configure, 在脚本中做一些配置,把需要的parser, decoder, encoder, demuxer等放进去,其它的disable掉。
写个ffdecoder, 在stagefright中调用ffmpeg.
C code:
1. 仿照AVCDecoder在libstagefright/codecs中建立相关目录和文件:ffdecoder.cpp, ffdecoder.h, ffdecoder_api.cpp, ffdecoder_api.h
2. 仿照AVCDecoder实现ffdecoder的函数:ffdecoder(), ~ffdecoder(), start(), stop(), getFormat(), read(), releaseFrames(), signalBufferReturned(). start()中调用ffmpeg_init(); stop()中调用ffmpeg_deinit(); read()中调用ffmpeg_decode(bufptr, size, other output data), 若framefinished,
传出yuv的MediaBuffer.
3. ffdecoder_api.cpp中 #include "ffmpeg headers" 时,需要加extern "C" {}.
3.1) ffmpeg_init(): av_register_all(); 需要根据传入的mime设置ffmpeg中对应的CodecID, width, height, extradata, extradata_size都从参数传入. pCodecCtx中需要配置的参数有codec_id, time_base, coded_width, coded_height, codec_type, thread_count, opaque, flags2, extradata, extradata_size;
avcodec_find_decoder(); avcodec_open(); avcodec_alloc_frame().
3.2) ffmpeg_deinit(): av_free_packet(); avcodec_close(); av_free(pFrame).
3.3) ffmpeg_decode(): 根据传入参数来设置packet的data和size参数; avcodec_decode_video2(); if(framefinished){传出yuv数据地址和linesize}
4. libstagefright/OMXCodec.cpp中AVCDecoder都改成ffdecoder. kDecoderInfo[] 中可以更改不同格式
Makefile:
1. libstagefright/codecs/ffmpeg中的Android.mk中的 LOCAL_SRC_FILES, LOCAL_MODULE, LOCAL_MODULE_TAGS, LOCAL_C_INCLUDES需要设置, 最后BUILD_STATIC_LIBRARY.
2. libstagefright/Android.mk中加
[cpp] view
plaincopy
LOCAL_STATIC_LIBRARIES += \
libstagefright_ffmpeg
LOCAL_STATIC_LIBRARIES += \
libavformat \
libavcodec \
libavdevice \
libavfilter \
libavutil
prelink-map:
ffmpeg放入stagefright后太大了, 要改一下prelink, 把OpenCore和PV相关的库关掉,把stagefright放到一个大的空间中。
http://blog.csdn.net/woker/article/details/7659290
Android能解析的文件格式太少,自己写一个Extractor来实现其它格式( .avi .mov .rmvb .rm .flv ...)的解析。
大体说一下ffmpeg内部的数据解析。ffmpeg内部对外部数据(文件,网络流等)的操作是通过选择合适的protocol来操作的,例如数据对象是文件,它会选择ff_file_protocol来操作。所有的protocol都是在av_register_all里面注册的(也可以在其它地方用ffurl_register_protocol()来注册,本文实现的ff_android_protocol就是在外部注册的)。每个protocol提供了对数据流的操作方法:
[cpp] view
plaincopy
<span style="font-size:16px;">typedef struct URLProtocol {
const char *name;
int (*url_open)(URLContext *h, const char *url, int flags);
int (*url_read)(URLContext *h, unsigned char *buf, int size);
int (*url_write)(URLContext *h, const unsigned char *buf, int size);
int64_t (*url_seek)(URLContext *h, int64_t pos, int whence);
int (*url_close)(URLContext *h);
struct URLProtocol *next;
int (*url_read_pause)(URLContext *h, int pause);
int64_t (*url_read_seek)(URLContext *h, int stream_index,
int64_t timestamp, int flags);
int (*url_get_file_handle)(URLContext *h);
int priv_data_size;
const AVClass *priv_data_class;
int flags;
int (*url_check)(URLContext *h, int mask);
} URLProtocol;
</span>
需要哪些功能就实现哪些接口。实际上,ffmpeg是根据数据名字来确定所选择的protocol, 如果名字是file:*,它就会用ff_file_protocol, 如果是http:*,它就会调用ff_http_protocol, 当然如果是android:*,它也会去找ff_android_protocol。以上就是ffmpeg对外部数据的访问。ffmpeg采集到原始数据后,会经过一系列操作,最后为解码器提供一个很方便的接口av_read_frame()。每次调用av_read_frame()都会返回一个packet,packet内封装了一个frame所需的原始数据。但是每次返回的packet内的数据不确定是哪个stream的,必须根据packet内的stream_index来确定。ffmpeg里面好像没有提供某个函数直接访问指定的stream(或者我没找到?)。
下面说说Stagefright和ffmpeg的交互. Stagefright为所有的Extractor提供的原始数据,都被封装成DataSource,DataSource提供了一些基本的功能。
[cpp] view
plaincopy
<span style="font-size:16px;"> virtual ssize_t readAt(off_t offset, void *data, size_t size) = 0;
virtual status_t getSize(off_t *size);
</span>
另外Stagefright内部是将每个stream封装成一个MediaSource,每个MediaSource相对独立地访问数据,数据被封装成MediaBuffer。
通过以上的了解,可以确定有两部分工作:
1. 为ffmepg写一个protocol,用来实现对Stagefright提供的DataSource的访问;
2. 写一个packet管理的类,确保MediaSource访问自己的packet,并将packet封装成MediaBuffer。
定义的类有:
FFMPEGExtractor, 接口类, 除了几个继承来的虚函数是public的, 其它成员都是private.
FFMPEGPktManager, 负责获取并管理从ffmpeg获得的packet.
FFMPEGSource, 负责指定stream的操作。
protocol:
[cpp] view
plaincopy
<span style="font-size:16px;">URLProtocol ff_android_protocol = {
"android",
android_open, // 不用干啥事,Stagefright都给我们准备好了,把new出来的AVAndroidStream(保存数据的结构体)存入URLContext.priv_data中。
android_read, // 调用DataSource的reatAt()。Offset要保存在AVAndroidStream中。
android_write, // 不支持。报错。
android_seek, // 改Offset。
android_close, // 释放资源
NULL,
NULL,
NULL,
android_get_handle, // 返回URLContext.priv_data
0,
NULL,
0,
NULL
};</span>
下面具体描述一下各个类。
1). FFMPEGPktManager.
[cpp] view
plaincopy
<span style="font-size:16px;">struct FFMPEGPktManager {
FFMPEGPktManager(AVFormatContext *pFormatCtx); // 根据pFormatCtx->nb_streams创建Vector,用以缓存不同stream的packet。
MediaBuffer* readPacket(unsigned int streamIndex); // 将stream[i]缓存的第一个packet封装并返回,若不存在则反复调用av_read_frame。
void freeStreamPackets(unsigned int streamIndex); // 释放stream[i]的全部缓存packet。
AVPacket* getPacketInfo(uint32_t streamIndex, uint32_t numOffset); // 返回stream[i]的第n个packet。
protected:
~FFMPEGPktManager();
private:
Mutex mLock; // 由于有多个stream同时访问,需要加锁保护
AVFormatContext *mFC;
Vector< Vector<AVPacket> > mPacketArray; // 保存缓冲packet
int retrievePacket(unsigned int streamIndex, int cnt); // 反复调用av_read_frame直到获取n个stream[i]的packet。
MediaBuffer *createMediaBuffer(AVPacket* pkt); // 根据AVPacket封装成MediaBuffer。注意buffer->meta_data()->set kKeyTime kKeyIsSyncFrame
FFMPEGPktManager(const FFMPEGPktManager &);
FFMPEGPktManager &operator=(const FFMPEGPktManager &);
};
</span>
2). FFMPEGSource
[cpp] view
plaincopy
<span style="font-size:16px;">struct FFMPEGSource : public MediaSource {
FFMPEGSource(
const sp<FFMPEGExtractor> &extractor,
FFMPEGPktManager * pktManager, // 不同的FFMPEGSource需要使用同一个pktManager。
sp<MetaData> trackMeta, size_t streamIndex);
virtual status_t start(MetaData *params); // do nothing
virtual status_t stop(); // do nothing
virtual sp<MetaData> getFormat(); // return mTrackMeta
virtual status_t read( // 从mPktManager里获取一个packet的MediaBuffer
MediaBuffer **buffer, const ReadOptions *options);
protected:
~FFMPEGSource();
private:
sp<FFMPEGExtractor> mExtractor;
FFMPEGPktManager * mPktManager;
size_t mStreamIndex;
sp<MetaData> mTrackMeta;
bool mIsAVC;
FFMPEGSource(const FFMPEGSource &);
FFMPEGSource &operator=(const FFMPEGSource &);
};
</span>
3). FFMPEGExtractor
[cpp] view
plaincopy
<span style="font-size:16px;">class FFMPEGExtractor : public MediaExtractor {
public:
// Extractor assumes ownership of "source".
FFMPEGExtractor(const sp<DataSource> &source); // 初始化ffmpeg,av_find_stream_info获取信息。解析每个stream,获取其metadata。创建FFMPEGPktManager。
virtual size_t countTracks(); // mTracks.size()
virtual sp<MediaSource> getTrack(size_t index); // new FFMPEGSource
virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags); // 创建缩略图并返回track相应的metadata
virtual sp<MetaData> getMetaData(); // kKeyMIMEType 指定MEDIA_MIMETYPE_CONTAINER_FFMPEG
protected:
virtual ~FFMPEGExtractor();
private:
sp<DataSource> mDataSource;
FFMPEGPktManager *mPktManager;
AVFormatContext *mFormatCtx;
sp<MetaData> mIndexMap;
int32_t trackIndex2StreamIndex(int32_t trackIndex); // trackIndex和mFormatContex里面保存的stream序号可能不同,需要转换
struct TrackInfo {
unsigned long mTrackNum;
sp<MetaData> mMeta;
};
Vector<TrackInfo> mTracks; // 每条track的信息
bool mExtractedThumbnails;
void findThumbnails();
FFMPEGExtractor(const FFMPEGExtractor &);
FFMPEGExtractor &operator=(const FFMPEGExtractor &);
};
</span>
接下来需要是FFMPEGExtractor生效。Stagefright是通过DataSource::sniff() 来获取所需的Extractor的。它遍历所有注册了的Sniff,获得得分(confidence)最高的一个。所以我们自己写一个SniffFFMPEG,并将confidence设成1.0.
[cpp] view
plaincopy
<span style="font-size:16px;">bool SniffFFMPEG(
const sp<DataSource> &source, String8 *mimeType, float *confidence,
sp<AMessage> *) {
LOGD("SniffFFMPEG: confidence force to be 1.0");
// QGC: ffmpeg force to be high confidence
*mimeType = MEDIA_MIMETYPE_CONTAINER_FFMPEG;
*confidence = 1.0f;
return true;
}
</span>
并在DataSource::RegisterDefaultSniffers()中注册:
[cpp] view
plaincopy
<span style="font-size:16px;"> RegisterSniffer(SniffFFMPEG);</span>
在MediaDefs.h/cpp中要为ffmpeg添加自己的mime:
[cpp] view
plaincopy
<span style="font-size:16px;">const char *MEDIA_MIMETYPE_CONTAINER_FFMPEG = "video/ffmpeg";</span>
在MediaExtractor.cpp中将MEDIA_MIMETYPE_CONTAINER_FFMPEG绑定到FFMPEGExtractor上:
[cpp] view
plaincopy
<span style="font-size:16px;"> if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_FFMPEG))
return new FFMPEGExtractor(source);
</span>
到这里,FFMPEGExtractor就完成了。如果想要播放.avi .mov 等文件,还需要改一下MediaScanner方面的东西,后面再说。
FFMPEG源文件放在$TOP/external/ffmpeg中, 编译成几个静态库libavcodec/libavformat/libavutil/...,以后再改成动态库。照网上的,写几个Android.mk和一个av.mk,可以 搞定。写了一个脚本调用configure, 在脚本中做一些配置,把需要的parser, decoder, encoder, demuxer等放进去,其它的disable掉。
写个ffdecoder, 在stagefright中调用ffmpeg.
C code:
1. 仿照AVCDecoder在libstagefright/codecs中建立相关目录和文件:ffdecoder.cpp, ffdecoder.h, ffdecoder_api.cpp, ffdecoder_api.h
2. 仿照AVCDecoder实现ffdecoder的函数:ffdecoder(), ~ffdecoder(), start(), stop(), getFormat(), read(), releaseFrames(), signalBufferReturned(). start()中调用ffmpeg_init(); stop()中调用ffmpeg_deinit(); read()中调用ffmpeg_decode(bufptr, size, other output data), 若framefinished,
传出yuv的MediaBuffer.
3. ffdecoder_api.cpp中 #include "ffmpeg headers" 时,需要加extern "C" {}.
3.1) ffmpeg_init(): av_register_all(); 需要根据传入的mime设置ffmpeg中对应的CodecID, width, height, extradata, extradata_size都从参数传入. pCodecCtx中需要配置的参数有codec_id, time_base, coded_width, coded_height, codec_type, thread_count, opaque, flags2, extradata, extradata_size;
avcodec_find_decoder(); avcodec_open(); avcodec_alloc_frame().
3.2) ffmpeg_deinit(): av_free_packet(); avcodec_close(); av_free(pFrame).
3.3) ffmpeg_decode(): 根据传入参数来设置packet的data和size参数; avcodec_decode_video2(); if(framefinished){传出yuv数据地址和linesize}
4. libstagefright/OMXCodec.cpp中AVCDecoder都改成ffdecoder. kDecoderInfo[] 中可以更改不同格式
Makefile:
1. libstagefright/codecs/ffmpeg中的Android.mk中的 LOCAL_SRC_FILES, LOCAL_MODULE, LOCAL_MODULE_TAGS, LOCAL_C_INCLUDES需要设置, 最后BUILD_STATIC_LIBRARY.
2. libstagefright/Android.mk中加
[cpp] view
plaincopy
LOCAL_STATIC_LIBRARIES += \
libstagefright_ffmpeg
LOCAL_STATIC_LIBRARIES += \
libavformat \
libavcodec \
libavdevice \
libavfilter \
libavutil
prelink-map:
ffmpeg放入stagefright后太大了, 要改一下prelink, 把OpenCore和PV相关的库关掉,把stagefright放到一个大的空间中。
http://blog.csdn.net/woker/article/details/7659290
Android能解析的文件格式太少,自己写一个Extractor来实现其它格式( .avi .mov .rmvb .rm .flv ...)的解析。
大体说一下ffmpeg内部的数据解析。ffmpeg内部对外部数据(文件,网络流等)的操作是通过选择合适的protocol来操作的,例如数据对象是文件,它会选择ff_file_protocol来操作。所有的protocol都是在av_register_all里面注册的(也可以在其它地方用ffurl_register_protocol()来注册,本文实现的ff_android_protocol就是在外部注册的)。每个protocol提供了对数据流的操作方法:
[cpp] view
plaincopy
<span style="font-size:16px;">typedef struct URLProtocol {
const char *name;
int (*url_open)(URLContext *h, const char *url, int flags);
int (*url_read)(URLContext *h, unsigned char *buf, int size);
int (*url_write)(URLContext *h, const unsigned char *buf, int size);
int64_t (*url_seek)(URLContext *h, int64_t pos, int whence);
int (*url_close)(URLContext *h);
struct URLProtocol *next;
int (*url_read_pause)(URLContext *h, int pause);
int64_t (*url_read_seek)(URLContext *h, int stream_index,
int64_t timestamp, int flags);
int (*url_get_file_handle)(URLContext *h);
int priv_data_size;
const AVClass *priv_data_class;
int flags;
int (*url_check)(URLContext *h, int mask);
} URLProtocol;
</span>
需要哪些功能就实现哪些接口。实际上,ffmpeg是根据数据名字来确定所选择的protocol, 如果名字是file:*,它就会用ff_file_protocol, 如果是http:*,它就会调用ff_http_protocol, 当然如果是android:*,它也会去找ff_android_protocol。以上就是ffmpeg对外部数据的访问。ffmpeg采集到原始数据后,会经过一系列操作,最后为解码器提供一个很方便的接口av_read_frame()。每次调用av_read_frame()都会返回一个packet,packet内封装了一个frame所需的原始数据。但是每次返回的packet内的数据不确定是哪个stream的,必须根据packet内的stream_index来确定。ffmpeg里面好像没有提供某个函数直接访问指定的stream(或者我没找到?)。
下面说说Stagefright和ffmpeg的交互. Stagefright为所有的Extractor提供的原始数据,都被封装成DataSource,DataSource提供了一些基本的功能。
[cpp] view
plaincopy
<span style="font-size:16px;"> virtual ssize_t readAt(off_t offset, void *data, size_t size) = 0;
virtual status_t getSize(off_t *size);
</span>
另外Stagefright内部是将每个stream封装成一个MediaSource,每个MediaSource相对独立地访问数据,数据被封装成MediaBuffer。
通过以上的了解,可以确定有两部分工作:
1. 为ffmepg写一个protocol,用来实现对Stagefright提供的DataSource的访问;
2. 写一个packet管理的类,确保MediaSource访问自己的packet,并将packet封装成MediaBuffer。
定义的类有:
FFMPEGExtractor, 接口类, 除了几个继承来的虚函数是public的, 其它成员都是private.
FFMPEGPktManager, 负责获取并管理从ffmpeg获得的packet.
FFMPEGSource, 负责指定stream的操作。
protocol:
[cpp] view
plaincopy
<span style="font-size:16px;">URLProtocol ff_android_protocol = {
"android",
android_open, // 不用干啥事,Stagefright都给我们准备好了,把new出来的AVAndroidStream(保存数据的结构体)存入URLContext.priv_data中。
android_read, // 调用DataSource的reatAt()。Offset要保存在AVAndroidStream中。
android_write, // 不支持。报错。
android_seek, // 改Offset。
android_close, // 释放资源
NULL,
NULL,
NULL,
android_get_handle, // 返回URLContext.priv_data
0,
NULL,
0,
NULL
};</span>
下面具体描述一下各个类。
1). FFMPEGPktManager.
[cpp] view
plaincopy
<span style="font-size:16px;">struct FFMPEGPktManager {
FFMPEGPktManager(AVFormatContext *pFormatCtx); // 根据pFormatCtx->nb_streams创建Vector,用以缓存不同stream的packet。
MediaBuffer* readPacket(unsigned int streamIndex); // 将stream[i]缓存的第一个packet封装并返回,若不存在则反复调用av_read_frame。
void freeStreamPackets(unsigned int streamIndex); // 释放stream[i]的全部缓存packet。
AVPacket* getPacketInfo(uint32_t streamIndex, uint32_t numOffset); // 返回stream[i]的第n个packet。
protected:
~FFMPEGPktManager();
private:
Mutex mLock; // 由于有多个stream同时访问,需要加锁保护
AVFormatContext *mFC;
Vector< Vector<AVPacket> > mPacketArray; // 保存缓冲packet
int retrievePacket(unsigned int streamIndex, int cnt); // 反复调用av_read_frame直到获取n个stream[i]的packet。
MediaBuffer *createMediaBuffer(AVPacket* pkt); // 根据AVPacket封装成MediaBuffer。注意buffer->meta_data()->set kKeyTime kKeyIsSyncFrame
FFMPEGPktManager(const FFMPEGPktManager &);
FFMPEGPktManager &operator=(const FFMPEGPktManager &);
};
</span>
2). FFMPEGSource
[cpp] view
plaincopy
<span style="font-size:16px;">struct FFMPEGSource : public MediaSource {
FFMPEGSource(
const sp<FFMPEGExtractor> &extractor,
FFMPEGPktManager * pktManager, // 不同的FFMPEGSource需要使用同一个pktManager。
sp<MetaData> trackMeta, size_t streamIndex);
virtual status_t start(MetaData *params); // do nothing
virtual status_t stop(); // do nothing
virtual sp<MetaData> getFormat(); // return mTrackMeta
virtual status_t read( // 从mPktManager里获取一个packet的MediaBuffer
MediaBuffer **buffer, const ReadOptions *options);
protected:
~FFMPEGSource();
private:
sp<FFMPEGExtractor> mExtractor;
FFMPEGPktManager * mPktManager;
size_t mStreamIndex;
sp<MetaData> mTrackMeta;
bool mIsAVC;
FFMPEGSource(const FFMPEGSource &);
FFMPEGSource &operator=(const FFMPEGSource &);
};
</span>
3). FFMPEGExtractor
[cpp] view
plaincopy
<span style="font-size:16px;">class FFMPEGExtractor : public MediaExtractor {
public:
// Extractor assumes ownership of "source".
FFMPEGExtractor(const sp<DataSource> &source); // 初始化ffmpeg,av_find_stream_info获取信息。解析每个stream,获取其metadata。创建FFMPEGPktManager。
virtual size_t countTracks(); // mTracks.size()
virtual sp<MediaSource> getTrack(size_t index); // new FFMPEGSource
virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags); // 创建缩略图并返回track相应的metadata
virtual sp<MetaData> getMetaData(); // kKeyMIMEType 指定MEDIA_MIMETYPE_CONTAINER_FFMPEG
protected:
virtual ~FFMPEGExtractor();
private:
sp<DataSource> mDataSource;
FFMPEGPktManager *mPktManager;
AVFormatContext *mFormatCtx;
sp<MetaData> mIndexMap;
int32_t trackIndex2StreamIndex(int32_t trackIndex); // trackIndex和mFormatContex里面保存的stream序号可能不同,需要转换
struct TrackInfo {
unsigned long mTrackNum;
sp<MetaData> mMeta;
};
Vector<TrackInfo> mTracks; // 每条track的信息
bool mExtractedThumbnails;
void findThumbnails();
FFMPEGExtractor(const FFMPEGExtractor &);
FFMPEGExtractor &operator=(const FFMPEGExtractor &);
};
</span>
接下来需要是FFMPEGExtractor生效。Stagefright是通过DataSource::sniff() 来获取所需的Extractor的。它遍历所有注册了的Sniff,获得得分(confidence)最高的一个。所以我们自己写一个SniffFFMPEG,并将confidence设成1.0.
[cpp] view
plaincopy
<span style="font-size:16px;">bool SniffFFMPEG(
const sp<DataSource> &source, String8 *mimeType, float *confidence,
sp<AMessage> *) {
LOGD("SniffFFMPEG: confidence force to be 1.0");
// QGC: ffmpeg force to be high confidence
*mimeType = MEDIA_MIMETYPE_CONTAINER_FFMPEG;
*confidence = 1.0f;
return true;
}
</span>
并在DataSource::RegisterDefaultSniffers()中注册:
[cpp] view
plaincopy
<span style="font-size:16px;"> RegisterSniffer(SniffFFMPEG);</span>
在MediaDefs.h/cpp中要为ffmpeg添加自己的mime:
[cpp] view
plaincopy
<span style="font-size:16px;">const char *MEDIA_MIMETYPE_CONTAINER_FFMPEG = "video/ffmpeg";</span>
在MediaExtractor.cpp中将MEDIA_MIMETYPE_CONTAINER_FFMPEG绑定到FFMPEGExtractor上:
[cpp] view
plaincopy
<span style="font-size:16px;"> if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_FFMPEG))
return new FFMPEGExtractor(source);
</span>
到这里,FFMPEGExtractor就完成了。如果想要播放.avi .mov 等文件,还需要改一下MediaScanner方面的东西,后面再说。
相关文章推荐
- 在Android 2.3中如何使用native_activity.h编写本地应用
- 【Android 2.3 & 2.3.3】 近场通信NFC API
- ffmpeg0.10移植到Android 2.3 (surface/OpenGL_ES)
- Android2.3 surface&&overlay
- 【Cocos2d-X(2.x) 游戏开发系列之二】cocos2dx最新2.0.1版本跨平台整合NDK+Xcode,Xcode编写&编译代码,Android导入打包运行即可!
- android风格和主题程序编写–style & theme
- Android_ICS_OMX_In_Stagefright------>2开始解码(软解)
- havlenapetr-FFMpeg移植到android2.3
- 【iOS-cocos2d-X 游戏开发之七】整合Cocos2dX的Android项目到Xcode项目中,Xcode编写&编译代码,Android导入打包运行即可!
- 在Android 2.3中如何使用native_activity.h编写本地应用
- 怎么修改android 2.3系统中自带的通讯录contacts
- Android设置全局变量&&启动系统自带应用程序
- Android 2.3 StageFright如何选定OMX组件的?
- havlenapetr-FFMpeg移植到android2.3
- Android 2.3 StageFright如何选定OMX组件的?
- android 2.3 emulator无法启动之version `GLIBC_2.11' not found
- Android 2.3 StageFright如何选定OMX组件的?
- Android 2.3 StageFright 如何选定OMX 组件的
- 【XDA汉化组编写】Android软件汉化/精简/去广告/优化教程 & FAQ
- 移植ffmpeg 到android 编写播放器(一)——序言