您当前的位置: 首页 > 学无止境 > 心得笔记 网站首页心得笔记
11. 通过OpenglES纹理修改完成yuv文件的播放显示~1
发布时间:2021-06-08 10:33:41编辑:雪饮阅读()
能够修改纹理数据和显示之后,接下来就要对yuv视频文件进行解析播放了。
#include <string>
#include <android/log.h>
#include <android/native_window_jni.h>
#include <EGL/egl.h>
#include <GLES2/gl2.h>
#include <string.h>
#define LOGD(...) __android_log_print(ANDROID_LOG_WARN,"testff",__VA_ARGS__)
//顶点着色器glsl ,一元运算符 # 常称为字符串化运算符,這裏用在宏定義上,則這裏的x會被展開為"x"字面量形式
#define GET_STR(x) #x
static const char *vertexShader = GET_STR(
attribute vec4 aPosition; //顶点坐标
attribute vec2 aTexCoord; //材质顶点坐标
varying vec2 vTexCoord; //输出的材质坐标
void main(){
vTexCoord = vec2(aTexCoord.x,1.0-aTexCoord.y);
gl_Position = aPosition;
}
);
//片元着色器,软解码和部分x86硬解码
static const char *fragYUV420P = GET_STR(
precision mediump float; //精度
varying vec2 vTexCoord; //顶点着色器传递的坐标
uniform sampler2D yTexture; //输入的材质(不透明灰度,单像素)
uniform sampler2D uTexture;
uniform sampler2D vTexture;
void main(){
vec3 yuv;
vec3 rgb;
yuv.r = texture2D(yTexture,vTexCoord).r;
//這裏一般減去0.5去做個四捨五入
yuv.g = texture2D(uTexture,vTexCoord).r - 0.5;
yuv.b = texture2D(vTexture,vTexCoord).r - 0.5;
//一個yuv轉rgb的公式
rgb = mat3(1.0, 1.0, 1.0,
0.0,-0.39465,2.03211,
1.13983,-0.58060,0.0)*yuv;
//输出像素颜色
gl_FragColor = vec4(rgb,1.0);
}
);
GLint InitShader(const char *code,GLint type)
{
//创建shader
GLint sh = glCreateShader(type);
if(sh == 0)
{
LOGD("glCreateShader %d failed!",type);
return 0;
}
//加载shader,最后一个参数代码长度,传0表示直接找到字符串的结尾
glShaderSource(sh,
1, //shader数量
&code, //shader代码
0); //代码长度
//编译shader
glCompileShader(sh);
//获取编译情况
GLint status;
glGetShaderiv(sh,GL_COMPILE_STATUS,&status);
if(status == 0)
{
LOGD("glCompileShader failed!");
return 0;
}
LOGD("glCompileShader success!");
return sh;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_yuv_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}extern "C"
JNIEXPORT void JNICALL
Java_com_example_yuv_XPlay_Open(JNIEnv *env, jobject thiz, jstring url_, jobject surface) {
const char *url=env->GetStringUTFChars(url_,0);
LOGD("open url is %s",url);
FILE *fp = fopen(url,"rb");
if(!fp)
{
LOGD("open file %s failed!",url);
return;
}
//獲取原始窗口
ANativeWindow *nwin=ANativeWindow_fromSurface(env,surface);
//1 display創建和初始化
EGLDisplay display=eglGetDisplay(EGL_DEFAULT_DISPLAY);
if(display==EGL_NO_DISPLAY){
LOGD("eglGetDisplay failed!");
return;
}
if(EGL_TRUE!=eglInitialize(display,0,0)){
LOGD("eglInitialize failed");
return;
}
//2 surface
//2-1 surface窗口配置
//輸出配置
EGLConfig config;
EGLint configNum;
//c語言數組結束填充,這裏用EGL_NONE
EGLint configSpec[]={
EGL_RED_SIZE,8,
EGL_GREEN_SIZE,8,
EGL_BLUE_SIZE,8,
EGL_SURFACE_TYPE,EGL_WINDOW_BIT,
EGL_NONE
};
//EGL_TRUE的宏定義值為1
if(EGL_TRUE!=eglChooseConfig(display,configSpec,&config,1,&configNum)){
LOGD("eglChooseConfig failed!");
return;
}
//創建surface
EGLSurface winsurface=eglCreateWindowSurface(display,config,nwin,0);
if(winsurface==EGL_NO_SURFACE){
LOGD("eglCreateWindowSurface failed!");
return;
}
//3 context創建關聯的上下文
const EGLint ctxAttr[]={
EGL_CONTEXT_CLIENT_VERSION,2,EGL_NONE
};
//第三個參數是在多個上下文進行共享的時候用的,這裏用不到,所以就是設置為EGL_NO_CONTEXT,最後一個參數是接收一個版本信息
EGLContext context=eglCreateContext(display,config,EGL_NO_CONTEXT,ctxAttr);
if(context==EGL_NO_CONTEXT){
LOGD("eglCreateContext failed!");
}
//上下文關聯起來就可以進行數據的交換,第二和第三個函數是分別用來繪製、讀取的
if(EGL_TRUE!=eglMakeCurrent(display,winsurface,winsurface,context)){
LOGD("eglMakeCurrent failed!");
return;
}
LOGD("EGL Init Success");
//顶点和片元shader初始化
//顶点shader初始化 GL_VERTEX_SHADER表示顶点着色器
GLint vsh = InitShader(vertexShader,GL_VERTEX_SHADER);
//片元yuv420 shader初始化 GL_FRAGMENT_SHADER表示片段着色器或者片元着色器
GLint fsh = InitShader(fragYUV420P,GL_FRAGMENT_SHADER);
/////////////////////////////////////////////////////////////
//创建渲染程序
GLint program = glCreateProgram();
if(program == 0)
{
LOGD("glCreateProgram failed!");
return;
}
//渲染程序中加入着色器代码
glAttachShader(program,vsh);
glAttachShader(program,fsh);
//链接程序
glLinkProgram(program);
GLint status = 0;
glGetProgramiv(program,GL_LINK_STATUS,&status);
if(status != GL_TRUE)
{
LOGD("glLinkProgram failed!");
return;
}
//使用程序对象program作为当前渲染状态的一部分
glUseProgram(program);
LOGD("glLinkProgram success!");
/////////////////////////////////////////////////////////////
//加入三维顶点数据两个三角形组成正方形,顶点信息,参数顺序“Z”字形右下到左上
static float vers[] = {
1.0f,-1.0f,0.0f,
-1.0f,-1.0f,0.0f,
1.0f,1.0f,0.0f,
-1.0f,1.0f,0.0f,
};
//获取顶点坐标,根据可以名称,这里是aPosition,对应定义于上面的vertexShader中
GLuint apos = (GLuint)glGetAttribLocation(program,"aPosition");
//启用指定属性
glEnableVertexAttribArray(apos);
/*传递顶点
* size:一个点有多少个数据,例如一个坐标是x,y,z则size为3
* 存储格式GL_FLOAT
* 反向向量没有,则使用GL_FALSE
* stride:每个值的间隔,这里总共有3个数据,每个数据为4个字节,则总共12个字节
* */
glVertexAttribPointer(apos,3,GL_FLOAT,GL_FALSE,12,vers);
//加入材质坐标数据,同样"Z"字形,右下到左上,只是材质坐标没有z轴
static float txts[] = {
1.0f,0.0f ,
0.0f,0.0f,
1.0f,1.0f,
0.0,1.0
};
GLuint atex = (GLuint)glGetAttribLocation(program,"aTexCoord");
glEnableVertexAttribArray(atex);
glVertexAttribPointer(atex,2,GL_FLOAT,GL_FALSE,8,txts);
//yuv纹理创建及初始化
int width = 424;
int height = 240;
//材质纹理初始化
//设置纹理层,yTexture来源于上面定义的fragYUV420P
glUniform1i( glGetUniformLocation(program,"yTexture"),0); //对于纹理第1层
glUniform1i( glGetUniformLocation(program,"uTexture"),1); //对于纹理第2层
glUniform1i( glGetUniformLocation(program,"vTexture"),2); //对于纹理第3层
//创建opengl纹理
GLuint texts[3] = {0};
//创建三个纹理
glGenTextures(3,texts);
//设置纹理属性,这里先做第一层纹理
glBindTexture(GL_TEXTURE_2D,texts[0]);
//缩小的过滤器线性过滤
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
//放大的过滤器
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
//设置纹理的格式和大小
glTexImage2D(GL_TEXTURE_2D,
0, //细节级别 0默认
GL_LUMINANCE,//gpu内部格式亮度,灰度图(1个字节),如果不是灰度图,那么还有一种就是rgb,是3个字节,浪费性能
width,height, //默认是拉升到全屏,尺寸尽量要是2的次方
0, //边框
GL_LUMINANCE,//数据的像素格式亮度,灰度图要与上面一致
GL_UNSIGNED_BYTE, //像素的数据类型
NULL //纹理的数据,这里只创建纹理,具体的数据在解码之后进行设置
);
//设置纹理属性
glBindTexture(GL_TEXTURE_2D,texts[1]);
//缩小的过滤器
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
//设置纹理的格式和大小
glTexImage2D(GL_TEXTURE_2D,
0, //细节基本 0默认
GL_LUMINANCE,//gpu内部格式亮度,灰度图
width/2,height/2, //拉升到全屏,后面这两层纹理的宽高都要除以2
0, //边框
GL_LUMINANCE,//数据的像素格式亮度,灰度图要与上面一致
GL_UNSIGNED_BYTE, //像素的数据类型
NULL //纹理的数据
);
//设置纹理属性
glBindTexture(GL_TEXTURE_2D,texts[2]);
//缩小的过滤器
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
//设置纹理的格式和大小
glTexImage2D(GL_TEXTURE_2D,
0, //细节基本 0默认
GL_LUMINANCE,//gpu内部格式亮度,灰度图
width/2,height/2, //拉升到全屏
0, //边框
GL_LUMINANCE,//数据的像素格式亮度,灰度图要与上面一致
GL_UNSIGNED_BYTE, //像素的数据类型
NULL //纹理的数据
);
//////////////////////////////////////////////////////
////纹理的修改和显示
/*
* 做像素格式转换时候一定要用unsigned char类型,char类型一改变就会产生符号位,所以不能直接使用char类型,有时候会影响到计算,
* 虽然这里不会用到修改
* */
unsigned char *buf[3] = {0};
buf[0] = new unsigned char[width*height];
//宽度和高度都是这个图像宽度与高度的一半,所以,这里要除以4
buf[1] = new unsigned char[width*height/4];
buf[2] = new unsigned char[width*height/4];
for(int i = 0; i<10000;i++)
{
/*memset是计算机中C/C++语言初始化函数。作用是将某一块内存中的内容全部设置为指定的值,这个函数通常为新申请的内存做初始化工作。
* void *memset(void *s, int ch, size_t n);
函数解释:将s中当前位置后面的n个字节(typedef unsigned int size_t )用 ch 替换并返回 s 。
这里i就是那个指定的值
* */
//memset(buf[0],i,width*height);
//除以4:同上
//memset(buf[1],i,width*height/4);
//memset(buf[2],i,width*height/4);
//420p 假如是8个像素的图像 yyyyyyyy uu vv
if(feof(fp) == 0)
{
//yyyyyyyy
fread(buf[0],1,width*height,fp);
fread(buf[1],1,width*height/4,fp);
fread(buf[2],1,width*height/4,fp);
}
//激活第1层纹理,绑定到创建的opengl纹理,GL_TEXTURE0这是一个宏,后缀最大可达31
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D,texts[0]);
//替换纹理内容,细节级别,x,y偏移位置这里不用设置也为0,数据格式、存储格式、写入到的空间
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width,height,GL_LUMINANCE,GL_UNSIGNED_BYTE,buf[0]);
//激活第2层纹理,绑定到创建的opengl纹理
glActiveTexture(GL_TEXTURE0+1);
glBindTexture(GL_TEXTURE_2D,texts[1]);
//替换纹理内容
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width/2,height/2,GL_LUMINANCE,GL_UNSIGNED_BYTE,buf[1]);
//激活第2层纹理,绑定到创建的opengl纹理
glActiveTexture(GL_TEXTURE0+2);
glBindTexture(GL_TEXTURE_2D,texts[2]);
//替换纹理内容
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width/2,height/2,GL_LUMINANCE,GL_UNSIGNED_BYTE,buf[2]);
//三维绘制,从0开始,总共有多少个顶点,这里有四个顶点(参见上篇中的顶点坐标图或者材质顶点坐标图)
glDrawArrays(GL_TRIANGLE_STRIP,0,4);
//窗口显示
eglSwapBuffers(display,winsurface);
}
env->ReleaseStringUTFChars(url_,url);
}
package com.example.yuv;
import androidx.appcompat.app.AppCompatActivity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//去掉标题栏
supportRequestWindowFeature( Window.FEATURE_NO_TITLE);
//全屏,隐藏状态
getWindow().setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN ,
WindowManager.LayoutParams.FLAG_FULLSCREEN
);
//屏幕为横屏
setRequestedOrientation( ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE );
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}其实个人觉得这里可做可不做,虽然我没有测试过不做的后果。
最后在目标设备中例如雷电4中允许了文件权限后,重新部署运行就可以看到视频如期播放了。
关键字词:yuv