Android使用FFmpeg动态库播放视频
16lz
2021-01-24
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);}
更多相关文章
- Android(安卓)音视频深入 四 Android原生API录视频MP4,有缺陷,没有
- Android播放视频的三种方式示例
- 视频教程-Android(安卓)IM 即时通讯实战项目-Android
- 关于Android(安卓)VideoView seekTo不准确的问题
- Android中Gallery图片与视频选择冲突的原因
- Android中获取手机支持的硬件解码器类型以及对应的解码器名称
- android企业实战视频培训班
- Android将多个视频文件拼接为一个文件
- 8天快速掌握Android开发