用surfaceview播放FFmpeg解码视屏

关于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");
    }
    @Override
    protected 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上 yes no

最后看先代码吧

#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标签下,不是工程的build
 sourceSets{
        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-lib
                       libavdevice-56-lib
                       libavfilter-5-lib
                       libavformat-56-lib
                       libavutil-54-lib
                       libpostproc-53-lib
                       libswresample-1-lib
                       libswscale-3-lib
                       libyuv-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;

    @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);
        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 25
    buildToolsVersion "25.0.3"
    defaultConfig {
        applicationId "com.example.demoffmpeg"
        minSdkVersion 21
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        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"
        }
    }
    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版本)

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Android中使用FFmpegSurfaceView可以实现视频的解码和显示。下面是一个简单的示例: 首先,你需要在Android项目中集成FFmpeg库。可以参考引用中的链接,按照指导进行编译和配置。 接下来,你可以使用FFmpeg解码功能将视频文件解码为YUV格式的数据。然后,使用FFmpeg提供的像素格式转换接口将YUV数据转换为RGB格式的数据。具体的代码示例如下: ```java // 初始化FFmpeg FFmpeg ffmpeg = FFmpeg.getInstance(context); ffmpeg.loadBinary(new LoadBinaryResponseHandler() { @Override public void onStart() { // 加载FFmpeg库成功 } @Override public void onFailure() { // 加载FFmpeg库失败 } @Override public void onSuccess() { // 加载FFmpeg库成功 } @Override public void onFinish() { // 加载FFmpeg库完成 } }); // 解码视频文件 ffmpeg.execute("-i input.mp4 -pix_fmt yuv420p output.yuv", new ExecuteBinaryResponseHandler() { @Override public void onStart() { // 开始解码 } @Override public void onProgress(String message) { // 解码进度 } @Override public void onFailure(String message) { // 解码失败 } @Override public void onSuccess(String message) { // 解码成功 } @Override public void onFinish() { // 解码完成 } }); // 将YUV数据转换为RGB数据 ffmpeg.execute("-pix_fmts", new ExecuteBinaryResponseHandler() { @Override public void onStart() { // 开始转换 } @Override public void onProgress(String message) { // 转换进度 } @Override public void onFailure(String message) { // 转换失败 } @Override public void onSuccess(String message) { // 转换成功 } @Override public void onFinish() { // 转换完成 } }); ``` 最后,你可以使用SurfaceView和ANativeWindow来显示RGB格式的视频数据。具体的代码示例如下: ```java // 创建SurfaceView SurfaceView surfaceView = new SurfaceView(context); // 获取SurfaceHolder SurfaceHolder surfaceHolder = surfaceView.getHolder(); // 设置SurfaceHolder的类型为SURFACE_TYPE_PUSH_BUFFERS surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); // 获取ANativeWindow ANativeWindow nativeWindow = surfaceHolder.getSurface(); // 设置ANativeWindow的格式为RGB nativeWindow.setFormat(PixelFormat.RGBA_8888); // 使用ANativeWindow显示RGB数据 nativeWindow.lockCanvas(null); // 在这里绘制RGB数据到Canvas nativeWindow.unlockCanvasAndPost(canvas); ``` 通过以上步骤,你可以在Android中使用FFmpegSurfaceView实现视频的解码和显示。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值