在《安卓使用SurfaceView繪制ffmpeg解碼的視頻數(shù)據(jù)》雖然我們成功地實(shí)現(xiàn)了視頻的渲染,
但是在YUV轉(zhuǎn)換成RGB的時(shí)候末早,我們調(diào)用了ffmpeg內(nèi)部的轉(zhuǎn)換函數(shù),這里面包含大量的計(jì)算轉(zhuǎn)換,
所以是很耗費(fèi)CPU性能的逊彭。
今天我們來學(xué)習(xí)一下如何將YUV轉(zhuǎn)換RGB的功能轉(zhuǎn)換到GPU中去執(zhí)行,減少CPU的計(jì)算工作量构订,達(dá)到性能優(yōu)化的目的侮叮。
解決方案是使用OpenGL渲染,將YUV轉(zhuǎn)換RGB的功能交由著色器去處理悼瘾。
Android中引入OpenGL
下面以O(shè)penGL 2.0為例囊榜。
1、 引入庫文件
在CMakeLists.txt
引入GLESv2
和EGL
庫(在ndk中內(nèi)置)亥宿。
直接target_link_libraries
加入即可:
target_link_libraries(
........
#引入opengl的相關(guān)庫
GLESv2
EGL
)
2卸勺、 引入相關(guān)的頭文件
需要引入的頭文件
#include <EGL/egl.h>
#include <GLES2/gl2.h>
至此,我們的安卓OpenGL環(huán)境就算引入成功了烫扼。
OpenGL渲染環(huán)境搭建
1曙求、 EGLDisplay
OpenGL(移動(dòng)端稱作的是EGL)要知道把目標(biāo)內(nèi)容繪制在哪里。這就是EGLDisplay所需的功能映企。
EGLDisplay是一個(gè)封裝系統(tǒng)物理屏幕的數(shù)據(jù)類型(可以理解為繪制目標(biāo)的一個(gè)抽象)悟狱,通常會(huì)調(diào)用eglGetDisplay方法返回EGLDisplay來作為OpenGL ES渲染的目標(biāo)。然后通過eglInitialize初始化顯示設(shè)備堰氓。
代碼如下:
//1 EGL display創(chuàng)建和初始化
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (display == EGL_NO_DISPLAY) {
LOGE("eglGetDisplay failed!");
return;
}
if (EGL_TRUE != eglInitialize(display, 0, 0)) {
LOGE("eglInitialize failed!");
return;
}
2挤渐、 EGLConfig
EGL有了Display之后,它就可以將OpenGL ES的輸出和設(shè)備的屏幕橋接起來豆赏,但是需要指定一些配置項(xiàng)挣菲,這時(shí)候EGLConfig就閃亮登場啦富稻。
//輸出配置
EGLConfig config;
EGLint configNum;
EGLint configSpec[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE
};
if (EGL_TRUE != eglChooseConfig(display, configSpec, &config, 1, &configNum)) {
LOGE("eglChooseConfig failed!");
return;
}
3、 創(chuàng)建EGLSurface
有了顯示設(shè)備白胀,那么如何將設(shè)備的屏幕與EGL鏈接起來呢椭赋?EGLSurface粉墨登場。
//獲取原始窗口
//surface是外部的SurfaceView傳遞進(jìn)來的
ANativeWindow *nwin = ANativeWindow_fromSurface(env, surface);
//創(chuàng)建surface
EGLSurface winsurface = eglCreateWindowSurface(display, config, nwin, 0);
if (winsurface == EGL_NO_SURFACE) {
LOGE("eglCreateWindowSurface failed!");
return;
}
4或杠、 EGLContext
OpenGL所創(chuàng)建的資源, 其實(shí)對程序員可見的僅僅是ID而已, 要操作其中內(nèi)容就需要依賴于EGLContext上下文哪怔。
//context 創(chuàng)建關(guān)聯(lián)的上下文
const EGLint ctxAttr[] = {
EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE
};
EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctxAttr);
if (context == EGL_NO_CONTEXT) {
LOGE("eglCreateContext failed!");
return;
}
if (EGL_TRUE != eglMakeCurrent(display, winsurface, winsurface, context)) {
LOGE("eglMakeCurrent failed!");
return;
}
4、 著色器
所有的渲染都需要位置信息與色彩信息這兩個(gè)信息才能成功渲染處理向抢。頂點(diǎn)著色器和片元著色器就完成了這兩個(gè)功能认境。
頂點(diǎn)著色器:
attribute vec4 aPosition; //頂點(diǎn)坐標(biāo),在外部獲取傳遞進(jìn)來
attribute vec2 aTexCoord; //材質(zhì)(紋理)頂點(diǎn)坐標(biāo)
varying vec2 vTexCoord; //輸出的材質(zhì)(紋理)坐標(biāo)挟鸠,給片元著色器使用
void main() {
//紋理坐標(biāo)轉(zhuǎn)換叉信,以左上角為原點(diǎn)的紋理坐標(biāo)轉(zhuǎn)換成以左下角為原點(diǎn)的紋理坐標(biāo),
// 比如以左上角為原點(diǎn)的(0艘希,0)對應(yīng)以左下角為原點(diǎn)的紋理坐標(biāo)的(0硼身,1)
vTexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);
gl_Position = aPosition;
}
片元著色器:
precision mediump float; //精度
varying vec2 vTexCoord; //頂點(diǎn)著色器傳遞的坐標(biāo),相同名字opengl會(huì)自動(dòng)關(guān)聯(lián)
uniform sampler2D yTexture; //輸入的材質(zhì)(不透明灰度覆享,單像素)
uniform sampler2D uTexture;
uniform sampler2D vTexture;
void main() {
vec3 yuv;
vec3 rgb;
yuv.r = texture2D(yTexture, vTexCoord).r; // y分量
// 因?yàn)閁V的默認(rèn)值是127佳遂,所以我們這里要減去0.5(OpenGLES的Shader中會(huì)把內(nèi)存中0~255的整數(shù)數(shù)值換算為0.0~1.0的浮點(diǎn)數(shù)值)
yuv.g = texture2D(uTexture, vTexCoord).r - 0.5; // u分量
yuv.b = texture2D(vTexture, vTexCoord).r - 0.5; // v分量
// yuv轉(zhuǎn)換成rgb,兩種方法撒顿,一種是RGB按照特定換算公式單獨(dú)轉(zhuǎn)換
// 另外一種是使用矩陣轉(zhuǎn)換
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);
}
著色器編寫好之后如何使用呢丑罪?我們看一張圖:
根據(jù)圖片流程,我們很方便就創(chuàng)建編譯并且鏈接好著色器凤壁。
GLint InitShader(const char *code, GLint type) {
//創(chuàng)建shader
GLint sh = glCreateShader(type);
if (sh == 0) {
LOGE("glCreateShader %d failed!", type);
return 0;
}
//加載shader
glShaderSource(sh,
1, //shader數(shù)量
&code, //shader代碼
0); //代碼長度
//編譯shader
glCompileShader(sh);
//獲取編譯情況
GLint status;
glGetShaderiv(sh, GL_COMPILE_STATUS, &status);
if (status == 0) {
LOGE("glCompileShader failed!");
return 0;
}
LOGE("glCompileShader success!");
return sh;
}
........此處省略若干代碼
//頂點(diǎn)和片元shader初始化
//頂點(diǎn)shader初始化
GLint vsh = InitShader(vertexShader, GL_VERTEX_SHADER);
//片元yuv420 shader初始化
GLint fsh = InitShader(fragYUV420P, GL_FRAGMENT_SHADER);
/////////////////////////////////////////////////////////////
//創(chuàng)建渲染程序
GLint program = glCreateProgram();
if (program == 0) {
LOGE("glCreateProgram failed!");
return;
}
//渲染程序中加入著色器代碼
glAttachShader(program, vsh);
glAttachShader(program, fsh);
//鏈接程序
glLinkProgram(program);
GLint status = 0;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status != GL_TRUE) {
LOGE("glLinkProgram failed!");
return;
}
glUseProgram(program);
LOGE("glLinkProgram success!");
/////////////////////////////////////////////////////////////
//加入三維頂點(diǎn)數(shù)據(jù) 兩個(gè)三角形組成正方形
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,
};
GLuint apos = (GLuint) glGetAttribLocation(program, "aPosition");
glEnableVertexAttribArray(apos);
//傳遞頂點(diǎn)
glVertexAttribPointer(apos, 3, GL_FLOAT, GL_FALSE, 12, vers);
//加入材質(zhì)坐標(biāo)數(shù)據(jù)
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);
//材質(zhì)紋理初始化
//設(shè)置紋理層
glUniform1i(glGetUniformLocation(program, "yTexture"), 0); //對于紋理第1層
glUniform1i(glGetUniformLocation(program, "uTexture"), 1); //對于紋理第2層
glUniform1i(glGetUniformLocation(program, "vTexture"), 2); //對于紋理第3層
//創(chuàng)建opengl紋理
GLuint texts[3] = {0};
//創(chuàng)建三個(gè)紋理
glGenTextures(3, texts);
//設(shè)置紋理屬性
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);
//設(shè)置紋理的格式和大小
glTexImage2D(GL_TEXTURE_2D,
0, //細(xì)節(jié)基本 0默認(rèn)
GL_LUMINANCE,//gpu內(nèi)部格式 亮度吩屹,灰度圖
width, height, //拉升到全屏
0, //邊框
GL_LUMINANCE,//數(shù)據(jù)的像素格式 亮度,灰度圖 要與上面一致
GL_UNSIGNED_BYTE, //像素的數(shù)據(jù)類型
NULL //紋理的數(shù)據(jù)
);
//設(shè)置紋理屬性
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);
//設(shè)置紋理的格式和大小
glTexImage2D(GL_TEXTURE_2D,
0, //細(xì)節(jié)基本 0默認(rèn)
GL_LUMINANCE,//gpu內(nèi)部格式 亮度客扎,灰度圖
width / 2, height / 2, //拉升到全屏
0, //邊框
GL_LUMINANCE,//數(shù)據(jù)的像素格式 亮度祟峦,灰度圖 要與上面一致
GL_UNSIGNED_BYTE, //像素的數(shù)據(jù)類型
NULL //紋理的數(shù)據(jù)
);
//設(shè)置紋理屬性
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);
//設(shè)置紋理的格式和大小
glTexImage2D(GL_TEXTURE_2D,
0, //細(xì)節(jié)基本 0默認(rèn)
GL_LUMINANCE,//gpu內(nèi)部格式 亮度罚斗,灰度圖
width / 2, height / 2, //拉升到全屏
0, //邊框
GL_LUMINANCE,//數(shù)據(jù)的像素格式 亮度徙鱼,灰度圖 要與上面一致
GL_UNSIGNED_BYTE, //像素的數(shù)據(jù)類型
NULL //紋理的數(shù)據(jù)
);
//////////////////////////////////////////////////////
////紋理的修改和顯示
unsigned char *buf[3] = {0};
buf[0] = new unsigned char[width * height];
buf[1] = new unsigned char[width * height / 4];
buf[2] = new unsigned char[width * height / 4];
5、 傳遞數(shù)據(jù)
到了這一步针姿,我們的渲染環(huán)境算是搭建完畢了袱吆,下面就是通過CPU傳遞數(shù)據(jù)到GPU進(jìn)行渲染。簡單地說就是獲取在著色器中定義的變量并且對其賦值距淫,然后調(diào)用繪制的API即可绞绒。
主要代碼:
// 解碼得到Y(jié)UV數(shù)據(jù)
// 數(shù)據(jù)Y
buf[0] = frame->data[0];
memcpy(buf[0],frame->data[0],width*height);
// 數(shù)據(jù)U
memcpy(buf[1],frame->data[1],width*height/4);
// 數(shù)據(jù)V
memcpy(buf[2],frame->data[2],width*height/4);
//激活第1層紋理,綁定到創(chuàng)建的opengl紋理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D,texts[0]);
//替換紋理內(nèi)容
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width,height,GL_LUMINANCE,GL_UNSIGNED_BYTE,buf[0]);
//激活第2層紋理,綁定到創(chuàng)建的opengl紋理
glActiveTexture(GL_TEXTURE0+1);
glBindTexture(GL_TEXTURE_2D,texts[1]);
//替換紋理內(nèi)容
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width/2,height/2,GL_LUMINANCE,GL_UNSIGNED_BYTE,buf[1]);
//激活第2層紋理,綁定到創(chuàng)建的opengl紋理
glActiveTexture(GL_TEXTURE0+2);
glBindTexture(GL_TEXTURE_2D,texts[2]);
//替換紋理內(nèi)容
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width/2,height/2,GL_LUMINANCE,GL_UNSIGNED_BYTE,buf[2]);
//三維繪制
glDrawArrays(GL_TRIANGLE_STRIP,0,4);
//窗口顯示
eglSwapBuffers(display,winsurface);
至此,我們的渲染過程就完成了榕暇,與前面的ffmpeg解碼為YUV數(shù)據(jù)聯(lián)系起來蓬衡,實(shí)現(xiàn)一邊解碼一邊渲染數(shù)據(jù)喻杈。通過對比筆者很明顯覺察到使用OpenGL渲染的視頻畫面流暢很多。而且對比兩種渲染方式的CPU使用率也發(fā)現(xiàn)OpenGL的渲染方式確實(shí)使得CPU的使用率大大降低狰晚。
最后貼一下結(jié)合ffmpeg從解碼到渲染全過程的完整代碼:
#include <jni.h>
#include <string>
#include "FlyLog.h"
#include <android/log.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <android/native_window_jni.h>
#include <EGL/egl.h>
#include <GLES2/gl2.h>
// 因?yàn)閒fmpeg是純C代碼筒饰,要在cpp中使用則需要使用 extern "C"
extern "C" {
#include "libavutil/avutil.h"
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
}
//頂點(diǎn)著色器glsl的宏
// 第二個(gè)#號(hào)的意思是自動(dòng)鏈接字符串,而不用增加引號(hào)壁晒,參考ijkplayer的寫法
#define GET_STR(x) #x
static const char *vertexShader = GET_STR(
attribute vec4 aPosition; //頂點(diǎn)坐標(biāo)瓷们,在外部獲取傳遞進(jìn)來
attribute vec2 aTexCoord; //材質(zhì)(紋理)頂點(diǎn)坐標(biāo)
varying vec2 vTexCoord; //輸出的材質(zhì)(紋理)坐標(biāo),給片元著色器使用
void main() {
//紋理坐標(biāo)轉(zhuǎn)換秒咐,以左上角為原點(diǎn)的紋理坐標(biāo)轉(zhuǎn)換成以左下角為原點(diǎn)的紋理坐標(biāo)谬晕,
// 比如以左上角為原點(diǎn)的(0,0)對應(yīng)以左下角為原點(diǎn)的紋理坐標(biāo)的(0携取,1)
vTexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);
gl_Position = aPosition;
}
);
//片元著色器,軟解碼和部分x86硬解碼解碼得出來的格式是YUV420p
static const char *fragYUV420P = GET_STR(
precision mediump float; //精度
varying vec2 vTexCoord; //頂點(diǎn)著色器傳遞的坐標(biāo)攒钳,相同名字opengl會(huì)自動(dòng)關(guān)聯(lián)
uniform sampler2D yTexture; //輸入的材質(zhì)(不透明灰度,單像素)
uniform sampler2D uTexture;
uniform sampler2D vTexture;
void main() {
vec3 yuv;
vec3 rgb;
yuv.r = texture2D(yTexture, vTexCoord).r; // y分量
// 因?yàn)閁V的默認(rèn)值是127雷滋,所以我們這里要減去0.5(OpenGLES的Shader中會(huì)把內(nèi)存中0~255的整數(shù)數(shù)值換算為0.0~1.0的浮點(diǎn)數(shù)值)
yuv.g = texture2D(uTexture, vTexCoord).r - 0.5; // u分量
yuv.b = texture2D(vTexture, vTexCoord).r - 0.5; // v分量
// yuv轉(zhuǎn)換成rgb夕玩,兩種方法,一種是RGB按照特定換算公式單獨(dú)轉(zhuǎn)換
// 另外一種是使用矩陣轉(zhuǎn)換
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) {
//創(chuàng)建shader
GLint sh = glCreateShader(type);
if (sh == 0) {
LOGE("glCreateShader %d failed!", type);
return 0;
}
//加載shader
glShaderSource(sh,
1, //shader數(shù)量
&code, //shader代碼
0); //代碼長度
//編譯shader
glCompileShader(sh);
//獲取編譯情況
GLint status;
glGetShaderiv(sh, GL_COMPILE_STATUS, &status);
if (status == 0) {
LOGE("glCompileShader failed!");
return 0;
}
LOGE("glCompileShader success!");
return sh;
}
/**
* 將數(shù)據(jù)轉(zhuǎn)換成double類型的一個(gè)方法
* @param r
* @return
*/
static double r2d(AVRational r) {
return r.num == 0 || r.den == 0 ? 0 : (double) r.num / (double) r.den;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_flyer_ffmpeg_FlyPlayer_playVideoByOpenGL(JNIEnv *env, jobject thiz, jstring video_path,
jobject surface) {
const char *path = env->GetStringUTFChars(video_path, 0);
AVFormatContext *fmt_ctx;
// 初始化格式化上下文
fmt_ctx = avformat_alloc_context();
// 使用ffmpeg打開文件
int re = avformat_open_input(&fmt_ctx, path, nullptr, nullptr);
if (re != 0) {
LOGE("打開文件失斁颉:%s", av_err2str(re));
return;
}
//探測流索引
re = avformat_find_stream_info(fmt_ctx, nullptr);
if (re < 0) {
LOGE("索引探測失斄敲稀:%s", av_err2str(re));
return;
}
//尋找視頻流索引
int v_idx = av_find_best_stream(
fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
if (v_idx == -1) {
LOGE("獲取視頻流索引失敗");
return;
}
//解碼器參數(shù)
AVCodecParameters *c_par;
//解碼器上下文
AVCodecContext *cc_ctx;
//聲明一個(gè)解碼器
const AVCodec *codec;
c_par = fmt_ctx->streams[v_idx]->codecpar;
//通過id查找解碼器
codec = avcodec_find_decoder(c_par->codec_id);
if (!codec) {
LOGE("查找解碼器失敗");
return;
}
//用參數(shù)c_par實(shí)例化編解碼器上下文,尸昧,并打開編解碼器
cc_ctx = avcodec_alloc_context3(codec);
// 關(guān)聯(lián)解碼器上下文
re = avcodec_parameters_to_context(cc_ctx, c_par);
if (re < 0) {
LOGE("解碼器上下文關(guān)聯(lián)失敗:%s", av_err2str(re));
return;
}
//打開解碼器
re = avcodec_open2(cc_ctx, codec, nullptr);
if (re != 0) {
LOGE("打開解碼器失敗:%s", av_err2str(re));
return;
}
// 獲取視頻的寬高,也可以通過解碼器獲取
AVStream *as = fmt_ctx->streams[v_idx];
int width = as->codecpar->width;
int height = as->codecpar->height;
LOGE("width:%d", width);
LOGE("height:%d", height);
//數(shù)據(jù)包
AVPacket *pkt;
//數(shù)據(jù)幀
AVFrame *frame;
//初始化
pkt = av_packet_alloc();
frame = av_frame_alloc();
//1 獲取原始窗口
ANativeWindow *nwin = ANativeWindow_fromSurface(env, surface);
////////////////////
///EGL
//1 EGL display創(chuàng)建和初始化
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (display == EGL_NO_DISPLAY) {
LOGE("eglGetDisplay failed!");
return;
}
if (EGL_TRUE != eglInitialize(display, 0, 0)) {
LOGE("eglInitialize failed!");
return;
}
//2 surface
//2-1 surface窗口配置
//輸出配置
EGLConfig config;
EGLint configNum;
EGLint configSpec[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE
};
if (EGL_TRUE != eglChooseConfig(display, configSpec, &config, 1, &configNum)) {
LOGE("eglChooseConfig failed!");
return;
}
//創(chuàng)建surface
EGLSurface winsurface = eglCreateWindowSurface(display, config, nwin, 0);
if (winsurface == EGL_NO_SURFACE) {
LOGE("eglCreateWindowSurface failed!");
return;
}
//3 context 創(chuàng)建關(guān)聯(lián)的上下文
const EGLint ctxAttr[] = {
EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE
};
EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctxAttr);
if (context == EGL_NO_CONTEXT) {
LOGE("eglCreateContext failed!");
return;
}
if (EGL_TRUE != eglMakeCurrent(display, winsurface, winsurface, context)) {
LOGE("eglMakeCurrent failed!");
return;
}
LOGE("EGL Init Success!");
//頂點(diǎn)和片元shader初始化
//頂點(diǎn)shader初始化
GLint vsh = InitShader(vertexShader, GL_VERTEX_SHADER);
//片元yuv420 shader初始化
GLint fsh = InitShader(fragYUV420P, GL_FRAGMENT_SHADER);
/////////////////////////////////////////////////////////////
//創(chuàng)建渲染程序
GLint program = glCreateProgram();
if (program == 0) {
LOGE("glCreateProgram failed!");
return;
}
//渲染程序中加入著色器代碼
glAttachShader(program, vsh);
glAttachShader(program, fsh);
//鏈接程序
glLinkProgram(program);
GLint status = 0;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status != GL_TRUE) {
LOGE("glLinkProgram failed!");
return;
}
glUseProgram(program);
LOGE("glLinkProgram success!");
/////////////////////////////////////////////////////////////
//加入三維頂點(diǎn)數(shù)據(jù) 兩個(gè)三角形組成正方形
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,
};
GLuint apos = (GLuint) glGetAttribLocation(program, "aPosition");
glEnableVertexAttribArray(apos);
//傳遞頂點(diǎn)
glVertexAttribPointer(apos, 3, GL_FLOAT, GL_FALSE, 12, vers);
//加入材質(zhì)坐標(biāo)數(shù)據(jù)
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);
//材質(zhì)紋理初始化
//設(shè)置紋理層
glUniform1i(glGetUniformLocation(program, "yTexture"), 0); //對于紋理第1層
glUniform1i(glGetUniformLocation(program, "uTexture"), 1); //對于紋理第2層
glUniform1i(glGetUniformLocation(program, "vTexture"), 2); //對于紋理第3層
//創(chuàng)建opengl紋理
GLuint texts[3] = {0};
//創(chuàng)建三個(gè)紋理
glGenTextures(3, texts);
//設(shè)置紋理屬性
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);
//設(shè)置紋理的格式和大小
glTexImage2D(GL_TEXTURE_2D,
0, //細(xì)節(jié)基本 0默認(rèn)
GL_LUMINANCE,//gpu內(nèi)部格式 亮度揩页,灰度圖
width, height, //拉升到全屏
0, //邊框
GL_LUMINANCE,//數(shù)據(jù)的像素格式 亮度,灰度圖 要與上面一致
GL_UNSIGNED_BYTE, //像素的數(shù)據(jù)類型
NULL //紋理的數(shù)據(jù)
);
//設(shè)置紋理屬性
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);
//設(shè)置紋理的格式和大小
glTexImage2D(GL_TEXTURE_2D,
0, //細(xì)節(jié)基本 0默認(rèn)
GL_LUMINANCE,//gpu內(nèi)部格式 亮度烹俗,灰度圖
width / 2, height / 2, //拉升到全屏
0, //邊框
GL_LUMINANCE,//數(shù)據(jù)的像素格式 亮度爆侣,灰度圖 要與上面一致
GL_UNSIGNED_BYTE, //像素的數(shù)據(jù)類型
NULL //紋理的數(shù)據(jù)
);
//設(shè)置紋理屬性
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);
//設(shè)置紋理的格式和大小
glTexImage2D(GL_TEXTURE_2D,
0, //細(xì)節(jié)基本 0默認(rèn)
GL_LUMINANCE,//gpu內(nèi)部格式 亮度,灰度圖
width / 2, height / 2, //拉升到全屏
0, //邊框
GL_LUMINANCE,//數(shù)據(jù)的像素格式 亮度幢妄,灰度圖 要與上面一致
GL_UNSIGNED_BYTE, //像素的數(shù)據(jù)類型
NULL //紋理的數(shù)據(jù)
);
//////////////////////////////////////////////////////
////紋理的修改和顯示
unsigned char *buf[3] = {0};
buf[0] = new unsigned char[width * height];
buf[1] = new unsigned char[width * height / 4];
buf[2] = new unsigned char[width * height / 4];
while (av_read_frame(fmt_ctx, pkt) >= 0) {//持續(xù)讀幀
// 只解碼視頻流
if (pkt->stream_index == v_idx) {
//發(fā)送數(shù)據(jù)包到解碼器
avcodec_send_packet(cc_ctx, pkt);
//清理
av_packet_unref(pkt);
//這里為什么要使用一個(gè)for循環(huán)呢兔仰?
// 因?yàn)閍vcodec_send_packet和avcodec_receive_frame并不是一對一的關(guān)系的
//一個(gè)avcodec_send_packet可能會(huì)出發(fā)多個(gè)avcodec_receive_frame
for (;;) {
// 接受解碼的數(shù)據(jù)
re = avcodec_receive_frame(cc_ctx, frame);
if (re != 0) {
break;
} else {
// 解碼得到Y(jié)UV數(shù)據(jù)
// 數(shù)據(jù)Y
buf[0] = frame->data[0];
memcpy(buf[0],frame->data[0],width*height);
// 數(shù)據(jù)U
memcpy(buf[1],frame->data[1],width*height/4);
// 數(shù)據(jù)V
memcpy(buf[2],frame->data[2],width*height/4);
//激活第1層紋理,綁定到創(chuàng)建的opengl紋理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D,texts[0]);
//替換紋理內(nèi)容
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width,height,GL_LUMINANCE,GL_UNSIGNED_BYTE,buf[0]);
//激活第2層紋理,綁定到創(chuàng)建的opengl紋理
glActiveTexture(GL_TEXTURE0+1);
glBindTexture(GL_TEXTURE_2D,texts[1]);
//替換紋理內(nèi)容
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width/2,height/2,GL_LUMINANCE,GL_UNSIGNED_BYTE,buf[1]);
//激活第2層紋理,綁定到創(chuàng)建的opengl紋理
glActiveTexture(GL_TEXTURE0+2);
glBindTexture(GL_TEXTURE_2D,texts[2]);
//替換紋理內(nèi)容
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width/2,height/2,GL_LUMINANCE,GL_UNSIGNED_BYTE,buf[2]);
//三維繪制
glDrawArrays(GL_TRIANGLE_STRIP,0,4);
//窗口顯示
eglSwapBuffers(display,winsurface);
}
}
}
}
//關(guān)閉環(huán)境
avcodec_free_context(&cc_ctx);
// 釋放資源
av_frame_free(&frame);
av_packet_free(&pkt);
avformat_free_context(fmt_ctx);
LOGE("播放完畢");
env->ReleaseStringUTFChars(video_path, path);
}
遇到的問題
筆者測試了兩個(gè)不同的視頻發(fā)現(xiàn)一個(gè)能播放,另外一個(gè)花屏蕉鸳,看不出圖像乎赴。
查找了一些資料至今仍找不到問題所在,兩個(gè)視頻使用SurfaceView渲染都是可以的潮尝,說明可能不是解碼的問題榕吼,估計(jì)是渲染程序的問題。而且兩個(gè)視頻解碼出來的YUV數(shù)據(jù)格式不一樣勉失,一個(gè)yuvj420p羹蚣,這個(gè)可以正常使用OpenGL渲染,一個(gè)是yuv420p乱凿,這個(gè)就不能渲染顽素,花屏咽弦。
懇親大神不吝賜教。
結(jié)束
最后如果你對音視頻開發(fā)感興趣可掃碼關(guān)注胁出,筆者在各個(gè)知識(shí)點(diǎn)學(xué)習(xí)完畢之后也會(huì)使用ffmepg從零開始編寫一個(gè)多媒體播放器离唬,包括本地播放及網(wǎng)絡(luò)流播放等等。歡迎關(guān)注划鸽,后續(xù)我們共同探討输莺,共同進(jìn)步。