1.下载 FFmpeg 源码

git clone https://git.ffmpeg.org/ffmpeg.git

这一步可能会花比较长的时间

2.编译 FFmpeg for Android

2.1.修改 FFmpeg 的 configure

由于FFMPEG默认编译出来的动态库文件名的版本号在.so之后(例如“libavcodec.so.5.100.1”),但是android平台不能识别这样文件名,所以我们需要修改FFMPEG生成的动态库的文件名。

打开 configure 文件,找到:

SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'
SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR)$(SLIBNAME)'

修改为

SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'

2.2.编写 Android 编译脚本

#!/bin/sh
NDK=/home/cent/Android/Sdk/ndk-bundle
SYSROOT=$NDK/platforms/android-/arch-arm
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
build_android()
{
./configure \
--prefix=$PREFIX \
--enable-shared \
--disable-static \
--disable-doc \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-avdevice \
--disable-doc \
--disable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=linux \
--arch=arm \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make clean
make
make install
}
CPU=arm
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"
build_android

2.3.编译

执行上面的脚本编译出我们需要的动态库

./build_android.sh

进入android/$CPU目录可以看到生成的动态库和我们需要的头文件

.
└── arm
├── include
│   ├── libavcodec
│   ├── libavfilter
│   ├── libavformat
│   ├── libavutil
│   ├── libswresample
│   └── libswscale
└── lib
├── libavcodec-.so
├── libavcodec.so -> libavcodec-.so
├── libavfilter-.so
├── libavfilter.so -> libavfilter-.so
├── libavformat-.so
├── libavformat.so -> libavformat-.so
├── libavutil-.so
├── libavutil.so -> libavutil-.so
├── libswresample-.so
├── libswresample.so -> libswresample-.so
├── libswscale-.so
├── libswscale.so -> libswscale-.so
└── pkgconfig

3.将上一步生成的头文件和库文件导入到Android Studio工程中

首先新建一个工程,并且勾选 Include C++ Support 即可得到一个基于CMake的模板工程。目录结构如下所示

.
├── app
│   ├── app.iml
│   ├── build
│   │   ├── generated
│   │   │   ├── res
│   │   │   └── source
│   │   ├── intermediates
│   │   │   ├── blame
│   │   │   ├── incremental
│   │   │   ├── manifest
│   │   │   ├── manifests
│   │   │   ├── res
│   │   │   ├── rs
│   │   │   └── symbols
│   │   └── outputs
│   │   └── logs
│   ├── build.gradle
│   ├── CMakeLists.txt
│   ├── CMakeLists.txt~
│   ├── libs
│   │   ├── armeabi
│   │   │   ├── libavcodec-.so
│   │   │   ├── libavfilter-.so
│   │   │   ├── libavformat-.so
│   │   │   ├── libavutil-.so
│   │   │   ├── libswresample-.so
│   │   │   └── libswscale-.so
│   │   └── include
│   │   ├── libavcodec
│   │   ├── libavfilter
│   │   ├── libavformat
│   │   ├── libavutil
│   │   ├── libswresample
│   │   └── libswscale
│   ├── proguard-rules.pro
│   └── src
│   ├── androidTest
│   │   └── java
│   ├── main
│   │   ├── AndroidManifest.xml
│   │   ├── cpp
│   │   ├── java
│   │   └── res
│   └── test
│   └── java
├── build
│   ├── android-profile
│   │   └── profile-------.rawproto
│   └── generated
│   └── mockable-android-.jar
├── build.gradle
├── FFMPEGTest.iml
├── gradle
│   └── wrapper
│   ├── gradle-wrapper.jar
│   └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.properties
└── settings.gradle

然后将上面编译FFMPEG生成的头文件和动态库拷贝到app/libs目录下,拷贝完后的目录结构如下所示

├── app
│ ├── libs
│ │ ├── armeabi
│ │ │ ├── libavcodec-.so
│ │ │ ├── libavfilter-.so
│ │ │ ├── libavformat-.so
│ │ │ ├── libavutil-.so
│ │ │ ├── libswresample-.so
│ │ │ └── libswscale-.so
│ │ └── include
│ │ ├── libavcodec
│ │ ├── libavfilter
│ │ ├── libavformat
│ │ ├── libavutil
│ │ ├── libswresample
│ │ └── libswscale
│ ├── proguard-rules.pro
│ └── src

这样还没完,我当时就是这样直接去编译,然后就踩了一个大坑,APP启动之后一直crash,原因就是没有找到我们在java文件里load的动态库。为什么呢?原因是在编译的时候,我们根本没有将我们的动态库打包到APP中,我们还需要修改app/build.gradle将我们放在libs目录下的动态库打包到APP中去

apply plugin: 'com.android.application'

android {
compileSdkVersion
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.example.cent.ffmpegtest"
minSdkVersion
targetSdkVersion
versionCode
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
externalNativeBuild {
cmake {
cppFlags "-frtti -fexceptions"
}
ndk{
abiFilters "armeabi"
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
} externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
} dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support.constraint:constraint-layout:1.0.1'
testCompile 'junit:junit:4.12'
}

紧接着我们还要指定abiFilters,因为AndroidStudio默认会编译所有架构的动态库,但是在本次例子中,我们实际上只拷贝了

├── libs
│ │ ├── armeabi

架构(目录名)的动态库,所以我们需要指定一个abiFilters来过滤一下,否则会出现编译错误。

 externalNativeBuild {
cmake {
cppFlags "-frtti -fexceptions"
}
ndk{
abiFilters "armeabi"
}
}
}

紧接着就是来编写我们的CMakeLists.txt文件来编译我们的动态库和native源文件了

cmake_minimum_required(VERSION 3.4.)

find_library( log-lib
log ) set(distribution_DIR ../../../../libs) add_library( native-lib
SHARED
src/main/cpp/native-lib.cpp ) add_library( avcodec-
SHARED
IMPORTED)
set_target_properties( avcodec-
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/armeabi/libavcodec-.so) add_library( avfilter-
SHARED
IMPORTED)
set_target_properties( avfilter-
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/armeabi/libavfilter-.so) add_library( avformat-
SHARED
IMPORTED)
set_target_properties( avformat-
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/armeabi/libavformat-.so) add_library( avutil-
SHARED
IMPORTED)
set_target_properties( avutil-
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/armeabi/libavutil-.so) add_library( swresample-
SHARED
IMPORTED)
set_target_properties( swresample-
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/armeabi/libswresample-.so) add_library( swscale-
SHARED
IMPORTED)
set_target_properties( swscale-
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/armeabi/libswscale-.so) include_directories(libs/include) target_link_libraries( native-lib
avcodec-
avfilter-
avformat-
avutil-
swresample-
swscale-
${log-lib} )

这样基本上就大功告成了。

4.使用FFMPEG

下面我们将通过一个小例子来看一下怎样使用FFMPEG。使用FFMPEG进行视频解码(音频和视频很相似)的一般流程如下图所示

首先需要在JAVA文件中加载我们需要的动态库

//MainActivity.java
public class MainActivity extends Activity { // Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
System.loadLibrary("avcodec-57");
System.loadLibrary("avfilter-6");
System.loadLibrary("avformat-57");
System.loadLibrary("avutil-55");
System.loadLibrary("swresample-2");
System.loadLibrary("swscale-4");
} @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI()); String input = new File(Environment.getExternalStorageDirectory(),"input.mp4").getAbsolutePath();
String output = new File(Environment.getExternalStorageDirectory(),"output_yuv420p.yuv").getAbsolutePath();
decode(input, output);
} /**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native static void decode(String input,String output);
}

然后在native代码中实现主要逻辑

//native-lib.cpp
#include <jni.h>
#include <string>
#include <android/log.h> extern "C" {
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//像素处理
#include "libswscale/swscale.h"
} #define FFLOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"ffmpeg",FORMAT,##__VA_ARGS__);
#define FFLOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"ffmpeg",FORMAT,##__VA_ARGS__); extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_cent_ffmpegtest_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
} extern "C"
JNIEXPORT void JNICALL
Java_com_example_cent_ffmpegtest_MainActivity_decode(JNIEnv *env, jclass type, jstring input_,
jstring output_) {
//获取输入输出文件名
const char *input = env->GetStringUTFChars(input_, );
const char *output = env->GetStringUTFChars(output_, ); //1.注册所有组件
av_register_all(); //封装格式上下文,统领全局的结构体,保存了视频文件封装格式的相关信息
AVFormatContext *pFormatCtx = avformat_alloc_context(); //2.打开输入视频文件
if (avformat_open_input(&pFormatCtx, input, NULL, NULL) != )
{
FFLOGE("%s","无法打开输入视频文件");
return;
} //3.获取视频文件信息
if (avformat_find_stream_info(pFormatCtx,NULL) < )
{
FFLOGE("%s","无法获取视频文件信息");
return;
} //获取视频流的索引位置
//遍历所有类型的流(音频流、视频流、字幕流),找到视频流
int v_stream_idx = -;
int i = ;
//number of streams
for (; i < pFormatCtx->nb_streams; i++)
{
//流的类型
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
v_stream_idx = i;
break;
}
} if (v_stream_idx == -)
{
FFLOGE("%s","找不到视频流\n");
return;
} //只有知道视频的编码方式,才能够根据编码方式去找到解码器
//获取视频流中的编解码上下文
AVCodecContext *pCodecCtx = pFormatCtx->streams[v_stream_idx]->codec;
//4.根据编解码上下文中的编码id查找对应的解码
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL)
{
FFLOGE("%s","找不到解码器\n");
return;
} //5.打开解码器
if (avcodec_open2(pCodecCtx,pCodec,NULL)<)
{
FFLOGE("%s","解码器无法打开\n");
return;
} //输出视频信息
FFLOGI("视频的文件格式:%s",pFormatCtx->iformat->name);
FFLOGI("视频时长:%d", (pFormatCtx->duration)/);
FFLOGI("视频的宽高:%d,%d",pCodecCtx->width,pCodecCtx->height);
FFLOGI("解码器的名称:%s",pCodec->name); //准备读取
//AVPacket用于存储一帧一帧的压缩数据(H264)
//缓冲区,开辟空间
AVPacket *packet = (AVPacket*)av_malloc(sizeof(AVPacket)); //AVFrame用于存储解码后的像素数据(YUV)
//内存分配
AVFrame *pFrame = av_frame_alloc();
//YUV420
AVFrame *pFrameYUV = av_frame_alloc();
//只有指定了AVFrame的像素格式、画面大小才能真正分配内存
//缓冲区分配内存
uint8_t *out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
//初始化缓冲区
avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height); //用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等
struct SwsContext *sws_ctx = sws_getContext(pCodecCtx->width,pCodecCtx->height,pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P,
SWS_BICUBIC, NULL, NULL, NULL);
int got_picture, ret; FILE *fp_yuv = fopen(output, "wb+"); int frame_count = ; //6.一帧一帧的读取压缩数据
while (av_read_frame(pFormatCtx, packet) >= )
{
//只要视频压缩数据(根据流的索引位置判断)
if (packet->stream_index == v_stream_idx)
{
//7.解码一帧视频压缩数据,得到视频像素数据
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if (ret < )
{
FFLOGE("%s","解码错误");
return;
} //为0说明解码完成,非0正在解码
if (got_picture)
{
//AVFrame转为像素格式YUV420,宽高
//2 6输入、输出数据
//3 7输入、输出画面一行的数据的大小 AVFrame 转换是一行一行转换的
//4 输入数据第一列要转码的位置 从0开始
//5 输入画面的高度
sws_scale(sws_ctx, pFrame->data, pFrame->linesize, , pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize); //输出到YUV文件
//AVFrame像素帧写入文件
//data解码后的图像像素数据(音频采样数据)
//Y 亮度 UV 色度(压缩了) 人对亮度更加敏感
//U V 个数是Y的1/4
int y_size = pCodecCtx->width * pCodecCtx->height;
fwrite(pFrameYUV->data[], , y_size, fp_yuv);
fwrite(pFrameYUV->data[], , y_size / , fp_yuv);
fwrite(pFrameYUV->data[], , y_size / , fp_yuv); frame_count++;
FFLOGI("解码第%d帧",frame_count);
}
} //释放资源
av_free_packet(packet);
} fclose(fp_yuv); av_frame_free(&pFrame); avcodec_close(pCodecCtx); avformat_free_context(pFormatCtx); env->ReleaseStringUTFChars(input_, input);
env->ReleaseStringUTFChars(output_, output);
}

记得在Manifest文件中添加需要的权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

最后简单了解一下FFMPEG中使用的几个主要数据结构的作用

AndroidStudio 中使用FFMPEG的更多相关文章

  1. PHP基础知识之————PHP Web脚本中使用FFmpeg

    简介 本文将尝试指出在PHP Web脚本中使用FFmpeg时需要了解的所有重要事项.它还将显示一些使用示例,以使事情更清楚.这个想法也可以应用到其他web脚本语言. 从PHP脚本调用命令行工具 选择一 ...

  2. AndroidStudio中activity实现去掉标题栏

    1.在代码中实现 this.requestWindowFeature(Window.FEATURE_NO_TITLE) 这段代码需要放在setContentView()前面 2.设置在Manifest ...

  3. androidStudio中如何加载字体资源?

    在android中字体的格式总是不能尽善尽美的显示出来 ,  于是要求我们使用一些有美感的字体,加载的方式(就像HTML的字体一样),我们需要通过加载字体的方式来使用android中不曾提供的字体; ...

  4. AndroidStudio中 R文件缺失的办法

    AndroidStudio中 R文件缺失 找不到R文件的原因有如下两类: 1:IDE或代码问题,非个人原因: 2:个人误操作导致IDE不予提示R文件: 下面是解决办法: 第一种 ①首先确保资源文件是否 ...

  5. Windows下AndroidStudio 中使用Git(AndroidStudio项目于GitHub关联)

    前提条件 : 1. 安装 Git 客户端 下载链接 2. 有 GitHub 账号 (假设你已经有了一些git基础, 如果还一点都不会, 请去找其他加成学习) AndroidStudio项目发布到Git ...

  6. androidStudio 中 gradle 常用功能

    1. gradle 使用 svn 当前版本信息. def getSvnRevision() { new ByteArrayOutputStream().withStream { os -> de ...

  7. AndroidStudio中各种常见快捷键记录

    AndroidStudio中各种常用操作快捷键记录 简单方法 直接设置AS的快捷键与eclipse相同,方便直接从eclipse切到AS的人. 常用的AS的默认快捷键 ctrl + N 根据类名查找J ...

  8. AndroidStudio中导入SlidingMenu报错解决方案

    ----------------------------------------------------------------------------------------------[版权申明: ...

  9. Java Web 中使用ffmpeg实现视频转码、视频截图

    Java Web 中使用ffmpeg实现视频转码.视频截图 转载自:[ http://www.cnblogs.com/dennisit/archive/2013/02/16/2913287.html  ...

随机推荐

  1. jmeter ant 运行 提示Error occurred during initialization of VM

    运行ant提示错误 网上找到的方法 将set HEAP= -Xms512m -Xmx1024m 改成set HEAP= -Xms512m -Xmx512m 保存后运行成功

  2. CSS3中与文字相关的样式

    1.给文字添加阴影:text-shadow属性(特别指出IE浏览器要IE10+的版本才支持)    语法如下: text-shadow:length length length color; 其中,第 ...

  3. poj2114树分治

    题意:给你一棵树,每条边有权值,求有没有一条链使得权值和为k 题解:和上一题类似,依旧是树分治,只是我们储存结果的时候是判断加起来为k的点对数,刚开始本来想用map存答案,结果就t了,后来用了vect ...

  4. 51nod 1272 思维/线段树

    http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1272 1272 最大距离 题目来源: Codility 基准时间限制:1 ...

  5. IIFE 立即执行的函数表达式

    介绍IIFE IIFE的性能 使用IIFE的好处 IIFE最佳实践 jQuery优化 在Bootstrap源码(具体请看<Bootstrap源码解析>)和其他jQuery插件经常看到如下的 ...

  6. 按返回键退出程序但不销毁代码,像QQ一样,后台运行

    @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BA ...

  7. 条款35:考虑virtual函数以外的其他选择

    有一部分人总是主张virtual函数几乎总应该是private:例如下面这个例子,例子时候游戏,游戏里面的任务都拥有健康值这一属性: class GameCharacter{ public: int ...

  8. 面试题12:打印1到最大的n位数

    题目:输入数字n,按顺序打印出从1最大的n位十进制数.比如输入3,则打印出1.2.3一直到最大的3位数即999. 考点:大数问题. 解决方案:在字符串上模拟数字加法. <剑指Offer>上 ...

  9. yii2.0缓存的使用

    1.片段缓存(针对于视图中的某部分进行缓存): <?php 设置有效时间 $time=15; 缓存依赖,存入文件.当文件内容发生改变是才会刷新新内容 $dependecy=[ 'class'=& ...

  10. UVA - 1471 Defense Lines (set/bit/lis)

    紫薯例题+1. 题意:给你一个长度为n(n<=200000)的序列a[n],求删除一个连续子序列后的可能的最长连续上升子序列的长度. 首先对序列进行分段,每一段连续的子序列的元素递增,设L[i] ...