ffmpeg学习笔记-Linux下编译Android动态库
Android平台要使用ffmpeg就需要编译生成动态库,这里采用Ubuntu编译Android动态库
文件准备
要编译生成Android需要以下文件
- NDK
- ffmpeg源代码
NDK下载
NDK可以去Google下载,也可以在国内一些Android网站下载
这里推荐两个Android的下载网站
Android Studio 中文组
AndroidDevTools
ffmpeg
ffmpeg在其官网可以直接下载,不需要翻墙
官网下载地址
配置环境
我这里下载的是android-ndk-r10e-linux-x86_64.zip
和ffmpeg-2.6.9.tar.gz
NDK
解压
下载的NDK,Google下载的话是一个zip压缩包,其他地方下载可能是bin文件,其实都是压缩包
zip解压缩:unzip android-ndk-r10e-linux-x86_64.zip
bin解压:./android-ndk-r10e-linux-x86_64.bin
配置环境变量
vim ~/.bashrc
在文件末尾加上,NDKROOT为ndk所在路径
export NDKROOT=/usr/ndk/android-ndk-r10e
export PATH=$NDKROOT:$PATH
使配置的环境变量立即生效
source ~/.bashrc
使用ndk-build -v
检查设置是否生效
如果输出类似下列语句,则代表配置成功
GNU Make 3.81
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
ffmpeg
ffmpeg解压
tar -xzvf ffmpeg-2.6.9.tar.gz
编译ffmpeg
编写ffmpeg编译脚本,后缀名为.sh
,这里我命名为build_android.sh
#!/bin/bash
make clean
export NDK=/usr/ndk/android-ndk-r10e
export SYSROOT=$NDK/platforms/android-9/arch-arm/
export TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64
export CPU=arm
export PREFIX=$(pwd)/android/$CPU
export ADDI_CFLAGS="-marm"
./configure
--target-os=linux \
--prefix=$PREFIX \
--arch=arm \
--disable-doc \
--enable-shared \
--disable-static \
--disable-yasm \
--disable-symver \
--enable-gpl \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-doc \
--disable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make clean
make
make install
如果在linux端不识别,那么可以使用dos2unix
转换一下文件
注意,在编译脚本里不可有多余空格,否则会报一堆莫名其妙的错误
使用chmod 755 build_android.sh
更改文件权限,使其可以执行
此时便可以使用./build_android.sh
编译ffmpeg了
此时编译出来的动态库后缀名不对,那么就需要修改configure文件,使其生成的动态库符合标准
使用./configure --help
可以查看如何配置configure文件
修改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)'
此时再编译就可以得到合格的so动态库了
在编译途中会生成一些.h
和.mak
文件
编译完成生成android文件夹,生成的动态库和头文件都在这里
Android app测试(转码功能)
创建Android项目
建立jni文件夹,将include目录拷贝至jni目录下
拷贝so动态库
libavcodec-56.so
libavdevice-56.so
libavfilter-5.so
libavformat-56.so
libavutil-54.so
libpostproc-53.so
libswresample-1.so
libswscale-3.so
拷贝至jni目录编写
Android.mk
文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := avcodec
LOCAL_SRC_FILES := libavcodec-56.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := avdevice
LOCAL_SRC_FILES := libavdevice-56.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := avfilter
LOCAL_SRC_FILES := libavfilter-5.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := avformat
LOCAL_SRC_FILES := libavformat-56.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := avutil
LOCAL_SRC_FILES := libavutil-54.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := postproc
LOCAL_SRC_FILES := libpostproc-53.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := swresample
LOCAL_SRC_FILES := libswresample-1.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := swscale
LOCAL_SRC_FILES := libswscale-3.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := ffmpeg_player
LOCAL_SRC_FILES := ffmpeg_player.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
LOCAL_LDLIBS := -llog
LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil postproc swresample swscale
include $(BUILD_SHARED_LIBRARY)
- 编写
Application.mk
文件
APP_ABI := armeabi
APP_PLATFORM := android-8
- 实现头文件
#include <android/log.h>
#include <stdio.h>
#include <stdlib.h>
#include "com_cj5785_ffmpegplayer_VideoUtils.h"
//封装格式
#include "include/libavformat/avformat.h"
//解码
#include "include/libavcodec/avcodec.h"
//像素处理
#include "include/libswscale/swscale.h"
#define LOGI(FORMAT,...) __android_log_print(5,"cj5785",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(6,"cj5785",FORMAT,##__VA_ARGS__);
JNIEXPORT void JNICALL Java_com_cj5785_ffmpegplayer_VideoUtils_decode
(JNIEnv *env, jclass jcls, jstring jstr_input, jstring jstr_output)
{
//将jstring转化为cstr
const char *input_cstr = (*env)->GetStringUTFChars(env, jstr_input, NULL);
const char *output_cstr = (*env)->GetStringUTFChars(env, jstr_output, NULL);
//1.注册组件
av_register_all();
//分装格式上下文
AVFormatContext *pFormatCtx = avformat_alloc_context();
//2.打开视频文件
//AVInputFormat和AVDictionary在pFormatContext中已经包含
if(avformat_open_input(&pFormatCtx, input_cstr, NULL, NULL) != 0)
{
LOGE("%s", "打开文件失败!");
return;
}
//3.获取视频相关信息
if(avformat_find_stream_info(pFormatCtx, NULL) < 0)
{
LOGE("%s", "获取视频信息失败!");
return;
}
//视频解码
int i = 0;
int video_stream_index = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++) {
//判断是否是视频流
if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
video_stream_index = i;
break;
}
}
if (video_stream_index == -1)
{
LOGE("%s","找不到视频流\n");
return;
}
//4.获取解码器
AVCodecContext *pCodecCtx = pFormatCtx->streams[video_stream_index]->codec;
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec == NULL)
{
LOGE("%s", "无法解码!");
return;
}
//5.打开解码器
if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
{
LOGE("%s", "解码失败!");
return;
}
//输出视频信息
LOGI("视频的文件格式:%s",pFormatCtx->iformat->name);
LOGI("视频时长:%d", (pFormatCtx->duration)/1000000);
LOGI("视频的宽高:%d,%d",pCodecCtx->width,pCodecCtx->height);
LOGI("解码器的名称:%s",pCodec->name);
//6.以帧为单位读取视频文件
//编码数据 AVPacket初始化
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
//解码数据(像素数据) AVFrame初始化
AVFrame *pFrame = av_frame_alloc();
AVFrame *pYUVFrame = av_frame_alloc();
//只有指定了AVFrame的像素格式,画面大小才能真正分配内存
//缓冲区分配内存
uint8_t *out_buf = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
//初始化缓冲区
avpicture_fill((AVPicture *)pYUVFrame, out_buf, 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_BILINEAR, NULL, NULL, NULL);
//打开写入文件
FILE *fp_yuv = fopen(output_cstr, "wb");
int len, got_frame, frame_count = 0;
while(av_read_frame(pFormatCtx, packet) >= 0)
{
//提取视频压缩数据
if(packet->stream_index == video_stream_index)
{
//AVPacket转化为AVFrame
len = avcodec_decode_video2(pCodecCtx, pFrame, &got_frame, packet);
if(len < 0)
{
LOGE("%s","解码错误!");
return;
}
//got_frame非零,表示正在解码
if(got_frame)
{
//由frame得到YUV的frame
//转为指定的YUV420P像素帧
sws_scale(sws_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
pYUVFrame->data, pYUVFrame->linesize);
//向YUV文件保存解码之后的帧数据
//一个像素包含一个Y
//UV都是Y的四分之一
int y_size = pCodecCtx->width * pCodecCtx->height;
fwrite(pYUVFrame->data[0], 1, y_size, fp_yuv);
fwrite(pYUVFrame->data[1], 1, y_size/4, fp_yuv);
fwrite(pYUVFrame->data[2], 1, y_size/4, fp_yuv);
LOGI("解码第%d帧", frame_count++);
}
}
//释放AVPacket
av_free_packet(packet);
}
//关闭各种打开的资源
fclose(fp_yuv);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
avformat_free_context(pFormatCtx);
//释放资源
(*env)->ReleaseStringUTFChars(env, jstr_input, input_cstr);
(*env)->ReleaseStringUTFChars(env, jstr_output, output_cstr);
}
- 创建调用类,注意动态库之间有相互关系,其调用顺序一定要对
public class VideoUtils {
public native static void decode(String input, String output);
static {
System.loadLibrary("avutil-54");
System.loadLibrary("swresample-1");
System.loadLibrary("avcodec-56");
System.loadLibrary("avformat-56");
System.loadLibrary("swscale-3");
System.loadLibrary("postproc-53");
System.loadLibrary("avfilter-5");
System.loadLibrary("avdevice-56");
System.loadLibrary("ffmpeg_player");
}
}
- 主活动文件
import java.io.File;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void buttonPush(View view) {
String input = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + "test_in.mp4";
String output = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + "test_out.yuv";
VideoUtils.decode(input, output);
Toast.makeText(MainActivity.this, "转码完成", Toast.LENGTH_SHORT).show();
}
}
- 布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="开始转码"
android:onClick="buttonPush"/>
</LinearLayout>
- 权限添加
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
之后编译,生成apk,在手机上测试,没问题
ffmpeg学习笔记-Linux下编译Android动态库的更多相关文章
- LINUX学习笔记——LINUX下EXP命令全库备份数据库文件
LINUX下EXP命令全库备份数据库文件 1)建立备份目录,目录操作权限授权给Oracle用户 mkdir /backup --创建backup文件夹 cd / --进入cd语句 ls -l ...
- linux下编译安装boost库
linux下编译安装boost库 linux下编译安装boost库 1.下载并解压boost 1.58 源代码 下载 解压 2.运行bootstrap.sh 3.使用b2进行构建 构建成功的提示 4. ...
- Linux下编译使用boost库:
Boost是什么不多说, 下面说说怎样在Linux下编译使用Boost的所有模块. 1. 先去Boost官网下载最新的Boost版本, 我下载的是boost_1_56_0版本, 解压. 2. 进入解压 ...
- Linux程序编译链接动态库版本号的问题
不同版本号的动态库可能会不兼容,假设程序在编译时指定动态库是某个低版本号.执行是用的一个高版本号,可能会导致无法执行. Linux上对动态库的命名採用libxxx.so.a.b.c的格式.当中a代表大 ...
- MongoDB学习笔记—Linux下搭建MongoDB环境
1.MongoDB简单说明 a MongoDB是由C++语言编写的一个基于分布式文件存储的开源数据库系统,它的目的在于为WEB应用提供可扩展的高性能数据存储解决方案. b MongoDB是一个介于关系 ...
- solr学习笔记-linux下配置solr(转)
本文地址: http://zhoujianghai.iteye.com/blog/1540176 首先介绍一下solr: Apache Solr (读音: SOLer) 是一个开源.高性能.采用Jav ...
- Linux 程序设计学习笔记----Linux下文件类型和属性管理
转载请注明出处:http://blog.csdn.net/suool/article/details/38318225 部分内容整理自网络,在此感谢各位大神. Linux文件类型和权限 数据表示 文件 ...
- Java学习笔记——Linux下安装配置tomcat
朝辞白帝彩云间,千里江陵一日还. 两岸猿声啼不住,轻舟已过万重山. ——早发白帝城 首先需要安装配置JDK,这里简单回顾下.Linux下用root身份在/opt/文件夹下创建jvm文件夹,然后使用ta ...
- Linux下编译安装PCRE库
备注:如果没有root权限,使用 --prefix 指定安装路径 ./configure --prefix=/home/work/tools/pcre-8.xx =================== ...
随机推荐
- mysql 常见ALTER TABLE操作
删除列 alter table table-name drop col-name; 增加列(单列) alter table table-name add col-name col-type comme ...
- 007_Linux驱动之_copy_from_user函数
1. copy_from_user函数的目的是从用户空间拷贝数据到内核空间 2. 解析原型: copy_from_user(void *to, const void __user *from, uns ...
- 【概率论】4-6:协方差和相关性(Covariance and Correlation)
title: [概率论]4-6:协方差和相关性(Covariance and Correlation) categories: - Mathematic - Probability keywords: ...
- CSPS模拟69-72
模拟69: T1,稍数学,主要还是dp(转移莫名像背包???),当C开到n2时复杂度为n4,考场上想了半天优化结果发现n是100,n4可过 #include<iostream> #incl ...
- Python学习日记(九)—— 模块二(logging、json&pickle、xml、requests、configparser、shutil、subprocess)
logging模块 用于便捷记录日志且线程安全的模块(便捷的写文件的模块,不允许多个人同时操作文件) 1.单文件日志 import logging logging.basicConfig(filena ...
- windows环境rabbitmq安装步骤
windows环境rabbitmq安装步骤: 1 提前安装erl; 2 rabbitmq安装后自动启动; 3 从开始菜单进入rabbit命令窗,启用插件; 下面是命令: 启用插件 rabbitmq ...
- Atcoder ABC 139B
Atcoder ABC 139B 题意: 一开始有1个插口,你的插排有 $ a $ 个插口,你需要 $ b $ 个插口,问你最少需要多少个插排. 解法: 暴力模拟. CODE: #include< ...
- pwn学习日记Day19 《程序员的自我修养》读书笔记
windows PE/COFF章总结 本章学习了windows下的可执行文件和目标文件格式PE/COFF.PE/COFF文件与ELF文件非常相似,它们都是基于段的结构的二进制文件格式.Windows下 ...
- GA算法及参数对结果的影响
1.遗传算法简介 遗传算法是一种基于自然选择和群体遗传机理的搜索算法,它模拟了自然选择和自然遗传过程中的繁殖.杂交和突变现象.再利用遗传算法求解问题时,问题的每一个可能解都被编码成一个“染色体”,即个 ...
- 如果你的电脑想升级并且支持m.2接口
便宜啊,赶紧入手. 文章来源:刘俊涛的博客 欢迎关注,有问题一起学习欢迎留言.评论