23.视频播放器项目实战-音视频播放

视频播放器项目实战-音视频播放

简单易懂版 | 图文结合 | 生动形象


🎬 先打个比方:播放就像餐厅上菜!

想象餐厅的厨房:

复制代码
🍽️ 餐厅上菜
   │
   ├── 🖼️ 视频菜(主厨负责)
   │   ├── 备菜:解码器准备YUV食材
   │   ├── 摆盘:Shader调味上色
   │   ├── 上菜:GLVideoView端盘上桌
   │   └── 摆桌:EGL布置桌面
   │
   ├── 🔊 音频菜(服务员负责)
   │   ├── 备菜:重采样调整口味
   │   ├── 装盘:PCM装碗
   │   ├── 传菜:缓冲队列传递
   │   └── 上菜:SLAudioPlay端盘上桌
   │
   └── 🎯 协调员:确保菜同时上桌

记住这个比喻,你就懂了!


🖼️ 一、视频显示模块

1.1 IVideoView接口设计

就像:端菜的盘子

c 复制代码
class IVideoView {
public:
    virtual void display(AVFrame *frame) = 0;
    virtual int init(int width, int height, ANativeWindow *window) = 0;
    virtual void close() = 0;
};

// 实现类
class GLVideoView : public IVideoView {
private:
    XShader *shader;
    XTexture *texture;
    IEGL *egl;
    int width, height;
    
public:
    virtual void display(AVFrame *frame) override {
        // 更新纹理
        texture->update(frame);
        // 绘制
        draw();
        // 交换缓冲
        egl->swapBuffer();
    }
    
    virtual int init(int w, int h, ANativeWindow *window) override {
        width = w;
        height = h;
        
        // 1️⃣ 初始化EGL
        egl = new XEGL();
        egl->init(window);
        
        // 2️⃣ 初始化Shader
        shader = new XShader();
        shader->init();
        
        // 3️⃣ 初始化纹理
        texture = new XTexture();
        texture->init();
        
        return 0;
    }
    
    virtual void close() override {
        if (texture) { delete texture; texture = NULL; }
        if (shader) { delete shader; shader = NULL; }
        if (egl) { delete egl; egl = NULL; }
    }
};

1.2 IEGL类封装

就像:餐桌布置

c 复制代码
class IEGL {
public:
    virtual int init(ANativeWindow *window) = 0;
    virtual void close() = 0;
    virtual void draw() = 0;
    virtual void swapBuffer() = 0;
};

class XEGL : public IEGL {
private:
    EGLDisplay display;
    EGLSurface surface;
    EGLContext context;
    
public:
    virtual int init(ANativeWindow *window) override {
        // 获取默认显示
        display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
        if (display == EGL_NO_DISPLAY) {
            printf("❌ 获取EGL显示失败!\n");
            return -1;
        }
        
        // 初始化EGL
        if (eglInitialize(display, NULL, NULL) != EGL_TRUE) {
            printf("❌ EGL初始化失败!\n");
            return -1;
        }
        
        // 选择配置
        EGLConfig config;
        EGLint configSize = 1;
        EGLint configAttrs[] = {
            EGL_RED_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE, 8,
            EGL_ALPHA_SIZE, 8,
            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
            EGL_NONE
        };
        
        if (eglChooseConfig(display, configAttrs, &config, configSize, &configSize) != EGL_TRUE) {
            printf("❌ 选择EGL配置失败!\n");
            return -1;
        }
        
        // 创建Surface
        surface = eglCreateWindowSurface(display, config, window, NULL);
        if (surface == EGL_NO_SURFACE) {
            printf("❌ 创建EGL Surface失败!\n");
            return -1;
        }
        
        // 创建Context
        EGLint contextAttrs[] = {
            EGL_CONTEXT_CLIENT_VERSION, 2,
            EGL_NONE
        };
        context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttrs);
        if (context == EGL_NO_CONTEXT) {
            printf("❌ 创建EGL Context失败!\n");
            return -1;
        }
        
        // 激活Context
        if (eglMakeCurrent(display, surface, surface, context) != EGL_TRUE) {
            printf("❌ 激活EGL Context失败!\n");
            return -1;
        }
        
        printf("✅ EGL初始化成功!\n");
        return 0;
    }
    
    virtual void close() override {
        if (display != EGL_NO_DISPLAY) {
            eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
            eglDestroyContext(display, context);
            eglDestroySurface(display, surface);
            eglTerminate(display);
            display = EGL_NO_DISPLAY;
        }
    }
    
    virtual void draw() override {
        glClear(GL_COLOR_BUFFER_BIT);
    }
    
    virtual void swapBuffer() override {
        eglSwapBuffers(display, surface);
    }
};

1.3 Android 8.0兼容性问题解决

问题:Android 8.0以上OpenGLES不能播放

c 复制代码
// 解决方案1:设置Surface属性
class XEGL : public IGL {
public:
    virtual int init(ANativeWindow *window) override {
        // Android 8.0+ 需要设置Surface属性
        ANativeWindow_setBuffersGeometry(window, 0, 0, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM);
        
        // ... 其余初始化代码
    }
};

// 解决方案2:使用PixelFormat
virtual int init(ANativeWindow *window) override {
    // 设置格式
    int format = PIXEL_FORMAT_RGBA;
    ANativeWindow_setBuffersGeometry(window, 0, 0, format);
    
    // ... 其余初始化代码
}

🎨 二、Shader模块

2.1 XShader初始化

就像:厨师调味

c 复制代码
class XShader {
private:
    GLuint program;
    GLuint vertexShader;
    GLuint fragmentShader;
    
    // 顶点着色器
    const char *vertexCode = 
        "attribute vec4 aPosition;\n"
        "attribute vec2 aTextCoord;\n"
        "varying vec2 vTextCoord;\n"
        "void main() {\n"
        "    gl_Position = aPosition;\n"
        "    vTextCoord = aTextCoord;\n"
        "}\n";
    
    // 片元着色器(YUV420P转RGB)
    const char *fragmentCode = 
        "precision mediump float;\n"
        "varying vec2 vTextCoord;\n"
        "uniform sampler2D yTexture;\n"
        "uniform sampler2D uTexture;\n"
        "uniform sampler2D vTexture;\n"
        "void main() {\n"
        "    float y = texture2D(yTexture, vTextCoord).r;\n"
        "    float u = texture2D(uTexture, vTextCoord).r - 0.5;\n"
        "    float v = texture2D(vTexture, vTextCoord).r - 0.5;\n"
        "    \n"
        "    float r = y + 1.403 * v;\n"
        "    float g = y - 0.344 * u - 0.714 * v;\n"
        "    float b = y + 1.770 * u;\n"
        "    \n"
        "    gl_FragColor = vec4(r, g, b, 1.0);\n"
        "}\n";
    
public:
    int init() {
        // 1️⃣ 创建顶点着色器
        vertexShader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertexShader, 1, &vertexCode, NULL);
        glCompileShader(vertexShader);
        
        // 2️⃣ 创建片元着色器
        fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragmentShader, 1, &fragmentCode, NULL);
        glCompileShader(fragmentShader);
        
        // 3️⃣ 创建程序
        program = glCreateProgram();
        glAttachShader(program, vertexShader);
        glAttachShader(program, fragmentShader);
        glLinkProgram(program);
        
        // 4️⃣ 使用程序
        glUseProgram(program);
        
        printf("✅ Shader初始化成功!\n");
        return 0;
    }
};

2.2 NV21和NV12格式Shader

问题:不同手机返回的YUV格式不同

c 复制代码
// NV21格式Shader
const char *fragmentCodeNV21 =
    "precision mediump float;\n"
    "varying vec2 vTextCoord;\n"
    "uniform sampler2D yTexture;\n"
    "uniform sampler2D uvTexture;\n"
    "void main() {\n"
    "    float y = texture2D(yTexture, vTextCoord).r;\n"
    "    vec2 uv = texture2D(uvTexture, vTextCoord).ar;\n"
    "    float u = uv.r - 0.5;\n"
    "    float v = uv.g - 0.5;\n"
    "    \n"
    "    float r = y + 1.403 * v;\n"
    "    float g = y - 0.344 * u - 0.714 * v;\n"
    "    float b = y + 1.770 * u;\n"
    "    \n"
    "    gl_FragColor = vec4(r, g, b, 1.0);\n"
    "}\n";

// 根据格式选择Shader
void initShader(int format) {
    if (format == X_DATA_TYPE_YUV420P) {
        loadShader(vertexCode, fragmentCode420P);
    } else if (format == X_DATA_TYPE_NV12) {
        loadShader(vertexCode, fragmentCodeNV12);
    } else if (format == X_DATA_TYPE_NV21) {
        loadShader(vertexCode, fragmentCodeNV21);
    }
}

🖼️ 三、纹理模块

3.1 XTexture初始化

就像:准备食材

c 复制代码
class XTexture {
private:
    GLuint textures[3];  // y,u,v纹理
    
public:
    int init() {
        // 创建3个纹理(Y、U、V)
        glGenTextures(3, textures);
        
        // 设置Y纹理
        glBindTexture(GL_TEXTURE_2D, textures[0]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        
        // 设置U纹理
        glBindTexture(GL_TEXTURE_2D, textures[1]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        
        // 设置V纹理
        glBindTexture(GL_TEXTURE_2D, textures[2]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        
        printf("✅ 纹理初始化成功!\n");
        return 0;
    }
    
    void update(AVFrame *frame) {
        // 更新Y纹理
        glBindTexture(GL_TEXTURE_2D, textures[0]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE,
                     frame->width, frame->height, 0,
                     GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->data[0]);
        
        // 更新U纹理
        glBindTexture(GL_TEXTURE_2D, textures[1]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE,
                     frame->width/2, frame->height/2, 0,
                     GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->data[1]);
        
        // 更新V纹理
        glBindTexture(GL_TEXTURE_2D, textures[2]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE,
                     frame->width/2, frame->height/2, 0,
                     GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->data[2]);
    }
};

3.2 GLVideoView完整实现

c 复制代码
class GLVideoView : public IVideoView {
private:
    XShader *shader = NULL;
    XTexture *texture = NULL;
    IEGL *egl = NULL;
    ANativeWindow *window = NULL;
    int width = 0;
    int height = 0;
    
public:
    virtual int init(int w, int h, ANativeWindow *win) override {
        width = w;
        height = h;
        window = win;
        
        // 1️⃣ 初始化EGL
        egl = new XEGL();
        if (egl->init(window) != 0) {
            printf("❌ EGL初始化失败!\n");
            return -1;
        }
        
        // 2️⃣ 初始化Shader
        shader = new XShader();
        if (shader->init() != 0) {
            printf("❌ Shader初始化失败!\n");
            return -1;
        }
        
        // 3️⃣ 初始化纹理
        texture = new XTexture();
        if (texture->init() != 0) {
            printf("❌ 纹理初始化失败!\n");
            return -1;
        }
        
        // 4️⃣ 设置视口
        glViewport(0, 0, width, height);
        
        printf("✅ GLVideoView初始化成功!\n");
        return 0;
    }
    
    virtual void display(AVFrame *frame) override {
        if (!frame) return;
        
        // 1️⃣ 更新纹理数据
        texture->update(frame);
        
        // 2️⃣ 绘制
        shader->draw();
        
        // 3️⃣ 交换缓冲
        egl->swapBuffer();
    }
    
    virtual void close() override {
        if (texture) { delete texture; texture = NULL; }
        if (shader) { delete shader; shader = NULL; }
        if (egl) { delete egl; egl = NULL; }
    }
};

🔊 四、音频播放模块

4.1 IResample音频重采样

就像:调整食材口味

c 复制代码
class IResample {
public:
    virtual int init(AVCodecParameters *codec) = 0;
    virtual int resample(AVFrame *frame, uint8_t *out) = 0;
};

class FFResample : public IResample {
private:
    SwrContext *swrCtx = NULL;
    
    // 输入参数
    int inSampleRate = 0;
    AVSampleFormat inFormat = AV_SAMPLE_FMT_NONE;
    uint64_t inChannelLayout = 0;
    
    // 输出参数
    int outSampleRate = 44100;
    AVSampleFormat outFormat = AV_SAMPLE_FMT_S16;
    uint64_t outChannelLayout = AV_CH_LAYOUT_STEREO;
    
public:
    virtual int init(AVCodecParameters *codec) override {
        inSampleRate = codec->sample_rate;
        inFormat = (AVSampleFormat)codec->format;
        inChannelLayout = codec->channel_layout;
        
        // 创建重采样上下文
        swrCtx = swr_alloc();
        
        // 设置输入参数
        av_opt_set_int(swrCtx, "in_channel_layout", inChannelLayout, 0);
        av_opt_set_int(swrCtx, "in_sample_rate", inSampleRate, 0);
        av_opt_set_sample_fmt(swrCtx, "in_sample_fmt", inFormat, 0);
        
        // 设置输出参数
        av_opt_set_int(swrCtx, "out_channel_layout", outChannelLayout, 0);
        av_opt_set_int(swrCtx, "out_sample_rate", outSampleRate, 0);
        av_opt_set_sample_fmt(swrCtx, "out_sample_fmt", outFormat, 0);
        
        // 初始化
        if (swr_init(swrCtx) < 0) {
            printf("❌ 重采样初始化失败!\n");
            return -1;
        }
        
        printf("✅ 重采样初始化成功!\n");
        return 0;
    }
    
    virtual int resample(AVFrame *frame, uint8_t *out) override {
        if (!swrCtx) return 0;
        
        // 重采样
        int samples = swr_convert(swrCtx, &out, frame->nb_samples,
                                  (const uint8_t**)frame->data, frame->nb_samples);
        
        return samples * 2 * 2;  // 返回字节数
    }
};

4.2 IAudioPlay音频播放接口

c 复制代码
class IAudioPlay {
public:
    virtual int init(int sampleRate, int channels) = 0;
    virtual void update(uint8_t *data, int size) = 0;
    virtual void start() = 0;
    virtual void stop() = 0;
    virtual void close() = 0;
};

4.3 SLAudioPlay音频播放实现

就像:服务员端菜

c 复制代码
class SLAudioPlay : public IAudioPlay {
private:
    SLObjectItf engineObject = NULL;
    SLEngineItf engineEngine = NULL;
    SLObjectItf outputMixObject = NULL;
    SLObjectItf playerObject = NULL;
    SLPlayItf playerPlay = NULL;
    SLBufferQueueItf bufferQueue = NULL;
    
    uint8_t *buffer = NULL;
    int bufferSize = 0;
    
public:
    virtual int init(int sampleRate, int channels) override {
        bufferSize = 4096;
        buffer = (uint8_t*)malloc(bufferSize);
        
        // 1️⃣ 创建OpenSLES引擎
        slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
        (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
        (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
        
        // 2️⃣ 创建混音器
        (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL);
        (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
        
        // 3️⃣ 配置PCM格式
        SLDataFormat_PCM pcmFormat = {
            SL_DATAFORMAT_PCM,
            2,  // 声道数
            SL_SAMPLINGRATE_44_1,
            SL_PCMSAMPLEFORMAT_FIXED_16,
            SL_PCMSAMPLEFORMAT_FIXED_16,
            SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
            SL_BYTEORDER_LITTLEENDIAN
        };
        
        // 4️⃣ 配置缓冲队列
        SLDataLocator_AndroidSimpleBufferQueue bufferQueueLocator = {
            SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
            10
        };
        
        SLDataSource dataSource = {&bufferQueueLocator, &pcmFormat};
        SLDataSink dataSink = {&outputMixLocator, outputMixObject};
        
        // 5️⃣ 创建播放器
        (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject,
                                           &dataSource, &dataSink, 1, ids, req);
        (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
        (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);
        (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &bufferQueue);
        
        printf("✅ SLAudioPlay初始化成功!\n");
        return 0;
    }
    
    virtual void update(uint8_t *data, int size) override {
        // 将音频数据压入缓冲队列
        (*bufferQueue)->Enqueue(bufferQueue, data, size);
    }
    
    virtual void start() override {
        (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
    }
    
    virtual void stop() override {
        (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_STOPPED);
    }
    
    virtual void close() override {
        if (playerObject) {
            (*playerObject)->Destroy(playerObject);
            playerObject = NULL;
        }
        if (outputMixObject) {
            (*outputMixObject)->Destroy(outputMixObject);
            outputMixObject = NULL;
        }
        if (engineObject) {
            (*engineObject)->Destroy(engineObject);
            engineObject = NULL;
        }
        if (buffer) {
            free(buffer);
            buffer = NULL;
        }
    }
};

📋 五、开发阶段对照表

根据子目录视频标题整理,以下是完整的12个开发阶段对照:

阶段 视频标题 对应章节 关键实现
1 IVideoView显示模块架构讲解和代码创建 一.1.1 IVideoView接口设计、GLVideoView实现
2 IEGL类初始化封装java部分添加XPlay窗口类 一.2 XEGL初始化、ANativeWindow封装
3 XShader基于yuv420p的初始化Init代码完成并测 二.1 XShader初始化、YUV420P转RGB
4 XTexture初始化并完成GLVideoView的调用 三.1 XTexture纹理创建、update更新
5 GLVideoView完成使用shader显示视频 三.2 GLVideoView完整实现、display渲染
6 完成音频重采样IResample模块初始化代码 四.1 FFResample初始化、SwrContext配置
7 完成音频重采样IResample模块的处理功能 四.1 swr_convert重采样、格式转换
8 完成了IAudioPlay和SLAudioPlay的音频播放 四.2-4.3 IAudioPlay接口、SLAudioPlay实现
9 IAudioPlay的Update将音频压入缓冲队列并测试 四.3 Enqueue压入队列、缓冲管理
10 SLAudioPlay音频播放完成并测试对MP4文件音视频播 四.3、五 音视频同步播放测试
11 完成硬解码并完成NV21和NV12格式的shader显示编写 二.2 NV21/NV12格式Shader、多格式支持
12 解决android8.0下opengles不能播放的问题 一.3 ANativeWindow_setBuffersGeometry、兼容处理

🎯 六、核心要点速记

复制代码
🖼️ 视频显示流程:
   IVideoView → XEGL初始化 → XShader编译 → XTexture创建 → GLVideoView渲染

🔊 音频播放流程:
   IResample重采样 → IAudioPlay接口 → SLAudioPlay播放 → 缓冲队列

🎨 Shader格式支持:
   YUV420P(标准格式)
   NV12(部分手机)
   NV21(部分手机)

⚠️ Android兼容:
   Android 8.0+ 需要设置Surface属性
   ANativeWindow_setBuffersGeometry

播放流程口诀

复制代码
1️⃣ 视 - 视频解码得YUV
2️⃣ 纹 - 纹理更新数据
3️⃣ 着 - Shader渲染颜色
4️⃣ 显 - EGL显示画面

1️⃣ 音 - 音频解码得PCM
2️⃣ 重 - 重采样调整格式
3️⃣ 压 - 压入缓冲队列
4️⃣ 播 - OpenSLES播放

本文档整理于 2026-06-15# 视频播放器项目实战-音视频播放

简单易懂版 | 图文结合 | 生动形象


🎬 先打个比方:播放就像电影院放映!

想象电影院的放映场景:

markdown 复制代码
🎥 电影院放映
   │
   ├── 🖼️ 视频播放:放映电影画面
   │   ├── 格式转换:YUV转RGB
   │   ├── OpenGL渲染:显示到屏幕
   │   ├── 双缓冲:平滑切换画面
   │   └── 帧率控制:稳定播放速度
   │
   ├── 🔊 音频播放:播放电影声音
   │   ├── 重采样:调整音频格式
   │   ├── OpenSLES播放:输出到扬声器
   │   ├── 缓冲队列:连续播放声音
   │   └── 音量控制:调节音量大小
   │
   └── 🎯 同步控制:画面和声音同步
       ├── 时间戳对齐:确保同步
       ├── 丢帧策略:处理延迟
       └── 缓冲管理:平衡播放

记住这个比喻,你就懂了!


🖼️ 一、视频播放模块

1.1 视频播放流程

就像:放映电影画面

复制代码
┌─────────────────────────────────────────────────────┐
│              视频播放流程                            │
├─────────────────────────────────────────────────────┤
│                                                     │
│   📦 YUV数据(解码输出)                             │
│       │                                             │
│       │ 格式转换(sws_scale)                        │
│       ▼                                             │
│   🎨 RGB数据                                         │
│       │                                             │
│       │ OpenGL渲染                                   │
│       ▼                                             │
│   🖥️ 屏幕显示                                        │
│       │                                             │
│       │ 双缓冲交换                                   │
│       ▼                                             │
│   👁️ 用户看到画面                                    │
│                                                     │
└─────────────────────────────────────────────────────┘

1.2 视频渲染器实现

c 复制代码
#include <GLES2/gl2.h>
#include <EGL/egl.h>

class VideoRenderer {
private:
    EGLDisplay eglDisplay;
    EGLSurface eglSurface;
    EGLContext eglContext;
    
    GLuint program;
    GLuint yTexture, uTexture, vTexture;
    
    int width, height;
    
    // 顶点着色器
    const char *vertexShaderSource = 
        "attribute vec4 aPosition;\n"
        "attribute vec2 aTexCoord;\n"
        "varying vec2 vTexCoord;\n"
        "void main() {\n"
        "    gl_Position = aPosition;\n"
        "    vTexCoord = aTexCoord;\n"
        "}\n";
    
    // 片元着色器(YUV转RGB)
    const char *fragmentShaderSource = 
        "precision mediump float;\n"
        "varying vec2 vTexCoord;\n"
        "uniform sampler2D yTexture;\n"
        "uniform sampler2D uTexture;\n"
        "uniform sampler2D vTexture;\n"
        "void main() {\n"
        "    float y = texture2D(yTexture, vTexCoord).r;\n"
        "    float u = texture2D(uTexture, vTexCoord).r - 0.5;\n"
        "    float v = texture2D(vTexture, vTexCoord).r - 0.5;\n"
        "    float r = y + 1.403 * v;\n"
        "    float g = y - 0.344 * u - 0.714 * v;\n"
        "    float b = y + 1.770 * u;\n"
        "    gl_FragColor = vec4(r, g, b, 1.0);\n"
        "}\n";
    
public:
    // 初始化
    int init(int w, int h, ANativeWindow *window) {
        width = w;
        height = h;
        
        // 1️⃣ 初始化EGL
        initEGL(window);
        
        // 2️⃣ 创建Shader程序
        program = createProgram(vertexShaderSource, fragmentShaderSource);
        
        // 3️⃣ 创建纹理
        createTextures();
        
        // 4️⃣ 设置视口
        glViewport(0, 0, width, height);
        
        printf("✅ 视频渲染器初始化成功!\n");
        return 0;
    }
    
    // 渲染一帧
    void render(AVFrame *frame) {
        // 1️⃣ 更新纹理数据
        updateTexture(yTexture, frame->data[0], frame->width, frame->height);
        updateTexture(uTexture, frame->data[1], frame->width/2, frame->height/2);
        updateTexture(vTexture, frame->data[2], frame->width/2, frame->height/2);
        
        // 2️⃣ 清除屏幕
        glClear(GL_COLOR_BUFFER_BIT);
        
        // 3️⃣ 使用程序
        glUseProgram(program);
        
        // 4️⃣ 设置顶点
        setVertices();
        
        // 5️⃣ 设置纹理
        setTextures();
        
        // 6️⃣ 绘制
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        
        // 7️⃣ 交换缓冲区
        eglSwapBuffers(eglDisplay, eglSurface);
    }
    
    // 释放
    void release() {
        // 释放纹理
        glDeleteTextures(1, &yTexture);
        glDeleteTextures(1, &uTexture);
        glDeleteTextures(1, &vTexture);
        
        // 释放程序
        glDeleteProgram(program);
        
        // 释放EGL
        releaseEGL();
        
        printf("✅ 视频渲染器已释放!\n");
    }
    
private:
    // 初始化EGL
    void initEGL(ANativeWindow *window) {
        // 获取显示
        eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
        eglInitialize(eglDisplay, NULL, NULL);
        
        // 选择配置
        EGLConfig config;
        EGLint configAttrs[] = {
            EGL_RED_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE, 8,
            EGL_ALPHA_SIZE, 8,
            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
            EGL_NONE
        };
        eglChooseConfig(eglDisplay, configAttrs, &config, 1, NULL);
        
        // 创建Surface
        eglSurface = eglCreateWindowSurface(eglDisplay, config, window, NULL);
        
        // 创建Context
        EGLint contextAttrs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
        eglContext = eglCreateContext(eglDisplay, config, NULL, contextAttrs);
        
        // 绑定Context
        eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
    }
    
    // 创建纹理
    void createTextures() {
        glGenTextures(1, &yTexture);
        glGenTextures(1, &uTexture);
        glGenTextures(1, &vTexture);
        
        // 设置纹理参数
        glBindTexture(GL_TEXTURE_2D, yTexture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        
        glBindTexture(GL_TEXTURE_2D, uTexture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        
        glBindTexture(GL_TEXTURE_2D, vTexture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    }
    
    // 更新纹理
    void updateTexture(GLuint texture, uint8_t *data, int w, int h) {
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w, h, 0, 
                     GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
    }
    
    // 设置顶点
    void setVertices() {
        GLfloat vertices[] = {
            -1.0f, -1.0f,
             1.0f, -1.0f,
            -1.0f,  1.0f,
             1.0f,  1.0f
        };
        
        GLfloat texCoords[] = {
            0.0f, 1.0f,
            1.0f, 1.0f,
            0.0f, 0.0f,
            1.0f, 0.0f
        };
        
        GLint aPosition = glGetAttribLocation(program, "aPosition");
        GLint aTexCoord = glGetAttribLocation(program, "aTexCoord");
        
        glEnableVertexAttribArray(aPosition);
        glVertexAttribPointer(aPosition, 2, GL_FLOAT, GL_FALSE, 0, vertices);
        
        glEnableVertexAttribArray(aTexCoord);
        glVertexAttribPointer(aTexCoord, 2, GL_FLOAT, GL_FALSE, 0, texCoords);
    }
    
    // 设置纹理
    void setTextures() {
        GLint yLoc = glGetUniformLocation(program, "yTexture");
        GLint uLoc = glGetUniformLocation(program, "uTexture");
        GLint vLoc = glGetUniformLocation(program, "vTexture");
        
        glUniform1i(yLoc, 0);
        glUniform1i(uLoc, 1);
        glUniform1i(vLoc, 2);
        
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, yTexture);
        
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, uTexture);
        
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, vTexture);
    }
};

🔊 二、音频播放模块

2.1 音频播放流程

就像:播放电影声音

复制代码
┌─────────────────────────────────────────────────────┐
│              音频播放流程                            │
├─────────────────────────────────────────────────────┤
│                                                     │
│   📦 PCM数据(解码输出)                             │
│       │                                             │
│       │ 重采样(swr_convert)                        │
│       ▼                                             │
│   🎵 目标格式PCM                                     │
│       │                                             │
│       │ 放入缓冲队列                                 │
│       ▼                                             │
│   📦 OpenSLES缓冲队列                                │
│       │                                             │
│       │ 播放                                         │
│       ▼                                             │
│   🔊 扬声器输出                                      │
│                                                     │
└─────────────────────────────────────────────────────┘

2.2 音频播放器实现

c 复制代码
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>

class AudioPlayer {
private:
    SLObjectItf engineObject;
    SLEngineItf engineEngine;
    SLObjectItf outputMixObject;
    SLObjectItf playerObject;
    SLPlayItf playerPlay;
    SLBufferQueueItf bufferQueue;
    
    SwrContext *swrCtx;
    
    uint8_t *buffer;
    int bufferSize;
    
public:
    // 初始化
    int init(int sampleRate, int channels) {
        // 1️⃣ 初始化OpenSLES
        initOpenSLES();
        
        // 2️⃣ 初始化重采样
        initResampler(sampleRate, channels);
        
        // 3️⃣ 分配缓冲区
        bufferSize = 4096;
        buffer = (uint8_t*)malloc(bufferSize);
        
        printf("✅ 音频播放器初始化成功!\n");
        return 0;
    }
    
    // 播放音频帧
    void play(AVFrame *frame) {
        // 1️⃣ 重采样
        int outSamples = swr_convert(swrCtx, &buffer, bufferSize/4,
                                     (const uint8_t**)frame->data, frame->nb_samples);
        
        // 2️⃣ 计算大小
        int outSize = outSamples * 2 * 2;  // 采样数 × 声道数 × 字节数
        
        // 3️⃣ 放入队列
        (*bufferQueue)->Enqueue(bufferQueue, buffer, outSize);
    }
    
    // 开始播放
    void start() {
        (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
        printf("✅ 开始播放音频!\n");
    }
    
    // 停止播放
    void stop() {
        (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_STOPPED);
        printf("✅ 停止播放音频!\n");
    }
    
    // 释放
    void release() {
        // 释放OpenSLES
        if (playerObject) (*playerObject)->Destroy(playerObject);
        if (outputMixObject) (*outputMixObject)->Destroy(outputMixObject);
        if (engineObject) (*engineObject)->Destroy(engineObject);
        
        // 释放重采样
        swr_free(&swrCtx);
        
        // 释放缓冲区
        free(buffer);
        
        printf("✅ 音频播放器已释放!\n");
    }
    
private:
    // 初始化OpenSLES
    void initOpenSLES() {
        // 创建引擎
        slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
        (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
        (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
        
        // 创建混音器
        (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL);
        (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
        
        // 配置PCM格式
        SLDataFormat_PCM pcmFormat = {
            SL_DATAFORMAT_PCM,
            2,
            SL_SAMPLINGRATE_44_1,
            SL_PCMSAMPLEFORMAT_FIXED_16,
            SL_PCMSAMPLEFORMAT_FIXED_16,
            SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
            SL_BYTEORDER_LITTLEENDIAN
        };
        
        // 配置缓冲队列
        SLDataLocator_AndroidSimpleBufferQueue bufferQueueLocator = {
            SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
            10
        };
        
        SLDataSource dataSource = {&bufferQueueLocator, &pcmFormat};
        
        // 配置输出
        SLDataLocator_OutputMix outputMixLocator = {
            SL_DATALOCATOR_OUTPUTMIX,
            outputMixObject
        };
        
        SLDataSink dataSink = {&outputMixLocator, NULL};
        
        // 创建播放器
        SLInterfaceID ids[] = {SL_IID_BUFFERQUEUE};
        SLboolean req[] = {SL_BOOLEAN_TRUE};
        
        (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject, 
                                           &dataSource, &dataSink, 1, ids, req);
        (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
        
        // 获取接口
        (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);
        (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &bufferQueue);
    }
    
    // 初始化重采样
    void initResampler(int sampleRate, int channels) {
        swrCtx = swr_alloc();
        
        av_opt_set_int(swrCtx, "in_channel_layout", 
                       channels == 2 ? AV_CH_LAYOUT_STEREO : AV_CH_LAYOUT_MONO, 0);
        av_opt_set_int(swrCtx, "in_sample_rate", sampleRate, 0);
        av_opt_set_sample_fmt(swrCtx, "in_sample_fmt", AV_SAMPLE_FMT_FLTP, 0);
        
        av_opt_set_int(swrCtx, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
        av_opt_set_int(swrCtx, "out_sample_rate", 44100, 0);
        av_opt_set_sample_fmt(swrCtx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
        
        swr_init(swrCtx);
    }
};

🎯 三、播放控制模块

3.1 播放状态管理

c 复制代码
enum PlayState {
    STATE_IDLE,
    STATE_PLAYING,
    STATE_PAUSED,
    STATE_STOPPED
};

class PlayController {
private:
    PlayState state = STATE_IDLE;
    
public:
    void play() {
        if (state == STATE_PAUSED || state == STATE_STOPPED) {
            state = STATE_PLAYING;
            printf("▶️ 开始播放\n");
        }
    }
    
    void pause() {
        if (state == STATE_PLAYING) {
            state = STATE_PAUSED;
            printf("⏸️ 已暂停\n");
        }
    }
    
    void stop() {
        state = STATE_STOPPED;
        printf("⏹️ 已停止\n");
    }
    
    PlayState getState() {
        return state;
    }
};

3.2 音量控制

c 复制代码
class VolumeController {
private:
    int volume = 100;  // 0-100
    bool muted = false;
    
public:
    void setVolume(int vol) {
        volume = vol;
        printf("🔊 音量:%d\n", volume);
    }
    
    void mute() {
        muted = true;
        printf("🔇 已静音\n");
    }
    
    void unmute() {
        muted = false;
        printf("🔊 已取消静音\n");
    }
    
    int getVolume() {
        return muted ? 0 : volume;
    }
};

📊 四、播放流程图

4.1 完整播放流程

objectivec 复制代码
┌─────────────────────────────────────────────────────┐
│              完整播放流程                            │
├─────────────────────────────────────────────────────┤
│                                                     │
│   📁 视频文件                                        │
│       │                                             │
│       │ 解封装                                       │
│       ▼                                             │
│   📦 数据包(AVPacket)                              │
│       │                                             │
│       ├─── 视频包 ────┐                              │
│       │              │                              │
│       │              ▼                              │
│       │         视频解码                              │
│       │              │                              │
│       │              ▼                              │
│       │         YUV帧                                │
│       │              │                              │
│       │              ▼                              │
│       │         OpenGL渲染                           │
│       │              │                              │
│       │              ▼                              │
│       │         🖥️ 屏幕显示                          │
│       │                                             │
│       ├─── 音频包 ────┐                              │
│       │              │                              │
│       │              ▼                              │
│       │         音频解码                              │
│       │              │                              │
│       │              ▼                              │
│       │         PCM帧                                │
│       │              │                              │
│       │              ▼                              │
│       │         重采样                                │
│       │              │                              │
│       │              ▼                              │
│       │         OpenSLES播放                         │
│       │              │                              │
│       │              ▼                              │
│       │         🔊 扬声器输出                        │
│                                                     │
└─────────────────────────────────────────────────────┘

🎯 五、总结

核心要点

模块 作用 生活类比
视频渲染 显示画面 放映电影
音频播放 播放声音 扬声器输出
播放控制 控制播放状态 播放按钮
音量控制 调节音量 音量旋钮

播放流程总结

复制代码
🎬 音视频播放完整流程:

1️⃣ 解封装读取数据包
2️⃣ 视频包 → 解码 → YUV → OpenGL渲染 → 屏幕
3️⃣ 音频包 → 解码 → PCM → 重采样 → OpenSLES播放 → 扬声器
4️⃣ 同步控制确保画面和声音同步

速记口诀

复制代码
1️⃣ 解 - 解封装读取
2️⃣ 解 - 解码数据
3️⃣ 渲 - 渲染画面
4️⃣ 播 - 播放声音
5️⃣ 控 - 控制播放

本文档整理于 2026-06-15

相关推荐
iOStanhaitao19 小时前
6.第一个c++安卓程序编译运行
音视频开发
音视频牛哥8 天前
不只是等待 IDR:SmartMediaKit 播放器对 H.264 GDR 码流的完整适配实践
音视频开发·视频编码·直播
三木彤15 天前
语音转文本python
音视频开发
鹧鸪晏19 天前
Android GLSurfaceView 完全指南
android·音视频开发
ltlovezh22 天前
AAC 元数据:ADTS 与 ASC 的区别、转换和常见坑
后端·ffmpeg·音视频开发
MonkeyKing24 天前
iOS 音频实战:边播边缓存、预加载与断点续播完整实现
音视频开发
11年老程序猿在线搬砖24 天前
2026年语聊APP开发费用深度拆解:从MVP到百万并发的预算清单
音视频开发·创业·技术选型·社交app开发·语聊app·开发费用
码流怪侠1 个月前
Android MediaCodec 全面详解:从入门到精通
android·程序员·音视频开发
L_Xian1 个月前
StarrySky重新维护了,摆烂了一段时间,想想还是搞搞吧。
android·github·音视频开发