ffmpeg学习笔记-native原生绘制

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

布局设置和权限添加

布局

<FrameLayout 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" ><com.cj5785.ffmpegnativeplayer.view.MySurfaceViewandroid:id="@+id/surface_view"android:layout_width="fill_parent"android:layout_height="fill_parent"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="开始"android:onClick="mPlay" /></FrameLayout>

权限

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

编写自定义view和控制器

自定义View

package com.cj5785.ffmpegnativeplayer.view;import android.content.Context;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;public class MySurfaceView extends SurfaceView {public MySurfaceView(Context context) {super(context);init();}public MySurfaceView(Context context, AttributeSet attrs) {super(context, attrs);init();}public MySurfaceView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init();}//初始化像素格式private void init() {SurfaceHolder holder = getHolder();holder.setFormat(PixelFormat.RGBA_8888);}
}

控制器

package com.cj5785.ffmpegnativeplayer;import android.view.Surface;public class NativePlayer {public native void render(String input, Surface surface);static {System.loadLibrary("avutil-54");System.loadLibrary("swresample-1");System.loadLibrary("avcodec-56");System.loadLibrary("avformat-56");System.loadLibrary("swscale-3");System.loadLibrary("postproc-53");System.loadLibrary("avfilter-5");System.loadLibrary("avdevice-56");System.loadLibrary("ffmpeg_native_player");}}

实现控制器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加入到工程,这里没有列出目录下包含的头文件)

│  Android.mk
│  Application.mk
│  com_cj5785_ffmpegnativeplayer_NativePlayer.h
│  ffmpeg_native_player.c
│  
└─include├─ffmpeg│  │  libavcodec-56.so│  │  libavdevice-56.so│  │  libavfilter-5.so│  │  libavformat-56.so│  │  libavutil-54.so│  │  libpostproc-53.so│  │  libswresample-1.so│  │  libswscale-3.so│  │  │  ├─libavcodec│  ├─libavdevice│  ├─libavfilter│  ├─libavformat│  ├─libavutil│  ├─libpostproc│  ├─libswresample│  └─libswscale│          └─libyuv│  libyuv.h│  libyuv.so│  └─libyuv
  • 修改Android.mk,使其能找到so动态库
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)
LOCAL_MODULE := avcodec
LOCAL_SRC_FILES := include/ffmpeg/libavcodec-56.so
include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)
LOCAL_MODULE := avdevice
LOCAL_SRC_FILES := include/ffmpeg/libavdevice-56.so
include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)
LOCAL_MODULE := avfilter
LOCAL_SRC_FILES := include/ffmpeg/libavfilter-5.so
include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)
LOCAL_MODULE := avformat
LOCAL_SRC_FILES := include/ffmpeg/libavformat-56.so
include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)
LOCAL_MODULE := avutil
LOCAL_SRC_FILES := include/ffmpeg/libavutil-54.so
include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)
LOCAL_MODULE := postproc
LOCAL_SRC_FILES := include/ffmpeg/libpostproc-53.so
include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)
LOCAL_MODULE := swresample
LOCAL_SRC_FILES := include/ffmpeg/libswresample-1.so
include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)
LOCAL_MODULE := swscale
LOCAL_SRC_FILES := include/ffmpeg/libswscale-3.so
include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)
LOCAL_MODULE := yuv
LOCAL_SRC_FILES := include/libyuv/libyuv.so
include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)
LOCAL_MODULE := ffmpeg_native_player
LOCAL_SRC_FILES := ffmpeg_native_player.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include/ffmpeg
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include/libyuv
LOCAL_LDLIBS := -llog -landroid
LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil postproc swresample swscale yuv
include $(BUILD_SHARED_LIBRARY)
  • 修改Application.mk,更改APP_PLATFORM,使其可以使用android/native_window_jni.handroid/native_window.h头文件
APP_ABI := armeabi armeabi-v7a
APP_PLATFORM := android-9
  • 实现jni头文件声明的函数
#include <android/log.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>#include "com_cj5785_ffmpegnativeplayer_NativePlayer.h"//封装格式
#include "include/ffmpeg/libavformat/avformat.h"
//解码
#include "include/ffmpeg/libavcodec/avcodec.h"
//像素处理
#include "include/ffmpeg/libswscale/swscale.h"
//包含yuvlib头文件
#include "include/libyuv/libyuv.h"#define LOGI(FORMAT,...) __android_log_print(4,"cj5785",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(6,"cj5785",FORMAT,##__VA_ARGS__);JNIEXPORT void JNICALL Java_com_cj5785_ffmpegnativeplayer_NativePlayer_render(JNIEnv *env, jobject jobj, jstring jstr_path, jobject obj_surface)
{LOGE("%s", "开始");const char *input_cstr = (*env)->GetStringUTFChars(env, jstr_path, NULL);//1.注册组件av_register_all();AVFormatContext *pFormatCtx = avformat_alloc_context();//2.打开视频文件if(avformat_open_input(&pFormatCtx, input_cstr, NULL, NULL) != 0){LOGE("%s", "打开文件失败!");return;}//3.获取视频相关信息if(avformat_find_stream_info(pFormatCtx, NULL) < 0){LOGE("%s", "获取视频信息失败!");return;}int i = 0;int video_stream_index = -1;for (i = 0; i < pFormatCtx->nb_streams; i++) {if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){video_stream_index = i;break;}}if (video_stream_index == -1){LOGE("%s","找不到视频流\n");return;}//4.获取解码器AVCodecContext *pCodecCtx = pFormatCtx->streams[video_stream_index]->codec;AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);if(pCodec == NULL){LOGE("%s", "无法解码!");return;}//5.打开解码器if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0){LOGE("%s", "解码失败!");return;}//6.以帧为单位读取视频文件AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));AVFrame *pFrame = av_frame_alloc();AVFrame *pRGBFrame = av_frame_alloc();//native绘制//窗体设置ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, obj_surface);//缓冲区设置ANativeWindow_Buffer outBuffer;int len, got_frame, frame_count = 0;while(av_read_frame(pFormatCtx, packet) >= 0){if(packet->stream_index == video_stream_index){len = avcodec_decode_video2(pCodecCtx, pFrame, &got_frame, packet);if(len < 0){LOGE("%s","解码错误!");return;}if(got_frame){LOGI("解码第%d帧", frame_count++);//a.lock//设置缓冲区属性(宽,高,像素格式)ANativeWindow_setBuffersGeometry(nativeWindow, pCodecCtx->width, pCodecCtx->height, WINDOW_FORMAT_RGBA_8888);ANativeWindow_lock(nativeWindow, &outBuffer, NULL);//b.fix buffer//设置RGB的缓冲区以及属性(像素格式,宽高),RGB缓冲区和outBuffer.bits是同一块内存avpicture_fill((AVPicture *)pRGBFrame, outBuffer.bits, AV_PIX_FMT_RGBA, pCodecCtx->width, pCodecCtx->height);//YUV转化为RGBI420ToARGB(pFrame->data[0], pFrame->linesize[0],pFrame->data[2], pFrame->linesize[2],pFrame->data[1], pFrame->linesize[1],pRGBFrame->data[0], pRGBFrame->linesize[0],pCodecCtx->width, pCodecCtx->height);//c.unlockANativeWindow_unlockAndPost(nativeWindow);usleep(16 * 1000);}}av_free_packet(packet);}ANativeWindow_release(nativeWindow);av_frame_free(&pFrame);av_frame_free(&pRGBFrame);avcodec_close(pCodecCtx);avformat_free_context(pFormatCtx);(*env)->ReleaseStringUTFChars(env, jstr_path, input_cstr);
}

调用native,使其能够播放

package com.cj5785.ffmpegnativeplayer;import java.io.File;import com.cj5785.ffmpegnativeplayer.view.MySurfaceView;import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.Surface;
import android.view.View;public class MainActivity extends Activity {private NativePlayer player;private MySurfaceView mySurfaceView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mySurfaceView = (MySurfaceView) findViewById(R.id.surface_view);player = new NativePlayer();}public void mPlay(View view) {String input = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + "oneplus.mp4";Surface surface = mySurfaceView.getHolder().getSurface();player.render(input, surface);}
}

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

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

MainActivity.java

package com.cj5785.ffmpegnativeplayer;import java.io.File;import com.cj5785.ffmpegnativeplayer.view.MySurfaceView;import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.Surface;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Spinner;public class MainActivity extends Activity {private NativePlayer player;private MySurfaceView mySurfaceView;private Spinner sp_video;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mySurfaceView = (MySurfaceView) findViewById(R.id.surface_view);sp_video = (Spinner)findViewById(R.id.sp_video);player = new NativePlayer();//视频列表String[] videoArray = getResources().getStringArray(R.array.video_list);ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1,videoArray);sp_video.setAdapter(adapter);}public void mPlay(View view) {String filename = sp_video.getSelectedItem().toString();String input = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + filename;Surface surface = mySurfaceView.getHolder().getSurface();player.render(input, surface);}
}

activity_main.xml

<FrameLayout 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" ><com.cj5785.ffmpegnativeplayer.view.MySurfaceViewandroid:id="@+id/surface_view"android:layout_width="fill_parent"android:layout_height="fill_parent"/><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="horizontal"><Spinner android:id="@+id/sp_video"android:layout_width="wrap_content"android:layout_height="wrap_content"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="开始"android:onClick="mPlay" /></LinearLayout></FrameLayout>

string.xml中添加数组值

<string-array name="video_list"><item>naxienian.mp4</item><item>cuc_ieschool.mkv</item><item>sintel.wmv</item><item>Nocturne.m4a</item>
</string-array>

需要注意的问题

在native的实现过程中,I420ToARGB()方法在调用的时候,UV的位置是颠倒的,需要对调UV的位置
在这个示例程序中,旨在说明native是怎么绘制的,其代码存在严重不足,比如在主线程中绘制界面
部分视频会出现花屏现象,这个问题在后面多线程解码的时候会解决

转载于:https://www.cnblogs.com/cj5785/p/10664660.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/264311.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CoreData一些基本概念

Core Data涉及到的几个主要的概念可以对应数据库来理解&#xff1a;NSManagedObjectContext&#xff08;托管对象上下文&#xff09;&#xff1a;数据库NSEntityDescription&#xff08;实体描述&#xff09;&#xff1a;表NSFetchRequest&#xff08;请求&#xff09;&#xf…

BDC技术(一个例子)

BDC技术 BDC&#xff08;Batch Data Conversion&#xff09;&#xff1a;在SAP系统里&#xff0c;由于某种原因&#xff0c;可能需要重复输入数据&#xff0c;&#xff08;数据不同&#xff0c;但是操作是相同的&#xff0c;典型的情形就是切换系统的时候&#xff0c;旧系统的数…

华为android怎样隐藏软件,华为怎么打开隐藏应用功能

隐藏应用是没有密码的&#xff0c;隐藏应用的方法&#xff1a;在主桌面两指分开&#xff0c;进入隐藏应用界面&#xff0c;点击(添加)&#xff0c;然后点击要隐藏的应用&#xff0c;再点击确定即可。应用锁有密码&#xff0c;是机主设定的&#xff0c;如果设置了指纹访问应用&a…

事务的传播性和隔离级别

事务的传播性&#xff1a;1、PROPOGATION_REQUIRES --需要在一个事务中执行2、PROPOGATION_SUPPOTS --不需要在一个事务中执行&#xff0c;如果有事务&#xff0c;也可以执行3 PROPOGATION_NOT_SUPPORTED --不支持在一个事务中执行&#xff0c;如果在…

Oracle建立表空间和用户

Oracle建立表空间和用户 建立表空间和用户的步骤&#xff1a; 用户 建立&#xff1a;create user username identified by "password"; 授权&#xff1a;grant create session to username;grant create table to username;grant create tablespace…

VC系统扫雷游戏外挂源代码程序下载(转帖

VC系统扫雷游戏外挂源代码程序下载&#xff08;转帖&#xff09;2008-03-04 10:25经过了多次测试写出了历史上第一个有点意义的MFC程序。效果差强人意。^_^ CODE:// CrackWinmineDlg.cpp : implementation file// #include "stdafx.h"#include "CrackWinmine.h&…

signature=54cb1c123491dc1a268a21f3502cccfc,Modelling information routing with noninterference

摘要&#xff1a;To achieve the highest levels of assurance, MILS architectures need to be formally analysed. A key challenge is to reason about the interaction between the software applications running on top of MILS core components, such as the separation…

JAVA面向对象程序设计(第二版) 袁绍欣 第四章答案

面向对象&#xff08;上&#xff09; 1.名词解释&#xff1a;构造方法、抽象 构造方法一般是用来初始化数据成员的&#xff0c;与类名相同&#xff0c;无返回值。 分为有参和无参数&#xff0c;当一个类中没有定义构造函数时&#xff0c;系统会给该类中加一个默认的空参数的构造…

转:看问题的5个层次

转自&#xff1a;http://www.narrativecard.com/%E7%9C%8B%E9%97%AE%E9%A2%98%E7%9A%845%E4%B8%AA%E5%B1%82%E6%AC%A1/ 转自&#xff1a;《创业教我的50件事》 事件当然要处理&#xff0c;就像疼痛必须注意。但若整天只忙于事件&#xff0c;就像疼了一个月还在猛吞止痛药&#…

鸿蒙系统公测版发布,鸿蒙OS2.0系统公测版什么时候发布-适配机型

鸿蒙OS2.0手机开发者Beta版本于今日已正式发布&#xff0c;那么鸿蒙OS2.0系统的公测版本上市时间&#xff0c;适配手机名单有哪些&#xff0c;接下来小编就为大家带来了解答&#xff0c;一起看看吧&#xff01;一、公测版发布时间12月16日&#xff0c;发布了鸿蒙OS2.0手机开发者…

jQuery实现分页

参考网上的资源(必优博客 http://www.biuuu.com/) 注意&#xff1a;CSS样式使分页导航水平显示&#xff0c;如果没有的话将以普通列表方式呈现。 <html><head><script type"text/javascript" src"Library/jquery1.3.1/dist/jquery.js">&…

对acm icpc 的随笔——01

1&#xff0c;提高编程能力2&#xff0c;学习算法&#xff08;读书&#xff0c;读论文&#xff0c;包括一些题目的验证&#xff09;3&#xff0c;准备好面临的挑战&#xff08;熟悉体型&#xff0c;调整心态&#xff09;4&#xff0c;启发思维 <时刻保持镇定&#xff0c;用心…

oracle常用的工具软件

PL/SQL DevelopertoadOBJECT BROWSEREmbarcadero Rapidsql转载于:https://blog.51cto.com/lsg123/1561234

angular 加入原生html,Angular HTML绑定

收到一只叮咚Angular 2.0.0和Angular 4.0.0 final仅为了安全的内容constructor(private sanitizer:DomSanitizer){}transform(style) {return this.sanitizer.bypassSecurityTrustHtml(style);//return this.sanitizer.bypassSecurityTrustStyle(style);// return this.sanitiz…

foriegn web site

http://www.codeproject.com www.ddj.com http://www.guru.com/ 转载于:https://www.cnblogs.com/liulf/archive/2009/06/11/1501670.html

Unity游戏开发之C#快速入门

C#是微软团队在开发.NET框架时开发的&#xff0c;它的构想接近于C、C&#xff0c;也和JAVA十分相似&#xff0c;有许多强大的编程功能。 个人感受是C#吸收了众多编程语言的优点&#xff0c;从中可以看到C、C、Java、Javascript、python的影子&#xff0c;设计思想都是相通的(过…

通过PowerShell获取Windows系统密码Hash

当你拿到了系统控制权之后如何才能更长的时间内控制已经拿到这台机器呢&#xff1f;作为白帽子&#xff0c;已经在对手防线上撕开一个口子&#xff0c;如果你需要进一步扩大战果&#xff0c;你首先需要做的就是潜伏下来&#xff0c;收集更多的信息便于你判断&#xff0c;便于有…

ant引入html页面,antdesign 中 使用 iconfont symbol方式引入图标不展示

最近在写项目遇到了一个问题&#xff0c;我使用vue框架搭建了一个项目。我创建了一个 index.js文件&#xff0c;内容为&#xff1a;import { Icon } from ant-design-vue;const IconFont Icon.createFromIconfontCN({scriptUrl: //at.alicdn.com/t/font_2296540_wpxzi5ar76.js…

java filter

一、Filter简介 Filter也称之为过滤器&#xff0c;它是Servlet技术中最激动人心的技术&#xff0c;WEB开发人员通过Filter技术&#xff0c;对web服务器管理的所有web资源&#xff1a;例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截&#xff0c;从而实现一些特殊的功…

visualstudio调试html,Visual Studio Code中调试JavaScript

一、安装Debugger for Chrome扩展插件二、配置运行环境最左侧菜单找到调试(一个虫子样图标)&#xff0c;点击下图中红色标注或者直接按F5&#xff1a;然后出现下图所示&#xff1a;选择Chrome&#xff0c;会生成Launch.json文件&#xff1a;{// 使用 IntelliSense 了解相关属性…