上次已将ffmpeg的动态库编译出来了,并且使用了ffmpeg的转码功能,成功将mp4格式视频转化为yuv视频,这篇文章基于上次测试的demo,使用surfaceview显示解码完成的像素数据

布局设置和权限添加

布局

  1. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent" >
  5. <com.cj5785.ffmpegnativeplayer.view.MySurfaceView
  6. android:id="@+id/surface_view"
  7. android:layout_width="fill_parent"
  8. android:layout_height="fill_parent"/>
  9. <Button
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content"
  12. android:text="开始"
  13. android:onClick="mPlay" />
  14. </FrameLayout>

权限

  1. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  2. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  3. <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

编写自定义view和控制器

自定义View

  1. package com.cj5785.ffmpegnativeplayer.view;
  2. import android.content.Context;
  3. import android.graphics.PixelFormat;
  4. import android.util.AttributeSet;
  5. import android.view.SurfaceHolder;
  6. import android.view.SurfaceView;
  7. public class MySurfaceView extends SurfaceView {
  8. public MySurfaceView(Context context) {
  9. super(context);
  10. init();
  11. }
  12. public MySurfaceView(Context context, AttributeSet attrs) {
  13. super(context, attrs);
  14. init();
  15. }
  16. public MySurfaceView(Context context, AttributeSet attrs, int defStyle) {
  17. super(context, attrs, defStyle);
  18. init();
  19. }
  20. //初始化像素格式
  21. private void init() {
  22. SurfaceHolder holder = getHolder();
  23. holder.setFormat(PixelFormat.RGBA_8888);
  24. }
  25. }

控制器

  1. package com.cj5785.ffmpegnativeplayer;
  2. import android.view.Surface;
  3. public class NativePlayer {
  4. public native void render(String input, Surface surface);
  5. static {
  6. System.loadLibrary("avutil-54");
  7. System.loadLibrary("swresample-1");
  8. System.loadLibrary("avcodec-56");
  9. System.loadLibrary("avformat-56");
  10. System.loadLibrary("swscale-3");
  11. System.loadLibrary("postproc-53");
  12. System.loadLibrary("avfilter-5");
  13. System.loadLibrary("avdevice-56");
  14. System.loadLibrary("ffmpeg_native_player");
  15. }
  16. }

实现控制器native方法

  • 使用javah生成头文件,这里可能存在无法找到Surface签名的问题,这时候需要指定classpath路径

    javah -classpath E:\eclipse-adt\sdk\platforms\android-15\android.jar;. com.cj5785.ffmpegnativeplayer.NativePlayer

    格式说明:-classpath后面跟的是android.jar路径,最后接native方法类的全名

  • 新建jni文件夹,将头文件移至jni文件夹,添加本地依赖

  • 复制生成ffmpeg的include目录和so动态库到jni目录

  • 将之前的Android.mkApplication.mk复制到jni文件夹,并做适当修改

    Android.mk主要修改模块名,使其与控制器调用相统一

    Application.mk主要将APP_PLATFORM := android-8修改为APP_PLATFORM := android-9

    注意,此处如果不修改Application.mk将导致android/native_window_jni.h无法找到,同时,由于使用了这个头文件,需要在Android.mk配置-landroid

  • 使用开源库libyuv实现yuv转化为RGBA_8888

    下载开源库libyuv,下载地址libyuv下载地址

    将libyuv下的所有文件放入jni目录(NDK工程规范,必须存在jni目录)

    修改libyuv的Android.mk文件,将最后的include $(BUILD_STATIC_LIBRARY)改为include $(BUILD_SHARED_LIBRARY),这样就可以生成so动态库了

    还可以将LOCAL_MODULE := libyuv_static改为LOCAL_MODULE := libyuv,方便so管理

    在jni目录下执行ndk-build即可对libyuv进行编译

    编译生成的so动态库位于与jni目录同级的lib下

    将lib添加到工程jni目录下,为了便于管理,将jni的include目录进行重新分配,重新分配目录如下:(已将libyuv的include加入到工程,这里没有列出目录下包含的头文件)

  1. Android.mk
  2. Application.mk
  3. com_cj5785_ffmpegnativeplayer_NativePlayer.h
  4. ffmpeg_native_player.c

  5. └─include
  6. ├─ffmpeg
  7. libavcodec-56.so
  8. libavdevice-56.so
  9. libavfilter-5.so
  10. libavformat-56.so
  11. libavutil-54.so
  12. libpostproc-53.so
  13. libswresample-1.so
  14. libswscale-3.so

  15. ├─libavcodec
  16. ├─libavdevice
  17. ├─libavfilter
  18. ├─libavformat
  19. ├─libavutil
  20. ├─libpostproc
  21. ├─libswresample
  22. └─libswscale

  23. └─libyuv
  24. libyuv.h
  25. libyuv.so

  26. └─libyuv
  • 修改Android.mk,使其能找到so动态库
  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_MODULE := avcodec
  4. LOCAL_SRC_FILES := include/ffmpeg/libavcodec-56.so
  5. include $(PREBUILT_SHARED_LIBRARY)
  6. include $(CLEAR_VARS)
  7. LOCAL_MODULE := avdevice
  8. LOCAL_SRC_FILES := include/ffmpeg/libavdevice-56.so
  9. include $(PREBUILT_SHARED_LIBRARY)
  10. include $(CLEAR_VARS)
  11. LOCAL_MODULE := avfilter
  12. LOCAL_SRC_FILES := include/ffmpeg/libavfilter-5.so
  13. include $(PREBUILT_SHARED_LIBRARY)
  14. include $(CLEAR_VARS)
  15. LOCAL_MODULE := avformat
  16. LOCAL_SRC_FILES := include/ffmpeg/libavformat-56.so
  17. include $(PREBUILT_SHARED_LIBRARY)
  18. include $(CLEAR_VARS)
  19. LOCAL_MODULE := avutil
  20. LOCAL_SRC_FILES := include/ffmpeg/libavutil-54.so
  21. include $(PREBUILT_SHARED_LIBRARY)
  22. include $(CLEAR_VARS)
  23. LOCAL_MODULE := postproc
  24. LOCAL_SRC_FILES := include/ffmpeg/libpostproc-53.so
  25. include $(PREBUILT_SHARED_LIBRARY)
  26. include $(CLEAR_VARS)
  27. LOCAL_MODULE := swresample
  28. LOCAL_SRC_FILES := include/ffmpeg/libswresample-1.so
  29. include $(PREBUILT_SHARED_LIBRARY)
  30. include $(CLEAR_VARS)
  31. LOCAL_MODULE := swscale
  32. LOCAL_SRC_FILES := include/ffmpeg/libswscale-3.so
  33. include $(PREBUILT_SHARED_LIBRARY)
  34. include $(CLEAR_VARS)
  35. LOCAL_MODULE := yuv
  36. LOCAL_SRC_FILES := include/libyuv/libyuv.so
  37. include $(PREBUILT_SHARED_LIBRARY)
  38. include $(CLEAR_VARS)
  39. LOCAL_MODULE := ffmpeg_native_player
  40. LOCAL_SRC_FILES := ffmpeg_native_player.c
  41. LOCAL_C_INCLUDES += $(LOCAL_PATH)/include/ffmpeg
  42. LOCAL_C_INCLUDES += $(LOCAL_PATH)/include/libyuv
  43. LOCAL_LDLIBS := -llog -landroid
  44. LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil postproc swresample swscale yuv
  45. include $(BUILD_SHARED_LIBRARY)
  • 修改Application.mk,更改APP_PLATFORM,使其可以使用android/native_window_jni.handroid/native_window.h头文件
  1. APP_ABI := armeabi armeabi-v7a
  2. APP_PLATFORM := android-9
  • 实现jni头文件声明的函数
  1. #include <android/log.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <android/native_window.h>
  6. #include <android/native_window_jni.h>
  7. #include "com_cj5785_ffmpegnativeplayer_NativePlayer.h"
  8. //封装格式
  9. #include "include/ffmpeg/libavformat/avformat.h"
  10. //解码
  11. #include "include/ffmpeg/libavcodec/avcodec.h"
  12. //像素处理
  13. #include "include/ffmpeg/libswscale/swscale.h"
  14. //包含yuvlib头文件
  15. #include "include/libyuv/libyuv.h"
  16. #define LOGI(FORMAT,...) __android_log_print(4,"cj5785",FORMAT,##__VA_ARGS__);
  17. #define LOGE(FORMAT,...) __android_log_print(6,"cj5785",FORMAT,##__VA_ARGS__);
  18. JNIEXPORT void JNICALL Java_com_cj5785_ffmpegnativeplayer_NativePlayer_render
  19. (JNIEnv *env, jobject jobj, jstring jstr_path, jobject obj_surface)
  20. {
  21. LOGE("%s", "开始");
  22. const char *input_cstr = (*env)->GetStringUTFChars(env, jstr_path, NULL);
  23. //1.注册组件
  24. av_register_all();
  25. AVFormatContext *pFormatCtx = avformat_alloc_context();
  26. //2.打开视频文件
  27. if(avformat_open_input(&pFormatCtx, input_cstr, NULL, NULL) != 0)
  28. {
  29. LOGE("%s", "打开文件失败!");
  30. return;
  31. }
  32. //3.获取视频相关信息
  33. if(avformat_find_stream_info(pFormatCtx, NULL) < 0)
  34. {
  35. LOGE("%s", "获取视频信息失败!");
  36. return;
  37. }
  38. int i = 0;
  39. int video_stream_index = -1;
  40. for (i = 0; i < pFormatCtx->nb_streams; i++) {
  41. if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
  42. {
  43. video_stream_index = i;
  44. break;
  45. }
  46. }
  47. if (video_stream_index == -1)
  48. {
  49. LOGE("%s","找不到视频流\n");
  50. return;
  51. }
  52. //4.获取解码器
  53. AVCodecContext *pCodecCtx = pFormatCtx->streams[video_stream_index]->codec;
  54. AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
  55. if(pCodec == NULL)
  56. {
  57. LOGE("%s", "无法解码!");
  58. return;
  59. }
  60. //5.打开解码器
  61. if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
  62. {
  63. LOGE("%s", "解码失败!");
  64. return;
  65. }
  66. //6.以帧为单位读取视频文件
  67. AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
  68. AVFrame *pFrame = av_frame_alloc();
  69. AVFrame *pRGBFrame = av_frame_alloc();
  70. //native绘制
  71. //窗体设置
  72. ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, obj_surface);
  73. //缓冲区设置
  74. ANativeWindow_Buffer outBuffer;
  75. int len, got_frame, frame_count = 0;
  76. while(av_read_frame(pFormatCtx, packet) >= 0)
  77. {
  78. if(packet->stream_index == video_stream_index)
  79. {
  80. len = avcodec_decode_video2(pCodecCtx, pFrame, &got_frame, packet);
  81. if(len < 0)
  82. {
  83. LOGE("%s","解码错误!");
  84. return;
  85. }
  86. if(got_frame)
  87. {
  88. LOGI("解码第%d帧", frame_count++);
  89. //a.lock
  90. //设置缓冲区属性(宽,高,像素格式)
  91. ANativeWindow_setBuffersGeometry(nativeWindow, pCodecCtx->width, pCodecCtx->height, WINDOW_FORMAT_RGBA_8888);
  92. ANativeWindow_lock(nativeWindow, &outBuffer, NULL);
  93. //b.fix buffer
  94. //设置RGB的缓冲区以及属性(像素格式,宽高),RGB缓冲区和outBuffer.bits是同一块内存
  95. avpicture_fill((AVPicture *)pRGBFrame, outBuffer.bits, AV_PIX_FMT_RGBA, pCodecCtx->width, pCodecCtx->height);
  96. //YUV转化为RGB
  97. I420ToARGB(pFrame->data[0], pFrame->linesize[0],
  98. pFrame->data[2], pFrame->linesize[2],
  99. pFrame->data[1], pFrame->linesize[1],
  100. pRGBFrame->data[0], pRGBFrame->linesize[0],
  101. pCodecCtx->width, pCodecCtx->height);
  102. //c.unlock
  103. ANativeWindow_unlockAndPost(nativeWindow);
  104. usleep(16 * 1000);
  105. }
  106. }
  107. av_free_packet(packet);
  108. }
  109. ANativeWindow_release(nativeWindow);
  110. av_frame_free(&pFrame);
  111. av_frame_free(&pRGBFrame);
  112. avcodec_close(pCodecCtx);
  113. avformat_free_context(pFormatCtx);
  114. (*env)->ReleaseStringUTFChars(env, jstr_path, input_cstr);
  115. }

调用native,使其能够播放

  1. package com.cj5785.ffmpegnativeplayer;
  2. import java.io.File;
  3. import com.cj5785.ffmpegnativeplayer.view.MySurfaceView;
  4. import android.app.Activity;
  5. import android.os.Bundle;
  6. import android.os.Environment;
  7. import android.view.Surface;
  8. import android.view.View;
  9. public class MainActivity extends Activity {
  10. private NativePlayer player;
  11. private MySurfaceView mySurfaceView;
  12. @Override
  13. protected void onCreate(Bundle savedInstanceState) {
  14. super.onCreate(savedInstanceState);
  15. setContentView(R.layout.activity_main);
  16. mySurfaceView = (MySurfaceView) findViewById(R.id.surface_view);
  17. player = new NativePlayer();
  18. }
  19. public void mPlay(View view) {
  20. String input = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + "oneplus.mp4";
  21. Surface surface = mySurfaceView.getHolder().getSurface();
  22. player.render(input, surface);
  23. }
  24. }

至此,已经可以编译生成apk了,在手机上测试也没有问题

更改布局和主活动,使其可以播放多个测试视频

MainActivity.java

  1. package com.cj5785.ffmpegnativeplayer;
  2. import java.io.File;
  3. import com.cj5785.ffmpegnativeplayer.view.MySurfaceView;
  4. import android.app.Activity;
  5. import android.os.Bundle;
  6. import android.os.Environment;
  7. import android.view.Surface;
  8. import android.view.View;
  9. import android.widget.ArrayAdapter;
  10. import android.widget.Spinner;
  11. public class MainActivity extends Activity {
  12. private NativePlayer player;
  13. private MySurfaceView mySurfaceView;
  14. private Spinner sp_video;
  15. @Override
  16. protected void onCreate(Bundle savedInstanceState) {
  17. super.onCreate(savedInstanceState);
  18. setContentView(R.layout.activity_main);
  19. mySurfaceView = (MySurfaceView) findViewById(R.id.surface_view);
  20. sp_video = (Spinner)findViewById(R.id.sp_video);
  21. player = new NativePlayer();
  22. //视频列表
  23. String[] videoArray = getResources().getStringArray(R.array.video_list);
  24. ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
  25. android.R.layout.simple_list_item_1, android.R.id.text1,videoArray);
  26. sp_video.setAdapter(adapter);
  27. }
  28. public void mPlay(View view) {
  29. String filename = sp_video.getSelectedItem().toString();
  30. String input = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + filename;
  31. Surface surface = mySurfaceView.getHolder().getSurface();
  32. player.render(input, surface);
  33. }
  34. }

activity_main.xml

  1. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent" >
  5. <com.cj5785.ffmpegnativeplayer.view.MySurfaceView
  6. android:id="@+id/surface_view"
  7. android:layout_width="fill_parent"
  8. android:layout_height="fill_parent"/>
  9. <LinearLayout
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content"
  12. android:orientation="horizontal">
  13. <Spinner
  14. android:id="@+id/sp_video"
  15. android:layout_width="wrap_content"
  16. android:layout_height="wrap_content"/>
  17. <Button
  18. android:layout_width="wrap_content"
  19. android:layout_height="wrap_content"
  20. android:text="开始"
  21. android:onClick="mPlay" />
  22. </LinearLayout>
  23. </FrameLayout>

string.xml中添加数组值

  1. <string-array name="video_list">
  2. <item>naxienian.mp4</item>
  3. <item>cuc_ieschool.mkv</item>
  4. <item>sintel.wmv</item>
  5. <item>Nocturne.m4a</item>
  6. </string-array>

需要注意的问题

在native的实现过程中,I420ToARGB()方法在调用的时候,UV的位置是颠倒的,需要对调UV的位置

在这个示例程序中,旨在说明native是怎么绘制的,其代码存在严重不足,比如在主线程中绘制界面

部分视频会出现花屏现象,这个问题在后面多线程解码的时候会解决

ffmpeg学习笔记-native原生绘制的更多相关文章

  1. ‎Cocos2d-x 学习笔记(25) 渲染 绘制 Render

    [Cocos2d-x]学习笔记目录 本文链接:https://www.cnblogs.com/deepcho/p/cocos2dx-render.html 1. 从程序入口到渲染方法 一个Cocos2 ...

  2. ffmpeg学习笔记-多线程音视频解码

    之前的视频解码仍然存在问题,那就是是在主线程中去完成解码的,会造成线程阻塞,这里将其改为多线程解码,使其主线程不被阻塞 前面介绍了音视频的主线程解码,那样会阻塞主线程,在前面学习了多线程以后,就可以对 ...

  3. ffmpeg学习笔记

           对于每一个刚開始学习的人,刚開始接触ffmpeg时,想必会有三个问题最为关心,即ffmpeg是什么?能干什么?怎么開始学习?本人前段时间開始接触ffmpeg,在刚開始学习过程中.这三个问 ...

  4. 【canvas学习笔记二】绘制图形

    上一篇我们已经讲述了canvas的基本用法,学会了构建canvas环境.现在我们就来学习绘制一些基本图形. 坐标 canvas的坐标原点在左上角,从左到右X轴坐标增加,从上到下Y轴坐标增加.坐标的一个 ...

  5. ffmpeg学习笔记-音频播放

    前文讲到音频解码,将音频解码,并且输入到PCM文件,这里将音频通过AudioTrack直接输出 音频播放说明 在Android中自带的MediaPlayer也可以对音频播放,但其支持格式太少 使用ff ...

  6. ffmpeg学习笔记-音频解码

    在之前的文章已经初步对视频解码有个初步的认识了,接下来来看一看音频解码 音频解码步骤 音频解码与视频解码一样,有者固有的步骤,只要按照步骤来,就能顺利的解码音频 以上是ffmpeg的解码流程图,可以看 ...

  7. ffmpeg学习笔记-Linux下编译Android动态库

    Android平台要使用ffmpeg就需要编译生成动态库,这里采用Ubuntu编译Android动态库 文件准备 要编译生成Android需要以下文件 NDK ffmpeg源代码 NDK下载 NDK可 ...

  8. ffmpeg学习笔记-初识ffmpeg

    ffmpeg用来对音视频进行处理,那么在使用ffmpeg前就需要ffmpeg有一个大概的了解,这里使用雷神的ppt素材进行整理,以便于复习 音视频基础知识 视频播放器的原理 播放视频的流程大致如下: ...

  9. 【canvas学习笔记四】绘制文字

    本节我们来学习如何绘制文字. 绘制文字有两个主要的方法: fillText(text, x, y [, maxWidth]) 在x, y位置填充文字text,有一个可选参数maxWidth设置最大绘制 ...

随机推荐

  1. Codeforces Round #551 (Div. 2) F. Serval and Bonus Problem (DP/FFT)

    yyb大佬的博客 这线段期望好神啊... 还有O(nlogn)FFTO(nlogn)FFTO(nlogn)FFT的做法 Freopen大佬的博客 本蒟蒻只会O(n2)O(n^2)O(n2) CODE ...

  2. Jquery实践--精读开篇

    JQuery实践,我已经看了最少三遍了.这里面的很多方法对我的工作很有帮助.但由于不是真的进行前端开发,所以JQuery中的很多功能也没有用到.所以隔一段时间想起,就会发觉,一些东西又忘记了.所以趁这 ...

  3. leetcode解题报告(9):Implement strStr()

    描述 Implement strStr(). Returns the index of the first occurrence of needle in haystack, or -1 if nee ...

  4. 基于ARM的SoC设计入门[转]

    原文:基于ARM的SoC设计入门 我们跳过所有对ARM介绍性的描述,直接进入工程师们最关心的问题.要设计一个基于ARM的SoC,我们首先要了解一个基于ARM的SoC的结构.图1是一个典型的SoC的结构 ...

  5. C语言学习笔记7-字符串

    本系列文章由jadeshu编写,转载请注明出处.http://blog.csdn.net/jadeshu/article/details/50752405 作者:jadeshu   邮箱: jades ...

  6. 字典-Python基础前传(9)

    (一)Python中为什么要有字典 jacky说科学存在的逻辑只有两个: 1.解释问题 2.解决问题 我们明白了科学的逻辑,我们理解任何的知识和技能,都是很简单的 之前jacky跟大家说list因为太 ...

  7. ImportError: No module named rospy

    Traceback (most recent call last): File "manage.py", line 4, in <module> import rosp ...

  8. intel官方的手册

    最近在学习汇编语言,需要用到intel的手册,无论是csdn还是其他的,都要下载币,还不便宜,也很老的资料了. 直接到这个地址:https://software.intel.com/en-us/art ...

  9. maven的pom报错web.xml is missing and <failOnMissingWebXml> is set to true

    错误信息:web.xml is missing and <failOnMissingWebXml> is set to true 解决办法:https://blog.csdn.net/si ...

  10. 使用log4j使某些java类的日志信息输出到指定日志文件中

    Log4j 是 Apache 的一个开放源代码项目,通过使用 Log4j,我们可以控制日志信息输送的目的地是控制台.文件.GUI 组件.甚至是套接口服务器.NT 的事件记录器.UNIX Syslog ...