ffmpeg实战教程(七)Android CMake avi解码后SurfaceView显示

本篇我们实现Android平台解码avi并用SurfaceView播放。

先上图看效果:

思路: 
1.把封装好的视频数据解码为YUV 
2.YUV数据转化为RGB。 
3.一帧一帧的传给SurfaceView显示出来

PS: 
其实YUV数据可直接在SurfaceView显示,在研究Android系统多媒体框架的stagefright视频显示时发现,根本找不到omx解码后的yuv是怎么转换成RGB的代码,yuv数据在render之后就找不到去向了,可画面确确实实的显示出来了。

稍微看一下AsomePlayer的代码,不难发现,视频的每一帧是通过调用了SoftwareRenderer来渲染显示的、这是一个很大的突破,以后可以直接丢yuv数据到surface显示,无需耗时耗效率的yuv转RGB了,这部分知识点会在以后的文章中实现本篇不涉及。

下面看具体实现: 
1.首先我们拷贝一份上一章的代码命名为ffmpegandroidplayer 
2.根据本章需要修改代码

界面上一个SurfaceView

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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=".MainActivity"tools:showIn="@layout/activity_main"><SurfaceViewandroid:id="@+id/surface_view"android:layout_width="match_parent"android:layout_height="match_parent" />
</RelativeLayout>

一个native 的play方法负责把Surface传给底层

   @Overridepublic void surfaceCreated(SurfaceHolder holder) {new Thread(new Runnable() {@Overridepublic void run() {play(surfaceHolder.getSurface());}}).start();}
...public native int play(Object surface);

3.修改CMakeLists.txt

target_link_libraries( native-lib log android avutil-55 swresample-2 avcodec-57 avfilter-6 swscale-4 avdevice-57 avformat-57${log-lib} )

4.实现play方法。 
写这里方法的时候报了一个错,记录一下。

java.lang.UnsatisfiedLinkError: No implementation found for int com.ws.ffmpegandroidplayer.MainActivity.play

解决思路:


1、JNIEnv *env参数的使用
所有JNI接口的第一个参数是JNIEnv *env, 在C中,使用方法是
(*env)->NewStringUTF(env, "Hello from JNI!");
但在C++中,其调用方法是
env->NewStringUTF("Hello from JNI!");
为什么有这种区别呢,看看jni.h中关于JNIEnv的定义就可以知道了:
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
#else
typedef const struct JNINativeInterface* JNIEnv;
#endif
可以看到,对于C和C++,定义有所不同,主要原因是C不支持类,所以采用了一种变通的方法。2、接口找不到
在Java中调用JNI接口时,出现异常,察看日志,发现有如下错误:
WARN/dalvikvm(422): No implementation found for native Lcom/whty/wcity/HelixPlayer;.setDllPath (Ljava/lang/String;)V
检查了几遍代码,Cpp中确实定义了这个接口,而且仔细对照了Java的包名、类名,确实没有错误,那为什么会出现这种问题呢。后来突然想到,JNI接口 都是以C的方式定义的,现在使用C++实现,函数定义前是否需要加上extern "C"呢?为此定义了一个头文件,在CPP文件中include该头文件,头文件加上如下代码片断:
#ifdef __cplusplus
extern "C" {
#endif
#endif
...
#ifdef __cplusplus
}

Java_com_ws_ffmpegandroidplayer_MainActivity_play源码如下

#include <jni.h>
#include <android/log.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
//};#define  LOG_TAG    "ffmpegandroidplayer"
#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)JNIEXPORT jint JNICALL
Java_com_ws_ffmpegandroidplayer_MainActivity_play(JNIEnv *env, jclass clazz, jobject surface) {LOGD("play");// sd卡中的视频文件地址,可自行修改或者通过jni传入//char *file_name = "/storage/emulated/0/ws2.mp4";char *file_name = "/storage/emulated/0/video.avi";av_register_all();AVFormatContext *pFormatCtx = avformat_alloc_context();// Open video fileif (avformat_open_input(&pFormatCtx, file_name, NULL, NULL) != 0) {LOGD("Couldn't open file:%s\n", file_name);return -1; // Couldn't open file}// Retrieve stream informationif (avformat_find_stream_info(pFormatCtx, NULL) < 0) {LOGD("Couldn't find stream information.");return -1;}// Find the first video streamint videoStream = -1, i;for (i = 0; i < pFormatCtx->nb_streams; i++) {if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO&& videoStream < 0) {videoStream = i;}}if (videoStream == -1) {LOGD("Didn't find a video stream.");return -1; // Didn't find a video stream}// Get a pointer to the codec context for the video streamAVCodecContext *pCodecCtx = pFormatCtx->streams[videoStream]->codec;// Find the decoder for the video streamAVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);if (pCodec == NULL) {LOGD("Codec not found.");return -1; // Codec not found}if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {LOGD("Could not open codec.");return -1; // Could not open codec}// 获取native windowANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);// 获取视频宽高int videoWidth = pCodecCtx->width;int videoHeight = pCodecCtx->height;// 设置native window的buffer大小,可自动拉伸ANativeWindow_setBuffersGeometry(nativeWindow, videoWidth, videoHeight,WINDOW_FORMAT_RGBA_8888);ANativeWindow_Buffer windowBuffer;if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {LOGD("Could not open codec.");return -1; // Could not open codec}// Allocate video frameAVFrame *pFrame = av_frame_alloc();// 用于渲染AVFrame *pFrameRGBA = av_frame_alloc();if (pFrameRGBA == NULL || pFrame == NULL) {LOGD("Could not allocate video frame.");return -1;}// Determine required buffer size and allocate buffer// buffer中数据就是用于渲染的,且格式为RGBAint numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGBA, pCodecCtx->width, pCodecCtx->height,1);uint8_t *buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));av_image_fill_arrays(pFrameRGBA->data, pFrameRGBA->linesize, buffer, AV_PIX_FMT_RGBA,pCodecCtx->width, pCodecCtx->height, 1);// 由于解码出来的帧格式不是RGBA的,在渲染之前需要进行格式转换struct SwsContext *sws_ctx = sws_getContext(pCodecCtx->width,pCodecCtx->height,pCodecCtx->pix_fmt,pCodecCtx->width,pCodecCtx->height,AV_PIX_FMT_RGBA,SWS_BILINEAR,NULL,NULL,NULL);int frameFinished;AVPacket packet;while (av_read_frame(pFormatCtx, &packet) >= 0) {// Is this a packet from the video stream?if (packet.stream_index == videoStream) {// Decode video frameavcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);// 并不是decode一次就可解码出一帧if (frameFinished) {// lock native window bufferANativeWindow_lock(nativeWindow, &windowBuffer, 0);// 格式转换sws_scale(sws_ctx, (uint8_t const *const *) pFrame->data,pFrame->linesize, 0, pCodecCtx->height,pFrameRGBA->data, pFrameRGBA->linesize);// 获取strideuint8_t *dst = (uint8_t *) windowBuffer.bits;int dstStride = windowBuffer.stride * 4;uint8_t *src = (pFrameRGBA->data[0]);int srcStride = pFrameRGBA->linesize[0];// 由于window的stride和帧的stride不同,因此需要逐行复制int h;for (h = 0; h < videoHeight; h++) {memcpy(dst + h * dstStride, src + h * srcStride, srcStride);}ANativeWindow_unlockAndPost(nativeWindow);}}av_packet_unref(&packet);}av_free(buffer);av_free(pFrameRGBA);// Free the YUV frameav_free(pFrame);// Close the codecsavcodec_close(pCodecCtx);// Close the video fileavformat_close_input(&pFormatCtx);return 0;
}
}

demo:https://github.com/WangShuo1143368701/FFmpegAndroid/tree/master/ffmpegandroidplayer

ffmpeg实战教程(七)Android CMake avi解码后SurfaceView显示相关推荐

  1. ffmpeg实战教程(八)Android平台下AVfilter 实现水印,滤镜等特效功能

    ffmpeg实战教程(八)Android平台下AVfilter 实现水印,滤镜等特效功能 ffmpeg实战教程(七)Android CMake avi解码后SurfaceView显示 本篇我们在此基础 ...

  2. ffmpeg实战教程(六)Android CMake实现解码(MP4转YUV)

    ffmpeg实战教程(六)Android CMake实现解码(MP4转YUV) 我们将使用最新版: 最新版ffmpeg ffmpeg3.3  新版Android studio Android stud ...

  3. ffmpeg实战教程(三)音频PCM采样为AAC,视频YUV编码为H264/HEVC

    ffmpeg实战教程(三)音频PCM采样为AAC,视频YUV编码为H264/HEVC https://blog.csdn.net/King1425/article/details/71180330 音 ...

  4. ffmpeg实战教程(二)用SDL播放YUV,并结合ffmpeg实现简易播放器

    ffmpeg实战教程(二)用SDL播放YUV,并结合ffmpeg实现简易播放器 https://blog.csdn.net/King1425/article/details/71171142 我们先实 ...

  5. ffmpeg实战教程(十一)手把手教你实现直播功能,不依赖第三方SDK

    直播,2016最火的技术之一了,更多的关于直播的知识:http://blog.csdn.net/king1425/article/details/72489272 -这篇我们就不依赖任何集成好的SDK ...

  6. ffmpeg实战教程(一)Mp4,mkv等格式解码为h264和yuv数据

    FFmpeg有非常强大的功能包括视频采集功能.视频格式转换.视频抓图.给视频加水印等.而网上对这些功能的使用大多是基于命令行的.这不利于我们深入学习定制化ffmpeg,今后我将写一系列的用代码实现这些 ...

  7. ffmpeg实战教程(十三)iJKPlayer源码简析

    要使用封装优化ijk就必须先了解ffmpeg,然后看ijk对ffmpeg的C层封装! 这是我看ijk源码时候的笔记,比较散乱.不喜勿喷~ ijk源码简析: 1.ijkplayer_jni.c 封装的播 ...

  8. ffmpeg实战教程(四)格式转换如MP4转MKV等

    知识延伸: I,P,B帧和PTS,DTS的关系 基本概念: I frame :帧内编码帧 又称intra picture,I 帧通常是每个 GOP(MPEG 所使用的一种视频压缩技术)的第一个帧,经过 ...

  9. ffmpeg实战教程(十)ffmpeg/camera实现最近很火的视频壁纸,相机壁纸

    本篇实现一个有意思的玩意儿,视频壁纸,相机壁纸 这玩意好像现在还都是国外版本,哈哈 先上图: 视频壁纸 相机壁纸 1.动态壁纸制作的知识: 每一个动态壁纸都继承自WallpaperService,其中 ...

最新文章

  1. 使用sbt编译打包,spark-submit命令提交的详细步骤
  2. mvc5控制器修改html,关于jquery:如何通过对控制器的ajax调用在MVC5中呈现局部视图并返回HTML...
  3. android AIDL示例代码(mark下)
  4. 南大cssci期刊目录_最新版CSSCI来源期刊目录(2019-2020)及增减变化!【南大核心】...
  5. 基于JAVA+SpringMVC+Mybatis+MYSQL的论坛管理系统
  6. mysql hang分析_mysql hang
  7. javascript中级--运动二
  8. Windows 7安装到虚拟磁盘VHD文件中
  9. smtplib,发送邮件时的bug
  10. 智能手机之新手篇[转]
  11. [C/C++]_[0基础]_[static_cast,reinterpret_cast,dynimic_cast的使用场景和差别]
  12. RG Magic Bullet
  13. python 爬取视频ts文件_python爬取视频网站中video标签的m3u8文件与ts文件
  14. 搜狗高速浏览器主页被篡改怎么办 搜狗浏览器中恢复被篡改主页的方法
  15. 老王的JAVA基础课:第2课 JDK安装和环境变量配置
  16. Appium+python实现App自动化登录
  17. 【Android基础知识】选项菜单、上下文菜单、子菜单的使用
  18. charles把抖音数据保存到本地处理
  19. int和long类型取值范围。 基本数据类型 byte , short , char ,int , long , float ,double,boolean类型取值范围
  20. 一文告诉你车载测试的秘密

热门文章

  1. spring cloud bus_Spring Cloud学习笔记--消息总线(Bus)
  2. QT5开发及实例学习之三字符串类
  3. Spark Scala当中reduceByKey的用法
  4. ERROR 2003 (HY000): Can't connect to MySQL server on 'localhost' (10061)解决办法
  5. (学习笔记)Oracle约束
  6. Kali安装虚拟机遇到的问题
  7. 华为SDN+VxLAN学习小记
  8. Java基础知识强化68:基本类型包装类之Character概述和Character常见方法
  9. 【我所認知的BIOS】—gt;ADU.exe
  10. Ubuntu下启动/重启/停止apache服务器