FFmpeg环境的搭建在前面一篇博客中已经写了,详情参照:AndroidStudio3.5.1下搭建FFmpeg环境

本文仅实现将mp4的视频部分渲染到SurfaceView中, 不包含音频,不包含播放控制。文中的视频是在SD卡根目录中有一个input.flv文件,需要手动导入,AndroidManifest.xml中需要声明读取权限

UI界面

<?xml version="1.0" encoding="utf-8"?>                
public class MainActivity extends AppCompatActivity {    Player player;    private SurfaceView mSurface;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initView();    }    public void play(View view) {        File file = new File(Environment.getExternalStorageDirectory(), "input.flv");        player.play(file.getAbsolutePath());    }    private void initView() {        player = new Player();        mSurface = (SurfaceView) findViewById(R.id.surface);        player.setSurfaceView(mSurface);    }}

player是个工具类,文章末尾会提供完整player类的代码

 

这样UI的部分就算完成了。 我们是要通过FFmpeg的库将视频渲染到SurfaceView,虽然native层没有SurfaceView这个类的,但是native层是有Surface的,而且可以用Surface来创建一个ANativeWindow,从而将视频渲染到界面上。 本质就是数据的copy

 

native播放代码

 

导入的库有

#include #include #include #include // 没有用extern "C"包括, 会提示undefined reference to 'xxx'// extern "C"的作用是 标识库是用C写的,extern "C" {#include "include/libavutil/avutil.h"#include "include/libavformat/avformat.h"#include "include/libswscale/swscale.h"#include "include/libavutil/imgutils.h"#include "include/libswresample/swresample.h"}

 

extern "C"JNIEXPORT void JNICALLJava_com_mobcb_ffmpegdemo_Player_native_1play(JNIEnv *env, jobject thiz, jstring path_,                                              jobject surface) {    const char *path = env->GetStringUTFChars(path_, 0);    // 初始化网络    avformat_network_init();    // 初始化总上下文    AVFormatContext *avFormatContext = avformat_alloc_context();    // 1.打开url/或者本地文件路径    // 打开时的参数设置    AVDictionary *opt = NULL;    // 设置超时参数值为3000000,这里单位是微秒,3000000微秒=3s    av_dict_set(&opt, "timeout", "3000000", 0);    // 打开url,ret=0标识成功,否则失败    int ret = avformat_open_input(&avFormatContext, path, NULL, &opt);    if (ret) {        return;    }    // 发送指令,让avFormatContext获取stream信息    // 一段视频中一般有两个流,视频流,音频流,有的还包含字幕流,这里就是要获取这些流信息    avformat_find_stream_info(avFormatContext, &opt);    // 视频流的id    int stream_index;    for (int i = 0; i < avFormatContext->nb_streams; ++i) {        // codecpar 是stream的参数信息, 包括 宽,高, 类型等        if (avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {            // 是视频流            stream_index = i;            break;        }    }    // 获取流的参数信息    AVCodecParameters *avCodecParameters = avFormatContext->streams[stream_index]->codecpar;    // 根据流的参数信息取得解码器,相当于从map中以codec_id为key取value    AVCodec *avCodec = avcodec_find_decoder(avCodecParameters->codec_id);    // 根据解码器,创建解码器上下文    AVCodecContext *avCodecContext = avcodec_alloc_context3(avCodec);    // 将流的参数信息数据填充到解码器上下文    avcodec_parameters_to_context(avCodecContext, avCodecParameters);    // 使用解码器初始化解码器上下文    avcodec_open2(avCodecContext, avCodec, NULL);    // 声明包    AVPacket *avPacket = av_packet_alloc();    // 创建视频帧到RGBA的转换上下文    SwsContext *swsContext = sws_getContext(            avCodecContext->width,            avCodecContext->height,            avCodecContext->pix_fmt,            avCodecContext->width,            avCodecContext->height,            AV_PIX_FMT_RGBA,            SWS_BILINEAR, 0, 0, 0    );    // 创建渲染的窗体    ANativeWindow *aNativeWindow = ANativeWindow_fromSurface(env, surface);    // 窗体的缓冲区,绘制实际是上就是改变此缓冲区的内容    ANativeWindow_Buffer buffer;    // 设置缓冲区大小    ANativeWindow_setBuffersGeometry(aNativeWindow, avCodecContext->width, avCodecContext->height,                                     WINDOW_FORMAT_RGBA_8888);    // 以一个package为单位读取帧信息    while ((av_read_frame(avFormatContext, avPacket) >= 0)) {        // 向解码器输入pack 数据        avcodec_send_packet(avCodecContext, avPacket);        // 解码之后的帧数据        AVFrame *avFrame = av_frame_alloc();        // 读取帧数据信息        int ret1 = avcodec_receive_frame(avCodecContext, avFrame);        if (ret1 == AVERROR(EAGAIN)) {            //output is not available in this state - user must try            // *                         to send new input            // 需要再发送数据,队列里已没有数据可取            continue;        } else if (ret1 < 0) {            // 无数据, 退出循环            break;        }        uint8_t *dst_data[0];        int dst_linesize[0];        // 申请一块画布,并且数据和行数都用宽高和pix_fmt声明好        av_image_alloc(dst_data, dst_linesize, avCodecContext->width, avCodecContext->height,                       AV_PIX_FMT_RGBA, 1);        if (avPacket->stream_index == stream_index) {            // 是视频流            if (ret1 == 0) {                // 读取帧信息成功                // 绘制                // 锁缓冲区                ANativeWindow_lock(aNativeWindow, &buffer, NULL);                // 将frame转换成统一格式,格式为av_image_alloc申请的AV_PIX_FMT_RGBA                sws_scale(swsContext,                          avFrame->data,                          avFrame->linesize,                          0,                          avFrame->height,                          dst_data, dst_linesize);                // 将转换格式之后的frame 拷贝到ANativeWindow的缓冲区                // 输出到屏幕的输入源                uint8_t *srcFirstLine = dst_data[0];                // 输出到屏幕的输入源的行数                int srcStride = dst_linesize[0];                // 输出到屏幕时的跨度 是 缓冲区的跨度 * 4 , 因为一个pixel是4个字节,包含RGBA四个变量                int windowStride = buffer.stride * 4;                // 屏幕的首行地址                uint8_t *windowFirstLine = (uint8_t *) buffer.bits;                for (int i = 0; i < buffer.height; ++i) {                    // 逐行拷贝数据                    memcpy(windowFirstLine + i * windowStride, srcFirstLine + i * srcStride,                           windowStride);                }                // 解锁缓冲区                ANativeWindow_unlockAndPost(aNativeWindow);                usleep(1000 * 16);                // 释放帧数据                av_frame_free(&avFrame);            }        }        av_packet_unref(avPacket);        // 这个地方必须要释放, 否则内存会飙升,已copy的帧内存释放掉        av_freep(&dst_data[0]);    }    ANativeWindow_release(aNativeWindow);    av_freep(avPacket);    sws_freeContext(swsContext);    avcodec_close(avCodecContext);    avformat_free_context(avFormatContext);    // 下面三个不可写,否则会闪退,上面的代码会将下面三个指针都释放掉//    av_freep(avCodec);//    av_freep(swsContext);//    av_freep(avCodecContext);    // 播放完要释放,否则会内存泄漏    av_freep(avFormatContext);    // 释放字符串    env->ReleaseStringUTFChars(path_, path);}

 

player类完整代码

public class Player implements SurfaceHolder.Callback {    static {        System.loadLibrary("native-lib");    }    private SurfaceHolder surfaceHolder;    public void setSurfaceView(SurfaceView surfaceView) {        if (null != this.surfaceHolder) {            this.surfaceHolder.removeCallback(this);        }        this.surfaceHolder = surfaceView.getHolder();        this.surfaceHolder.addCallback(this);    }    public void play(String path) {        native_play(path, surfaceHolder.getSurface());    }    @Override    public void surfaceCreated(SurfaceHolder holder) {    }    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {        this.surfaceHolder = holder;    }    @Override    public void surfaceDestroyed(SurfaceHolder holder) {    }    public native void native_play(String path, Surface surface);}

 

更多相关文章

  1. Android(安卓)音视频深入 四 Android原生API录视频MP4,有缺陷,没有
  2. Android播放视频的三种方式示例
  3. 视频教程-Android(安卓)IM 即时通讯实战项目-Android
  4. 关于Android(安卓)VideoView seekTo不准确的问题
  5. Android中Gallery图片与视频选择冲突的原因
  6. Android中获取手机支持的硬件解码器类型以及对应的解码器名称
  7. android企业实战视频培训班
  8. Android将多个视频文件拼接为一个文件
  9. 8天快速掌握Android开发

随机推荐

  1. mysql关闭与删除bin-log日志详解
  2. 如何在postgres中更新时间戳字段的一部分
  3. SQL Server 2008 数据库镜像部署实例之三
  4. SQL Server表中某些字段含有水平制表符、
  5. Mysql中使用树的设计
  6. 数据库事务——还是这是一个规范化问题?
  7. fmdb 数据库升级1-----增加表字段
  8. CentOS 7下升级MySQL5.7.23的一个坑
  9. mysql根据查询结果创建表
  10. 报错pymysql.err.DataError: (1406, "Dat