安卓上使用FFMPEG解码视频并处理

【嵌牛导读】:安卓上OPENCV是经过剪切的,部分功能如VideoCapture不能使用。本文使用首先将FFMPEG封装呈.so库,然后使用JNI调用C语言实现了视频的解码处理。

【嵌牛鼻子】:FFMPEG  android JNI  视频解码

【嵌牛提问】:安卓上使用FFMPEG进行视频解码

【嵌牛正文】:

JNI调用NATIVE的FFMPEG

Android的opencv不支持videocapture解码视频,是因为Android上的opencv没有集成ffmpeg这个解码的库。所以需要自己把ffmpeg的源码打包成.so文件,在Android中使用JNI调用.so库,然后再c++中读取手机中的视频解码以后转为一个一个的MAT矩阵,以方便后期处理。下面是步骤

一.   将ffmpeg源码编译成.so库。我使用的是别人编译好的库。具体编译方法看这个网页。https://www.2cto.com/kf/201804/739639.html

二.  编写NATIVE函数

1.新建一个类FFmpegDecode的类,写两个native函数

2.使用Android studio上的终端。转到app\build\intermediates\classes\debug文件夹下

3.生成java的native函数所对应的c++函数头文件

4.然后在app\build\intermediates\classes\debug目录下找到生成的.h头文件

三.安卓上配置NDK。

1修改工程下的gradle.properties

修改app下的build.gradle

sourceSets.main.jni.srcDirs= []

sourceSets.main.jniLibs.srcDirs = ['src/main/libs','src/main/jniLibs']

//禁止自带的ndk功能task ndkBuild(type: Exec,description:'Compile JNI source with NDK') {


Properties properties = new Properties()


properties.load(project.rootProject.file('local.properties').newDataInputStream())


def ndkDir= properties.getProperty('ndk.dir')


if (org.apache.tools.ant.taskdefs.condition.Os.isFamily(org.apache.tools.ant.taskdefs.condition.Os.FAMILY_WINDOWS)) {


commandLine "$ndkDir/ndk-build.cmd",'-C',file('src/main/jni').absolutePath


} else{


commandLine "$ndkDir/ndk-build",'-C',file('src/main/jni').absolutePath


}

}

tasks.withType(JavaCompile) {

    compileTask

-> compileTask.dependsOn ndkBuild

}

task ndkClean(type: Exec,description:'Clean NDK Binaries') {


Properties properties = new Properties()


properties.load(project.rootProject.file('local.properties').newDataInputStream())


def ndkDir= properties.getProperty('ndk.dir')


if (org.apache.tools.ant.taskdefs.condition.Os.isFamily(org.apache.tools.ant.taskdefs.condition.Os.FAMILY_WINDOWS)) {


commandLine "$ndkDir/ndk-build.cmd",'clean','-C',file('src/main/jni').absolutePath


} else{


commandLine "$ndkDir/ndk-build",'clean','-C',file('src/main/jni').absolutePath


}

}

defaultConfig {


multiDexEnabled true

}

clean.dependsOn 'ndkClean'

2修改工程下的local.properties

3在app/src/main下新建jni文件夹。在jni文件夹下新建两个文件Android.mk和Application.mk。

4.将刚才生成的头文件复制到jni文件夹下。同时新建对应的.cpp文件。

5.在jni文件夹下面新建一个include文件夹,把ffmpeg的源码拷贝到该文件夹下

6.将之前编译好的.so文件放到jni文件夹下面

7.编写Android.mk文件

LOCAL_PATH := $(call my-dir)

#ffmpeg lib

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 := yuv

LOCAL_SRC_FILES := libyuv.so

include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE := opencv_java

LOCAL_SRC_FILES := libopencv_java.so

include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)

#OPENCV_CAMERA_MODULES:=on

#OPENCV_INSTALL_MODULES:=off

include ..\..\..\..\native\jni\OpenCV.mk

LOCAL_MODULE     :=ffmdecode

LOCAL_SRC_FILES :=com_tinymonster_ffmpegstudy1_FFmpegDecode.cpp

LOCAL_C_INCLUDES += $(LOCAL_PATH)/include/ffmpeg

LOCAL_C_INCLUDES += $(LOCAL_PATH)/include/libyuv

LOCAL_LDLIBS     += -llog -ldl

LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil postprocswresample swscale yuv

include $(BUILD_SHARED_LIBRARY)

8.编写Application文件

APP_STL := gnustl_static

APP_CPPFLAGS := -frtti -fexceptions

APP_ABI := armeabi

9.将opencv的native库放到工程目录下。

10.点击屏幕右侧的gradle中的ndkbuild。

11.然后会看到jni下面生成了libs和obj两个文件夹。这两个文件夹下面生成的是对应的.so库

12.编写c代码,读取手机视频,解码视频,并转为OPENCV的MAT。

#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"ccj",FORMAT,##__VA_ARGS__);

#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"ccj",FORMAT,##__VA_ARGS__);

using namespace cv;

JNIEXPORT jint JNICALLJava_com_tinymonster_ffmpegstudy1_FFmpegDecode_DecodeFile

  (JNIEnv* env, jclassobj, jstring input_){

LOGE("%s","1");

const

char *filename= env->GetStringUTFChars(input_, 0);

AVCodec*pCodec; //

解码器指针

AVCodecContext* pCodecCtx; //ffmpeg解码类的类成员

AVFrame* pAvFrame; //多媒体帧,保存解码后的数据帧

AVFormatContext* pFormatCtx; //保存视频流的信息

av_register_all(); //注册库中所有可用的文件格式和编码器

pFormatCtx= avformat_alloc_context();

if(avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) { //检查文件头部

LOGE("%s","Can'tfind the stream!");

}

if(avformat_find_stream_info(pFormatCtx,NULL) < 0) { //查找流信息

LOGE("%s","Can'tfind the stream information !");

}

intvideoindex= -1;

for(int i=0; i< pFormatCtx->nb_streams; ++i) //遍历各个流,找到第一个视频流,并记录该流的编码信息

{

if (pFormatCtx->streams[i]->codec->codec_type== AVMEDIA_TYPE_VIDEO) {

videoindex= I;

break;

}

            }


if(videoindex==

-1) {

LOGE("%s","Don'tfind a video stream !");

return 1;

}

pCodecCtx= pFormatCtx->streams[videoindex]->codec; //得到一个指向视频流的上下文指针

pCodec= avcodec_find_decoder(pCodecCtx->codec_id); //到该格式的解码器

if (pCodec== NULL) {

LOGE("%s","Cant'tfind the decoder !");

return 2;

}

if(avcodec_open2(pCodecCtx,pCodec,NULL) < 0) { //打开解码器

LOGE("%s","Can't open the decoder !");

return 3;

}

pAvFrame= avcodec_alloc_frame(); //分配帧存储空间


AVFrame* pFrameBGR= avcodec_alloc_frame(); //存储解码后转换的RGB数据

        //

保存BGR,opencv中是按BGR来保存的

int size=avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);

uint8_t*out_buffer= (uint8_t*)av_malloc(size);

avpicture_fill((AVPicture*)pFrameBGR, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);

AVPacket* packet= (AVPacket*)malloc(sizeof(AVPacket));

LOGI("视频的文件格式:%s",pFormatCtx->iformat->name);

LOGI("视频时长:%d", (pFormatCtx->duration)/1000000);

LOGI("视频的宽高:%d,%d",pCodecCtx->width,pCodecCtx->height);

LOGI("解码器的名称:%s",pCodec->name);

struct SwsContext*img_convert_ctx;

img_convert_ctx= sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL);

//opencv

cv::Mat pCvMat;

pCvMat.create(cv::Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3);

int ret;

int got_picture;

//读取每一帧

int frame_count= 0;

while (av_read_frame(pFormatCtx, packet) >= 0)

                    {

if(packet->stream_index==videoindex)

                        {

ret= avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);

if(ret< 0)

                            {

printf("Decode Error.(解码错误)\n");

return 4;

}

LOGI("解码第%d帧",frame_count);

if (got_picture)

                            {

//YUV to RGB

sws_scale(img_convert_ctx, (const uint8_t* const*)pAvFrame->data, pAvFrame->linesize, 0, pCodecCtx->height, pFrameBGR->data, pFrameBGR->linesize);

memcpy(pCvMat.data, out_buffer, size);//拷贝

frame_count++;

LOGI("解码第%d帧",frame_count);

}

                        }

av_free_packet(packet);

}

av_free(out_buffer);

av_free(pFrameBGR);

av_free(pAvFrame);

avcodec_close(pCodecCtx);

avformat_close_input(&pFormatCtx);

sws_freeContext(img_convert_ctx);

retur 0;


}

13.这样就可以在JAVA代码中调用C++代码处理视频了                   



最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容