关于FFmpeg解码请看第一篇教程:FFmpeg解码

下载转码库libyuv

一般我们用surfaceview播放视频都是才用RGBA格式等播放的,但我们解码之后的视频可能是h.264等等 所以我们这里不管解码的是什么格式直接转化为RGBA即可.

所以我们这里用ffmeg解码再用libyuv转码(谷歌推出的一个转码库)

转码库:libyuv是谷歌推出一个转码库
libyuv官网地址(请科学上网)

我用的版本是我现在看到最新的:星期六01 17:57:45 2017
我用的版本地址

注意:官网有个git XXXX 的地址 不使用git来下载的

下载方式:
点击你想要的版本后 会跳转到二级页面 ,在二级界面点击tgz即可下载压缩包.

编译libyuv

我下载后打开解压查看目录:

其实里面很多文件都是多余的.
我们现在来精简下.
1 创建一个文件夹名字为jni(一定要这个 后面解释)

2 将解压目录的Android.mk 和include文件夹和source文件夹放入
如图:

注:上面的Application.mk是我自己创建的.是用来控制编译生成不同架构的共享库,当然这个文件也可以不要默认生成arm架构
下面是这个文件内容

3 修改Android.mk内容


解释下为什么要修改:上面是配置libjpeg库的一些内容,这里暂时不需要,如果你需要的话,需要上面下载libjpeg库放入和修改

4 把jni放入linux系统下使用ndk-build进行编译

关于ndk-build编译:大家在网上自行下载bdk文件到linux下然后解压配置环境变量.
NDK下载地址

5 在linux系统下的jni目录使用ndk-build编译(刚刚第四部上传的)

编译完成之后会在jni 目录下生成libs和obj文件夹,我们拷贝libs下的so文件到本地即可


eclipse

关于FFmpeg解码请看第一篇教程:FFmpeg解码

接续用上一篇文章的代码修改使用.

把刚才编译生成的so文件放入jni下

把linyuv的include(我们前面编译jni文件里面)头文件放入 jni/include下

最后修改Android.mk文件关联下so库

这篇标题说了用surfaceview进行播放,那么surfaceview肯定要传入jni代码内控制。而在c代码控制(专业点就是native控制)需要带入ndk自带的库

配置完成,接下来看下其代码(需要看过上篇文章)

1 创建一个继承SurfaceView的控件VideoView.之所以继承是方便初始化的.这个控件用于解码把视频显示在界面上

//VideoView.java文件
package com.fmple.demoffmepeg;import android.content.Context;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;public class VideoView extends SurfaceView{public VideoView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init();}public VideoView(Context context, AttributeSet attrs) {super(context, attrs,0);}public VideoView(Context context) {super(context,null);}//初始化进行输出设置格式public void init(){SurfaceHolder holder = getHolder();holder.setFormat(PixelFormat.RGB_888);}
}

接着我们看布局文件:

<!--activity_main.xml--><RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context="com.fmple.demoffmepeg.MainActivity" ><com.fmple.demoffmepeg.VideoView
        android:id="@+id/vv"android:layout_width="match_parent"android:layout_height="match_parent"/></RelativeLayout>

最后看看java层代码

//MainActivity.java
package com.fmple.demoffmepeg;import java.io.File;import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Surface;
import android.view.SurfaceView;public class MainActivity extends Activity {private VideoView videoView;static{System.loadLibrary("DemoFFmepeg");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);videoView = (VideoView) findViewById(R.id.vv);final File inputFile = new File(Environment.getExternalStorageDirectory(),"a.mov");//由于是耗时操作 所以开了个线程new Thread(){public void run() {ffmpeg(inputFile.getAbsolutePath(),videoView.getHolder().getSurface() );};}.start();}/*** * @param input 视频文件的输入路径* @param out 把视频文件解码成yuv格式输出路径*/public native void ffmpeg(String input,Surface surface);}

在看c代码之前先补充知识

Native控制surfaceview方法步骤:

1 ANativeWindow_fromSurface函数关联java层的suraceview:

API介绍:
/**
*返回 ANativeWindow 相关联的java层的surfaceview对象,以它作为媒介和c交互
* 使用后一定要调用ANativeWindow_release(),不然会内存泄露
*/

2 ANativeWindow_Buffer 是一个结构体,surfaceview读取它的内容然后绘制到界面上

typedef struct ANativeWindow_Buffer {// The number of pixels that are show horizontally.int32_t width;// The number of pixels that are shown vertically.int32_t height;// The number of *pixels* that a line in the buffer takes in// memory.  This may be >= width.int32_t stride;// The format of the buffer.  One of WINDOW_FORMAT_*int32_t format;// The actual bits.void* bits;// Do not touch.uint32_t reserved[6];
} ANativeWindow_Buffer;

3 ANativeWindow_lock
锁定窗口(window)的下一个画面就行写入

int32_t ANativeWindow_lock(ANativeWindow* window, ANativeWindow_Buffer* outBuffer,ARect* inOutDirtyBounds);

第一个参数:和java层suraceview相关联的ANativeWindow对象,就是我们介绍的第一个函数所返回值
第二个参数: 缓存对象.我们介绍的第二个对象
第三个参数:输出的矩形位置.如果不指定那么全部绘制

4 ANativeWindow_setBuffersGeometry

int32_t ANativeWindow_setBuffersGeometry(ANativeWindow* window, int32_t width, int32_t height, int32_t format);

API介绍:

 改变窗口缓存区的格式和大小宽度和高度控制缓存区的像素数量,而不是窗口屏幕的宽高如果尺寸和窗口的物理尺寸不同,则缓存区将进行缩放后合成到屏幕对于所有参数,如果返回0则窗口的所有基本数值将会强制回滚

设置缓冲区的一些格式
第一个参数:和java层相关联的ANativeWindow
第二个参数: 宽 像素宽(不是播放的宽和高)
第三个参数:高 像素高
第四个参数:缓存区格式 一般和我们的surfaceview初始化的格式一致

5 ANativeWindow_unlockAndPost
解锁先前锁定的窗口 ,将新的缓存区内容显示在界面上

6 ANativeWindow_release
释放和java层关联的对象

那么大致的流程图:

Created with Raphaël 2.1.0开始ffmeg解码视频解码第x帧解码结束 ?释放资源ANativeWindow_release结束调用ANativeWindow_lock锁定窗口调用ANativeWindow_setBuffersGeometry设置缓存区ANativeWindow_Buffer格式将解码数据转为surfaceview初始化的格式将解码数据写入ANativeWindow_Buffer中调用ANativeWindow_unlockAndPost释放缓存数据显示到surfaceview中显示视频在surface上yesno

最后看先代码吧

#include <jni.h>#include<stdio.h>
#include<android/log.h>
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//像素处理
#include "libswscale/swscale.h"
#include <android/native_window_jni.h>
#include <android/native_window.h>
#include<unistd.h>
#include "libyuv.h"
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO," FMY",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"FMY",FORMAT,##__VA_ARGS__);
char * output_cstr;JNIEXPORT void JNICALL Java_com_fmple_demoffmepeg_MainActivity_ffmpeg
(JNIEnv * env, jobject jobj, jstring input, jobject surface){char * input_char = (*env)->GetStringUTFChars(env,input,NULL);//头文件libavformat/avformat.h//注册所有组件/*** 初始化libavformat和注册所有的 muxers, demuxers 和协议,如果你不想使用次函数,* 则可以手动选择你想要的支持格式* 详情你可以选择下面的函数查询* @see av_register_input_format()* @see av_register_output_format()** muxer是合并将视频文件、音频文件和字幕文件合并为某一个视频格式。如,可将a.avi, a.mp3, a.srt用muxer合并为mkv格式的视频文件。* demuxer是拆分这些文件的。*/av_register_all();// 封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息。AVFormatContext * pFormatCtx = avformat_alloc_context();/*** 打开输入流并且读取头信息。但解码器没有打开* 这个输入流必须使用avformat_close_input()关闭* @param ps(第一个参数的形参名称) 指向 你由你提供AVFormatContext(AVFormatContext是由avformat_alloc_context函数分配的)。* 有可能ps指向空,在这种情况下,AVFormatContext由此函数分配并写入ps。* 注意: 你提供的AVFormatContext在函数执行失败的时候将会被释放* @param url 你要打开视频文件路径.* @param fmt  如果不为空,那么这个参数将强制作为输入格式,否则自动检索* @param options 一个关于AVFormatContext and demuxer-private 选项的字典.* 返回时,此参数将被销毁,并替换为包含未找到的选项的dict。有可能是空的** @return 返回0表示成功, 一个负数常量AVERROR是失败的.** @note 如果你想自定义IO,你需要预分配格式内容并且设置pd属性*/if(avformat_open_input(&pFormatCtx,input_char,NULL,NULL)!=0){LOGE("NDK>>>%s","avformat_open_input打开失败");return;}//上面打开输入流后会将视频封装格式信息写入AVFormatContext中那么我们下一步就可以得到一些展示信息/**** 读取媒体文件中的数据包以获取流信息,这个对于对于文件格式没有头信息的很有帮助,比如说mpeg* 这个函数还可以计算在MPEG-2重复帧模式的真实帧速率。* 逻辑文件位置不会被这个函数改变* 检索过的数据包或许会缓存以供后续处理* @param ic  第一个参数 封装格式上下文* @param options*              如果不为空, 一个长度为 ic.nb_streams (封装格式所有流,字幕 视频 音频等) 的字典。*              字典中第i个成员  包含一个对应ic第i个流中对的编码器。*              在返回时,每个字典将会填充没有找到的选项* @return 如果返回>=0 代表成功, AVERROR_xxx 表示失败** @note 这个函数 不保证能打开所有编码器,所以返回一个非空的选项是一个完全正常的行为*** @todo*  下个版本目标无视即可* Let the user decide somehow what information is needed so that*       we do not waste time getting stuff the user does not need.*/if( avformat_find_stream_info(pFormatCtx,NULL)<0){LOGE("NDK>>>%s","avformat_find_stream_info失败");return ;}LOGE("NDK>>>%s","成功");//  //输出视频信息//  LOGI("视频的文件格式:%s",pFormatCtx->iformat->name);//  LOGI("视频时长:%d", (pFormatCtx->duration)/1000000);//获取视频流的索引位置//遍历所有类型的流(音频流、视频流、字幕流),找到视频流int v_stream_idx = -1;int i = 0;//遍历封装格式中所有流for (; i < pFormatCtx->nb_streams; ++i) {//获取视频流pFormatCtx->streams[i]//pFormatCtx->streams[i]->codec获取编码器//codec_type获取编码器类型//当前流等于视频 记录下标if (pFormatCtx->streams[i]->codec->codec_type ==AVMEDIA_TYPE_VIDEO) {v_stream_idx = i;break;}}if (v_stream_idx==-1) {LOGE("没有找视频流")}else{LOGE("找到视频流")}//编码器上下文结构体,保存了视频(音频)编解码相关信息//得到视频流编码器AVCodecContext *pCodecCtx = pFormatCtx->streams[v_stream_idx]->codec;//   每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。AVCodec *pCodec =avcodec_find_decoder(pCodecCtx->codec_id);//(迅雷看看,找不到解码器,临时下载一个解码器)if (pCodec == NULL){LOGE("%s","找不到解码器\n");return;}else{LOGE("%s","找到解码器\n");}//打开解码器/*** 初始化 指定AVCodecContext去使用 给定的AVCodec* 在使用之前函数必须使用avcodec_alloc_context3()分配上下文。** 以下函数 avcodec_find_decoder_by_name(), avcodec_find_encoder_by_name(),* avcodec_find_decoder() and avcodec_find_encoder() 提供了一个简便的得到一个解码器的方法** @warning 这个函数线程不是安全的** @note 在使用解码程序之前,始终调用此函数 (如 @ref avcodec_decode_video2()).* 下面是示例代码* @code* avcodec_register_all();* av_dict_set(&opts, "b", "2.5M", 0);* codec = avcodec_find_decoder(AV_CODEC_ID_H264);* if (!codec)*     exit(1);** context = avcodec_alloc_context3(codec);** if (avcodec_open2(context, codec, opts) < 0)*     exit(1);* @endcode*** @param avctx 要初始化的编码器* @param codec 用这个codec去打开给定的上下文编码器.如果 codec 不为空 那么必须* 事先用avcodec_alloc_context3和avcodec_get_context_defaults3传递给这个context,那么这个codec* 要么为NULL要么就是上面调用函数所使用的codec** @param** 选项填充AVCodecContext和编解码器私有选项的字典。返回时,此对象将填充未找到的选项。** @return 返回0表示成功, 负数失败* @see avcodec_alloc_context3(), avcodec_find_decoder(), avcodec_find_encoder(),*      av_dict_set(), av_opt_find().*/if(avcodec_open2(pCodecCtx,pCodec,NULL)==0){LOGE("%s","打开编码器成功\n");}else{LOGE("%s","打开编码器失败\n");return;}//输出视频信息LOGE("视频的文件格式:%s",pFormatCtx->iformat->name);//得到视频播放时长if(pFormatCtx->duration != AV_NOPTS_VALUE){int hours, mins, secs, us;int64_t duration = pFormatCtx->duration + 5000;secs = duration / AV_TIME_BASE;us = duration % AV_TIME_BASE;mins = secs / 60;secs %= 60;hours = mins/ 60;mins %= 60;LOGE("%02d:%02d:%02d.%02d\n", hours, mins, secs, (100 * us) / AV_TIME_BASE);}LOGE("视频的宽高:%d,%d",pCodecCtx->width,pCodecCtx->height);LOGE("解码器的名称:%s",pCodec->name);////  //存储一帧压缩编码数据。AVPacket *packet =av_malloc(sizeof(AVPacket));////  //输出转码文件地址//  FILE *fp_yuv = fopen(output_cstr,"wb+");////  //AVFrame用于存储解码后的像素数据(YUV)//  //内存分配AVFrame *pFrame = av_frame_alloc();////  //YUV420转码用//  AVFrame *pFrameYUV = av_frame_alloc();//  //avpicture_get_size()函数介绍://  ////  /**//   * 如果给定存储图片的格式,那么计算给定的宽高所占用的大小//   *//   * @param pix_fmt   图片像素格式//   * @param width     图片宽//   * @param height     图片高//   * @return 返回计算的图片缓存大小或者错误情况下的负数错误代码//   *//   *//   * 这里计算缓存区的大小,但是没有分配,这里是用来后面转码使用//   *///  uint8_t *out_buffer = av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P,pCodecCtx->width,pCodecCtx->height));////  //初始化缓冲区//  /**//   * 基于指定的图片参数和提供的图片缓存区去设置图片字段//   *//   * 使用ptr所指向的图片数据缓存  填充图片属性//   *//   * 如果 ptr是空,这个函数仅填充图片行大小(linesize)的数组并且返回图片缓存请求的大小//   *//   * 要分配图片缓存并且再一次填充图片数据请使用 avpicture_alloc().//   * @param picture       要填充的图片//   * @param ptr           存储图片的数据的缓存区, or NULL//   * @param pix_fmt       图片像素格式//   * @param width         图片宽//   * @param height        图片高//   * @return 返回请求的字节大小,在错误的情况下返回负数//   *//   * @see av_image_fill_arrays()//   *///  avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);//  //用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等//  /**//   *分配和返回 SwsContext. You need it to perform//   * scaling/conversion operations using sws_scale().//   *//   * @param srcW 原始图宽//   * @param srcH 原始图高//   * @param srcFormat 原始图格式//   * @param dstW 目标图宽//   * @param dstH 不解释//   * @param dstFormat 不解释//   * @param flags 指定一个标志用于重新调整算法和选项//   *  具体参考:http://blog.csdn.net/leixiaohua1020/article/details/12029505//   * @return 一个指定分配内容的指针, 错误情况返回空//   * @note this function is to be removed after a saner alternative is//   *       written//   *///      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;////  //返回和java surface关联的ANativeWindow通过本地本地方法交互ANativeWindow * nativeWindow =ANativeWindow_fromSurface(env,surface);    //缓存ANativeWindow_Buffer outBuffer;    //设置缓存的几何信息AVFrame *rgb_frame = av_frame_alloc();uint8_t *out_buffer = av_malloc(avpicture_get_size(AV_PIX_FMT_RGBA,pCodecCtx->width,pCodecCtx->height));//////读取每一帧/***返回下一帧的流* 此函数返回存储在文件中的内容,并且不会验证解码器有什么有效帧。* 函数将存储在文件中的帧进行分割 并且返回给每一个调用者。** 函数不会删除在有效帧之间的无效数据 以便在可能解码过程中提供解码器最大的信息帮助* 如果 pkt->buf 是空的,那么这个对应数据包是有效的直到下一次调用av_read_frame()* 或者直到使用avformat_close_input().否则包无期限有效* 在这两种情况下 这个数据包当你不在需要的时候,你必须使用使用av_free_packet释放它* 对于视屏,数据包刚好只包含一帧.对于音频,如果它每一帧是一个已知固定大小的,那么他包含整数帧(如. PCM or ADPCM data)* 如果音频帧具有可变大小(如. MPEG audio),那么他只包含一帧* pkt->pts, pkt->dts and pkt->duration 始终在AVStream.time_base 单位设置正确的数值*(如果这个格式无法提供.那么进行猜测)* 如果视频格式有B帧那么pkt->pts可以是 AV_NOPTS_VALUE.如果你没有解压他的有效部分那么最好依靠pkt->dts** @return 0表示成功, < 0 错误或者文结束*/while(av_read_frame(pFormatCtx,packet)>=0){//一个包里有很多种类型如音频视频等 所以判断 这个包对应的流的在封装格式的下表//如果这个包是视频频包那么得到压缩的视频包if (packet->stream_index==v_stream_idx) {LOGE("测试");/*** 解码视频帧 从avpkt->data读取数据并且解码avpkt->size的大小后转化为图片.* 一些解码器可以支持在一个ACpacket中存在多帧的情况,像这样的解码器将只解码第一帧** @warning  输入缓存区必须 实际读取的字节流小于 FF_INPUT_BUFFER_PADDING_SIZE,* 一些优化过的比特流 读取32位或者64字节 的时候可以一次性读取完** @warning 在缓冲器的buf结尾设置0以确保被破坏的MPEG流不会发生超线程** @note 有 CODEC_CAP_DELAY 才能设置一个在输入和输出之间的延迟,这些需要使用avpkt->data=NULL,*  在结束返回剩余帧数的时候avpkt->size=0** @note  这个AVCodecContext 在数据包传入解码器之前必须调用avcodec_open2*** @param avctx 解码器上下文** @param[out] 解码的视频帧图片将会被存储在AVFrame.*                 使用av_frame_alloc 得到一个AVFrame,*                 编码器将会分配 使用  AVCodecContext.get_buffer2() 回调*                 的实际图片的内存.*                 当AVCodecContext.refcounted_frames 设置为1,这帧(frame)是引用计数,并且返回*                 的引用计数是属于调用者的.*             frame在长实际不使用的时候调用者必须调用av_frame_unref()就行释放*             如果av_frame_is_writable()返回1那么调用者可以安全的写入到这个frame中。*                 当AVCodecContext.refcounted_frames设置为0,返回的引用属于解码器,*                只有下次使用这个函数或者关闭或者刷新这个编码器之前有效。调用者不会写入它**@param[in,out] got_picture_ptr 如果为0表示不能解压, 否者它不是0.** @param[in] avpkt 这个输入的avpkt包含输入缓存区*              你能使用av_init_packet()创建像这样的packet然后设置数据和大小,*              一些解码器还可以添加一些其他字段 比如  flags&AV_PKT_FLAG_KEY  flags&AV_PKT_FLAG_KEY*          所有解码器都设计为尽可能少地使用** @return 再错误时返回一个负数 , 否则返回使用字节数或者或者0(没有帧被解压返回0)otherwise the number of bytes**/ret=avcodec_decode_video2(pCodecCtx,pFrame,&got_picture,packet);if(ret>=0){LOGE("解压成功");//AVFrame转为像素格式YUV420,宽高//2 6输入、输出数据//3 7输入、输出画面一行的数据的大小 AVFrame 转换是一行一行转换的//4 输入数据第一列要转码的位置 从0开始//5 输入画面的高度//              sws_scale(sws_ctx,pFrame->data,pFrame->linesize,0,pCodecCtx->height,pFrameYUV->data,pFrameYUV->linesize);//输出到YUV文件//AVFrame像素帧写入文件//data解码后的图像像素数据(音频采样数据)//Y 亮度 UV 色度(压缩了) 人对亮度更加敏感//U V 个数是Y的1/4//              int y_size = pCodecCtx->width * pCodecCtx->height;ANativeWindow_lock(nativeWindow,&outBuffer,NULL);
//              ANativeWindow_setBuffersGeometry(nativeWindow,pCodecCtx->width,pCodecCtx->height,WINDOW_FORMAT_RGBA_8888);avpicture_fill((AVPicture *)rgb_frame,out_buffer, AV_PIX_FMT_RGBA, pCodecCtx->width, pCodecCtx->height);
//
//I420ToARGB(pFrame->data[0],pFrame->linesize[0],pFrame->data[2],pFrame->linesize[2],pFrame->data[1],pFrame->linesize[1],rgb_frame->data[0], rgb_frame->linesize[0],pCodecCtx->width,pCodecCtx->height);int h = 0;for (h = 0; h < pCodecCtx->height; h++) {memcpy(outBuffer.bits + h * outBuffer.stride*4, out_buffer + h * rgb_frame->linesize[0], rgb_frame->linesize[0]);
//              memcpy(outBuffer.bits,out_buffer,pCodecCtx->width*pCodecCtx->height*4);}
LOGE("锁定成功");ANativeWindow_unlockAndPost(nativeWindow);
//               //获取帧率tbr fbs//float fram_rate =pFormatCtx->streams[v_stream_idx]->avg_frame_rate.num/pFormatCtx->streams[v_stream_idx]->avg_frame_rate.den;
//              如果奔溃请开启下面的线程休眠
//              usleep(1000);}}av_free_packet(packet);}(*env)->ReleaseStringUTFChars(env,input,input_char);//关闭文件//  fclose(fp_yuv);ANativeWindow_release(nativeWindow);
//  //关闭资源av_frame_free(&pFrame);
//  //  av_frame_free(&pFrameYUV);
//  av_frame_free(&rgb_frame);
//  //关闭编码器上下文avcodec_close(pCodecCtx);
//  //关闭输入流avformat_close_input(&pFormatCtx);
//  //关闭封装格式avformat_free_context(pFormatCtx);
}

Studio版本

都差不多 只不过才用Cmake方式.这里和上一篇差不多 所以在上一篇基础添加修改方法而已
导入所有so库到
关于FFmpeg解码请看第一篇教程:FFmpeg解码

关于有人说找不到so库的,请在build.gradle里面添加如下代码

//对应子项目的build下android标签下,不是工程的buildsourceSets{main{jniLibs.srcDirs =['libs']}}

1 添加yuvso.so到libs中

2 修改cmake文件

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html# Sets the minimum version of CMake required to build the native library.cmake_minimum_required(VERSION 3.4.1)# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.add_library( # Sets the name of the library.native-lib# Sets the library as a shared library.SHARED# Provides a relative path to your source file(s).src/main/cpp/native-lib.c)##################################################################################################
##################################################################################################
set(distribution_DIR ${CMAKE_SOURCE_DIR}/libs/)#libavcodec-56.so
add_library(libavcodec-56-lib SHARED IMPORTED)
set_target_properties(libavcodec-56-lib  PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/${ANDROID_ABI}/libavcodec-56.so)#libavdevice-56.so
add_library(libavdevice-56-lib SHARED IMPORTED)
set_target_properties(libavdevice-56-lib  PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/${ANDROID_ABI}/libavdevice-56.so)#libavfilter-5.so
add_library(libavfilter-5-lib SHARED IMPORTED)
set_target_properties(libavfilter-5-lib  PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/${ANDROID_ABI}/libavfilter-5.so)#libavformat-56.so
add_library(libavformat-56-lib SHARED IMPORTED)
set_target_properties(libavformat-56-lib  PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/${ANDROID_ABI}/libavformat-56.so)#libavutil-54.so
add_library(libavutil-54-lib SHARED IMPORTED)
set_target_properties(libavutil-54-lib  PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/${ANDROID_ABI}/libavutil-54.so)#libpostproc-53.so
add_library(libpostproc-53-lib SHARED IMPORTED)
set_target_properties(libpostproc-53-lib  PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/${ANDROID_ABI}/libpostproc-53.so)#libswresample-1.so
add_library(libswresample-1-lib SHARED IMPORTED)
set_target_properties(libswresample-1-lib  PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/${ANDROID_ABI}/libswresample-1.so)#libswscale-3.so
add_library(libswscale-3-lib SHARED IMPORTED)
set_target_properties(libswscale-3-lib  PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/${ANDROID_ABI}/libswscale-3.so)#libyuv.so
add_library(libyuv-lib SHARED IMPORTED)
set_target_properties(libyuv-lib PROPERTIES IMPORTED_LOCATION ${distribution_DIR}/${ANDROID_ABI}/libyuv.so)##################################################################################################
################################################################################################### Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.find_library( # Sets the name of the path variable.log-lib# Specifies the name of the NDK library that# you want CMake to locate.log )#这个库是用于surface在c代码中交互而添加的
find_library( # Sets the name of the path variable.lnative_window_jni-lib# Specifies the name of the NDK library that# you want CMake to locate.android )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
include_directories(src/main/cpp/include/)
target_link_libraries( # Specifies the target library.native-lib# Links the target library to the log library# included in the NDK.${log-lib}${lnative_window_jni-lib}libavcodec-56-liblibavdevice-56-liblibavfilter-5-liblibavformat-56-liblibavutil-54-liblibpostproc-53-liblibswresample-1-liblibswscale-3-liblibyuv-lib)

最后看看界面

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.demoffmpeg.MainActivity"><SurfaceView
        android:layout_width="0dp"android:id="@+id/sv"android:layout_height="0dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /><TextView
        android:id="@+id/sample_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="onClick"android:textColor="#fff"android:text="播放视频!"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

最后看java代码吧

package com.example.demoffmpeg;import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.View;
import android.widget.TextView;import java.io.File;public class MainActivity extends AppCompatActivity {// Used to load the 'native-lib' library on application startup.static {System.loadLibrary("native-lib");}private SurfaceView sv;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// Example of a call to a native methodTextView tv = (TextView) findViewById(R.id.sample_text);sv = (SurfaceView) findViewById(R.id.sv);}/*** A native method that is implemented by the 'native-lib' native library,* which is packaged with this application.*/public native void plays(String videoPath, Surface surface);//点击播放视频public void onClick(View view) {final File inputFile = new File(Environment.getExternalStorageDirectory(),"a.mp4");//由于是耗时操作 所以开了个线程new Thread(){public void run() {plays(inputFile.getAbsolutePath(),sv.getHolder().getSurface() );};}.start();}
}

附上build.gradle

apply plugin: 'com.android.application'android {compileSdkVersion 25buildToolsVersion "25.0.3"defaultConfig {applicationId "com.example.demoffmpeg"minSdkVersion 21targetSdkVersion 25versionCode 1versionName "1.0"testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"externalNativeBuild {cmake {cppFlags "-frtti -fexceptions"}}ndk{abiFilters 'armeabi'}}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}externalNativeBuild {cmake {path "CMakeLists.txt"}}sourceSets{main{jniLibs.srcDirs =['libs']}}
}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:appcompat-v7:25.3.1'compile 'com.android.support.constraint:constraint-layout:1.0.2'testCompile 'junit:junit:4.12'
}

github连接(studio版本)

用surfaceview播放FFmpeg解码视屏相关推荐

  1. android集成EasyPlayer播放器播放实时流媒体视屏

    android集成EasyPlayer播放器播放实时流媒体视屏 最近公司项目需要实现一个rtsp实时流媒体视频的播放,在移动端尝试了多个第三方能播放rtsp流实时视频的软件后发现EasyPlayer的 ...

  2. iOS 音视频开发:Audio Unit播放FFmpeg解码的音频

    本文档描述了iOS播放经FFmpeg解码的音频数据的编程步骤,具体基于Audio Toolbox框架的Audio Session和Audio Unit框架提供的接口实现.在iOS 7及以上平台Audi ...

  3. 使用FFmpeg将视屏剪辑并上传到华为服务器上

    使用FFmpeg将视屏剪辑并上传到华为服务器上 注意需要自己在windows上安装好FFmpeg组件 教程地址:https://www.jianshu.com/p/2b609afb9800 这套代码不 ...

  4. java调用FFmpeg实现视屏压缩

    前言 最近做人脸识别,对于过大的视屏(10M)以上,识别失败的几率比较大.找了下相关的视屏压缩资料.首先使用的是jave进行视屏压缩,但是踩坑失败,jave中有转码的过程,虽然压缩成功,各种压缩参数也 ...

  5. wxpython播放视频_opencv视屏流嵌入wxpython框架

    前几篇博客分享搭建人脸识别与情绪判断的环境和源码,但是没有UI,界面很难看,一打开就是opencv弹出的一个视屏框.处女座的我看着非常难受,于是决定做一个UI,稍微规矩好看一点,再怎么说,这样的话也算 ...

  6. NDK--利用OpenSL ES实现播放FFmpeg解码后的音频流

    OpenSL ES全称为Open Sound Library for Embedded Systems.OpenSL ES是无授权费.跨平台.针对嵌入式系统 精心优化的硬件音频加速API.当然安卓也使 ...

  7. win10 ffmpeg录制视屏音频

    参考 FFmpeg windows 录屏(录像)录音 实测 录制音频需要的依赖 添加环境变量方便命令行呼出 D:\录制视屏\ bat启动 #录屏.bat python D:\录制视屏\录屏.py 分辨 ...

  8. ffmpeg解码花屏

    问题: 解码为YUV420转为Bitmap后显示在屏幕上时,有三分之二为花屏:如图: 首先用h264Visa分析帧: 已经读出了sps等信息,这些信在解码第一帧时被写入环境变量中,所以下边的帧不需要s ...

  9. Unity 播放全景视屏

    文章目录 前言 二.使用步骤 1.场景物体层级, 2.Sphere属性设置 3.播放 总结 前言 unity 采用原生VideoPlayer组件播放全景视屏, untiy2019.4.17,和一个全景 ...

最新文章

  1. Asp.Net开发架构设计(二)
  2. jdk自带的反编译在那个包下_求教JDK中的反编译命令
  3. 【项目管理】敏捷开发项目管理流程
  4. 接口响应时组装响应json_企业实战之spring项目《接口响应体格式统一封装》
  5. Kotlin 知识梳理(13) 运行时的泛型
  6. ruby sinatra mysql_一分钟开始持续集成之旅系列之:Ruby + Sinatra 应用
  7. oracle查询sql记录数,oracle查询所有表的记录数SQL
  8. Pytorch——搭建深度神经网络的基本流程
  9. hdu 1026【Ignatius and the Princess I】
  10. PDF的图片怎么提取?这两种方法值得收藏
  11. C语言每日一练——第50天:八进制转十进制
  12. 《3D打印:正在到来的工业革命(第2版)》——2.4节粉末床熔融
  13. Windows 10 下载
  14. Python实现经纬度转换
  15. 性能测试和优化实录(mybatisplus异步写库@Async引起的对象无法释放问题)
  16. MetaMask遇到了一个错误Try again by closing and reopening the-BUG
  17. linux oracle创建用户并授权
  18. 苹果审核被拒1.1和5.0
  19. Android之运用百度地图检索地点
  20. 无敌哥-创新设计思维

热门文章

  1. ATT格式汇编命令集合
  2. 软件包下载地址(一)
  3. [Excel表格]单元格内换行
  4. 【UE4】pawn 和 Character 的区别
  5. 聚合API文档阅读帮助
  6. 《男人装》2006.05
  7. 我是没有志气的程序员
  8. 祝所有高考学子高考加油!
  9. glide 加载图片宽度填满,高度自适应图片比列
  10. mysql 用sql语句创建数据库和表