diff --git a/ffmpeg/CMakeLists.txt b/ffmpeg/CMakeLists.txt index 8f2dad0..b82a580 100644 --- a/ffmpeg/CMakeLists.txt +++ b/ffmpeg/CMakeLists.txt @@ -26,6 +26,8 @@ set(PROJECT_SOURCES subtitle/assdata.hpp videorender/openglrender.cc videorender/openglrender.hpp + videorender/openglshaderprogram.cc + videorender/openglshaderprogram.hpp videorender/videopreviewwidget.cc videorender/videopreviewwidget.hpp videorender/videorender.cc diff --git a/ffmpeg/videorender/openglrender.cc b/ffmpeg/videorender/openglrender.cc index ad2ed1c..5174e79 100644 --- a/ffmpeg/videorender/openglrender.cc +++ b/ffmpeg/videorender/openglrender.cc @@ -1,4 +1,5 @@ #include "openglrender.hpp" +#include "openglshaderprogram.hpp" #include #include @@ -6,8 +7,6 @@ #include #include -#include -#include extern "C" { #include @@ -25,15 +24,17 @@ class OpenglRender::OpenglRenderPrivate OpenglRender *q_ptr; - QScopedPointer programPtr; + GLuint vao = 0; // 顶点数组对象,任何随后的顶点属性调用都会储存在这个VAO中,一个VAO可以有多个VBO + + QScopedPointer programPtr; GLuint textureY; GLuint textureU; GLuint textureV; GLuint textureUV; GLuint textureRGBA; - QOpenGLBuffer vbo = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); - QOpenGLBuffer ebo = QOpenGLBuffer(QOpenGLBuffer::IndexBuffer); - GLuint vao = 0; // 顶点数组对象,任何随后的顶点属性调用都会储存在这个VAO中,一个VAO可以有多个VBO + // sub + QScopedPointer subProgramPtr; + GLuint textureSub; const QVector supportFormats = {AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUYV422, AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24, @@ -64,14 +65,15 @@ OpenglRender::~OpenglRender() return; } makeCurrent(); - d_ptr->programPtr.reset(); - d_ptr->vbo.destroy(); - d_ptr->ebo.destroy(); glDeleteVertexArrays(1, &d_ptr->vao); + d_ptr->programPtr.reset(); + d_ptr->subProgramPtr.reset(); glDeleteTextures(1, &d_ptr->textureY); glDeleteTextures(1, &d_ptr->textureU); glDeleteTextures(1, &d_ptr->textureV); glDeleteTextures(1, &d_ptr->textureUV); + glDeleteTextures(1, &d_ptr->textureRGBA); + glDeleteTextures(1, &d_ptr->textureSub); doneCurrent(); } @@ -187,6 +189,17 @@ void OpenglRender::initTexture() glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } +void OpenglRender::initSubTexture() +{ + d_ptr->subProgramPtr->setUniformValue("tex", 0); + glGenTextures(1, &d_ptr->textureSub); + glBindTexture(GL_TEXTURE_2D, d_ptr->textureSub); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_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); +} + void OpenglRender::resizeViewport() { auto avframe = d_ptr->framePtr->avFrame(); @@ -204,6 +217,169 @@ void OpenglRender::resizeViewport() rect.height() * devicePixelRatio); } +void OpenglRender::setColorSpace() +{ + auto avFrame = d_ptr->framePtr->avFrame(); + switch (avFrame->colorspace) { + case AVCOL_SPC_BT470BG: + case AVCOL_SPC_SMPTE170M: + d_ptr->programPtr->setUniformValue("offset", ColorSpace::kBT601Offset); + d_ptr->programPtr->setUniformValue("colorConversion", QMatrix3x3(ColorSpace::kBT601Matrix)); + break; + case AVCOL_SPC_BT2020_NCL: + case AVCOL_SPC_BT2020_CL: + d_ptr->programPtr->setUniformValue("offset", ColorSpace::kBT2020ffset); + d_ptr->programPtr->setUniformValue("colorConversion", QMatrix3x3(ColorSpace::kBT2020Matrix)); + break; + //case AVCOL_SPC_BT709: + default: + d_ptr->programPtr->setUniformValue("offset", ColorSpace::kBT7090ffset); + d_ptr->programPtr->setUniformValue("colorConversion", QMatrix3x3(ColorSpace::kBT709Matrix)); + break; + } +} + +void OpenglRender::paintVideoFrame() +{ + auto format = d_ptr->framePtr->avFrame()->format; + //qDebug() << format; + // 绑定纹理 + switch (format) { + case AV_PIX_FMT_YUV420P: updateYUV420P(); break; + case AV_PIX_FMT_YUYV422: updateYUYV422(); break; + case AV_PIX_FMT_RGB24: + case AV_PIX_FMT_BGR24: updateRGB(); break; + case AV_PIX_FMT_YUV422P: updateYUV422P(); break; + case AV_PIX_FMT_YUV444P: updateYUV444P(); break; + case AV_PIX_FMT_YUV410P: updateYUV410P(); break; + case AV_PIX_FMT_YUV411P: updateYUV411P(); break; + case AV_PIX_FMT_UYVY422: updateUYVY422(); break; + case AV_PIX_FMT_BGR8: updateRGB8(GL_UNSIGNED_BYTE_2_3_3_REV); break; + case AV_PIX_FMT_RGB8: updateRGB8(GL_UNSIGNED_BYTE_3_3_2); break; + case AV_PIX_FMT_NV12: + case AV_PIX_FMT_NV21: updateNV12(); break; + case AV_PIX_FMT_ARGB: + case AV_PIX_FMT_RGBA: + case AV_PIX_FMT_ABGR: + case AV_PIX_FMT_BGRA: + case AV_PIX_FMT_0RGB: + case AV_PIX_FMT_RGB0: + case AV_PIX_FMT_0BGR: + case AV_PIX_FMT_BGR0: updateRGBA(); break; + case AV_PIX_FMT_YUV420P10LE: updateYUV420P10LE(); break; + case AV_PIX_FMT_P010LE: updateP010LE(); break; + default: break; + } + d_ptr->programPtr->bind(); // 绑定着色器 + d_ptr->programPtr->setUniformValue("format", format); + + setColorSpace(); + draw(); + d_ptr->programPtr->release(); +} + +void OpenglRender::paintSubTitleFrame() +{ + if (d_ptr->subTitleFramePtr.isNull() || d_ptr->framePtr.isNull()) { + return; + } + if (d_ptr->subTitleFramePtr->pts() > d_ptr->framePtr->pts() + || (d_ptr->subTitleFramePtr->pts() + d_ptr->subTitleFramePtr->duration()) + < d_ptr->framePtr->pts()) { + return; + } + auto img = d_ptr->subTitleFramePtr->image(); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, d_ptr->textureSub); + glTexImage2D(GL_TEXTURE_2D, + 0, + GL_RGBA, + img.width(), + img.height(), + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + img.constBits()); + glEnable(GL_BLEND); + d_ptr->subProgramPtr->bind(); + draw(); + d_ptr->subProgramPtr->release(); + glDisable(GL_BLEND); +} + +void OpenglRender::clear() +{ + // 将窗口的位平面区域(背景)设置为先前由glClearColor、glClearDepth和选择的值 + glClearColor(d_ptr->backgroundColor.redF(), + d_ptr->backgroundColor.greenF(), + d_ptr->backgroundColor.blueF(), + d_ptr->backgroundColor.alphaF()); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +void OpenglRender::draw() +{ + glBindVertexArray(d_ptr->vao); // 绑定VAO + glDrawElements( + GL_TRIANGLES, // 绘制的图元类型 + 6, // 指定要渲染的元素数(点数) + GL_UNSIGNED_INT, // 指定索引中值的类型(indices) + nullptr); // 指定当前绑定到GL_ELEMENT_array_buffer目标的缓冲区的数据存储中数组中第一个索引的偏移量。 + glBindVertexArray(0); +} + +void OpenglRender::initializeGL() +{ + initializeOpenGLFunctions(); + + //glEnable(GL_DEPTH_TEST); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_STENCIL_TEST); + glDisable(GL_DEPTH_TEST); + glDisable(GL_DITHER); + clear(); + + glGenVertexArrays(1, &d_ptr->vao); + glBindVertexArray(d_ptr->vao); + + // 加载shader脚本程序 + d_ptr->programPtr.reset(new OpenGLShaderProgram(this)); + d_ptr->programPtr->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shader/video.vert"); + d_ptr->programPtr->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shader/video.frag"); + d_ptr->programPtr->link(); + d_ptr->programPtr->bind(); + d_ptr->programPtr->initVertex("aPos", "aTexCord"); + initTexture(); + d_ptr->programPtr->release(); + + d_ptr->subProgramPtr.reset(new OpenGLShaderProgram(this)); + d_ptr->subProgramPtr->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shader/video.vert"); + d_ptr->subProgramPtr->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shader/sub.frag"); + d_ptr->subProgramPtr->link(); + d_ptr->subProgramPtr->bind(); + d_ptr->subProgramPtr->initVertex("aPos", "aTexCord"); + initSubTexture(); + d_ptr->subProgramPtr->release(); + + // 释放 + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); // 设置为零以破坏现有的顶点数组对象绑定} +} + +void OpenglRender::paintGL() +{ + clear(); + + if (d_ptr->framePtr.isNull()) { + return; + } + + resizeViewport(); + + paintVideoFrame(); + paintSubTitleFrame(); +} + void OpenglRender::updateYUV420P() { auto frame = d_ptr->framePtr->avFrame(); @@ -588,185 +764,4 @@ void OpenglRender::updateP010LE() frame->data[1]); } -void OpenglRender::setColorSpace() -{ - auto avFrame = d_ptr->framePtr->avFrame(); - switch (avFrame->colorspace) { - case AVCOL_SPC_BT470BG: - case AVCOL_SPC_SMPTE170M: - d_ptr->programPtr->setUniformValue("offset", ColorSpace::kBT601Offset); - d_ptr->programPtr->setUniformValue("colorConversion", QMatrix3x3(ColorSpace::kBT601Matrix)); - break; - case AVCOL_SPC_BT2020_NCL: - case AVCOL_SPC_BT2020_CL: - d_ptr->programPtr->setUniformValue("offset", ColorSpace::kBT2020ffset); - d_ptr->programPtr->setUniformValue("colorConversion", QMatrix3x3(ColorSpace::kBT2020Matrix)); - break; - //case AVCOL_SPC_BT709: - default: - d_ptr->programPtr->setUniformValue("offset", ColorSpace::kBT7090ffset); - d_ptr->programPtr->setUniformValue("colorConversion", QMatrix3x3(ColorSpace::kBT709Matrix)); - break; - } -} - -void OpenglRender::paintSubTitleFrame() -{ - if (d_ptr->subTitleFramePtr.isNull() || d_ptr->framePtr.isNull()) { - return; - } - if (d_ptr->subTitleFramePtr->pts() > d_ptr->framePtr->pts() - || (d_ptr->subTitleFramePtr->pts() + d_ptr->subTitleFramePtr->duration()) - < d_ptr->framePtr->pts()) { - return; - } - auto img = d_ptr->subTitleFramePtr->image(); - glActiveTexture(GL_TEXTURE4); - glBindTexture(GL_TEXTURE_2D, d_ptr->textureRGBA); - glTexImage2D(GL_TEXTURE_2D, - 0, - GL_RGBA, - img.width(), - img.height(), - 0, - GL_RGBA, - GL_UNSIGNED_BYTE, - img.constBits()); - glEnable(GL_BLEND); - d_ptr->programPtr->bind(); - d_ptr->programPtr->setUniformValue("format", 26); - glBindVertexArray(d_ptr->vao); - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr); - glBindVertexArray(0); - d_ptr->programPtr->release(); - glDisable(GL_BLEND); -} - -void OpenglRender::initializeGL() -{ - initializeOpenGLFunctions(); - - //glEnable(GL_DEPTH_TEST); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glClearColor(0.0, 0.0, 0.0, 1.0); - glClear(GL_COLOR_BUFFER_BIT); - glDisable(GL_STENCIL_TEST); - glDisable(GL_DEPTH_TEST); - glDisable(GL_DITHER); - // 加载shader脚本程序 - d_ptr->programPtr.reset(new QOpenGLShaderProgram(this)); - d_ptr->programPtr->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shader/video.vert"); - d_ptr->programPtr->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shader/video.frag"); - d_ptr->programPtr->link(); - d_ptr->programPtr->bind(); - - initVbo(); - initTexture(); - - d_ptr->programPtr->release(); -} - -void OpenglRender::paintGL() -{ - // 将窗口的位平面区域(背景)设置为先前由glClearColor、glClearDepth和选择的值 - glClearColor(d_ptr->backgroundColor.redF(), - d_ptr->backgroundColor.greenF(), - d_ptr->backgroundColor.blueF(), - d_ptr->backgroundColor.alphaF()); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - if (d_ptr->framePtr.isNull()) { - return; - } - - resizeViewport(); - - auto format = d_ptr->framePtr->avFrame()->format; - //qDebug() << format; - // 绑定纹理 - switch (format) { - case AV_PIX_FMT_YUV420P: updateYUV420P(); break; - case AV_PIX_FMT_YUYV422: updateYUYV422(); break; - case AV_PIX_FMT_RGB24: - case AV_PIX_FMT_BGR24: updateRGB(); break; - case AV_PIX_FMT_YUV422P: updateYUV422P(); break; - case AV_PIX_FMT_YUV444P: updateYUV444P(); break; - case AV_PIX_FMT_YUV410P: updateYUV410P(); break; - case AV_PIX_FMT_YUV411P: updateYUV411P(); break; - case AV_PIX_FMT_UYVY422: updateUYVY422(); break; - case AV_PIX_FMT_BGR8: updateRGB8(GL_UNSIGNED_BYTE_2_3_3_REV); break; - case AV_PIX_FMT_RGB8: updateRGB8(GL_UNSIGNED_BYTE_3_3_2); break; - case AV_PIX_FMT_NV12: - case AV_PIX_FMT_NV21: updateNV12(); break; - case AV_PIX_FMT_ARGB: - case AV_PIX_FMT_RGBA: - case AV_PIX_FMT_ABGR: - case AV_PIX_FMT_BGRA: - case AV_PIX_FMT_0RGB: - case AV_PIX_FMT_RGB0: - case AV_PIX_FMT_0BGR: - case AV_PIX_FMT_BGR0: updateRGBA(); break; - case AV_PIX_FMT_YUV420P10LE: updateYUV420P10LE(); break; - case AV_PIX_FMT_P010LE: updateP010LE(); break; - default: break; - } - d_ptr->programPtr->bind(); // 绑定着色器 - d_ptr->programPtr->setUniformValue("format", format); - - setColorSpace(); - - glBindVertexArray(d_ptr->vao); // 绑定VAO - glDrawElements( - GL_TRIANGLES, // 绘制的图元类型 - 6, // 指定要渲染的元素数(点数) - GL_UNSIGNED_INT, // 指定索引中值的类型(indices) - nullptr); // 指定当前绑定到GL_ELEMENT_array_buffer目标的缓冲区的数据存储中数组中第一个索引的偏移量。 - glBindVertexArray(0); - - d_ptr->programPtr->release(); - - paintSubTitleFrame(); -} - -void OpenglRender::initVbo() -{ - GLuint posAttr = GLuint(d_ptr->programPtr->attributeLocation("aPos")); - GLuint texCord = GLuint(d_ptr->programPtr->attributeLocation("aTexCord")); - - glGenVertexArrays(1, &d_ptr->vao); - glBindVertexArray(d_ptr->vao); - - float vertices[] = { - // positions // texture coords - 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top right - 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom right - -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom left - -1.0f, 1.0f, 0.0f, 0.0f, 1.0f // top left - }; - unsigned int indices[] = {0, 1, 3, 1, 2, 3}; - - d_ptr->vbo.destroy(); - d_ptr->vbo.create(); - d_ptr->vbo.bind(); - d_ptr->vbo.allocate(vertices, sizeof(vertices)); - - d_ptr->ebo.destroy(); - d_ptr->ebo.create(); - d_ptr->ebo.bind(); - d_ptr->ebo.allocate(indices, sizeof(indices)); - - d_ptr->programPtr->setAttributeBuffer(posAttr, GL_FLOAT, 0, 3, sizeof(float) * 5); - d_ptr->programPtr->enableAttributeArray(posAttr); - d_ptr->programPtr->setAttributeBuffer(texCord, GL_FLOAT, 3 * sizeof(float), 2, sizeof(float) * 5); - d_ptr->programPtr->enableAttributeArray(texCord); - - // 启用通用顶点属性数组 - // 属性索引是从调用glGetAttribLocation接收的,或者传递给glBindAttribLocation。 - glEnableVertexAttribArray(texCord); - - // 释放 - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); // 设置为零以破坏现有的顶点数组对象绑定} -} - } // namespace Ffmpeg diff --git a/ffmpeg/videorender/openglrender.hpp b/ffmpeg/videorender/openglrender.hpp index 62ae8f7..673de5b 100644 --- a/ffmpeg/videorender/openglrender.hpp +++ b/ffmpeg/videorender/openglrender.hpp @@ -34,11 +34,14 @@ class FFMPEG_EXPORT OpenglRender : public VideoRender, void updateSubTitleFrame(QSharedPointer frame) override; private: - void initVbo(); + void clear(); + void draw(); void initTexture(); + void initSubTexture(); void resizeViewport(); void setColorSpace(); + void paintVideoFrame(); void paintSubTitleFrame(); void updateYUV420P(); diff --git a/ffmpeg/videorender/openglshaderprogram.cc b/ffmpeg/videorender/openglshaderprogram.cc new file mode 100644 index 0000000..175bd0a --- /dev/null +++ b/ffmpeg/videorender/openglshaderprogram.cc @@ -0,0 +1,66 @@ +#include "OpenGLShaderProgram.hpp" + +#include + +namespace Ffmpeg { + +class OpenGLShaderProgram::OpenGLShaderProgramPrivate +{ +public: + explicit OpenGLShaderProgramPrivate(OpenGLShaderProgram *q) + : q_ptr(q) + {} + + OpenGLShaderProgram *q_ptr; + + QOpenGLBuffer vbo = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); + QOpenGLBuffer ebo = QOpenGLBuffer(QOpenGLBuffer::IndexBuffer); +}; + +OpenGLShaderProgram::OpenGLShaderProgram(QObject *parent) + : QOpenGLShaderProgram(parent) + , d_ptr(new OpenGLShaderProgramPrivate(this)) +{} + +OpenGLShaderProgram::~OpenGLShaderProgram() +{ + clear(); +} + +void OpenGLShaderProgram::initVertex(const QString &pos, const QString &texCord) +{ + GLuint posAttr = GLuint(attributeLocation(pos)); + GLuint texCordAttr = GLuint(attributeLocation(texCord)); + + float vertices[] = { + // positions // texture coords + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top right + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom right + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom left + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f // top left + }; + unsigned int indices[] = {0, 1, 3, 1, 2, 3}; + + d_ptr->vbo.destroy(); + d_ptr->vbo.create(); + d_ptr->vbo.bind(); + d_ptr->vbo.allocate(vertices, sizeof(vertices)); + + d_ptr->ebo.destroy(); + d_ptr->ebo.create(); + d_ptr->ebo.bind(); + d_ptr->ebo.allocate(indices, sizeof(indices)); + + setAttributeBuffer(posAttr, GL_FLOAT, 0, 3, sizeof(float) * 5); + enableAttributeArray(posAttr); + setAttributeBuffer(texCordAttr, GL_FLOAT, 3 * sizeof(float), 2, sizeof(float) * 5); + enableAttributeArray(texCordAttr); +} + +void OpenGLShaderProgram::clear() +{ + d_ptr->vbo.destroy(); + d_ptr->ebo.destroy(); +} + +} // namespace Ffmpeg diff --git a/ffmpeg/videorender/openglshaderprogram.hpp b/ffmpeg/videorender/openglshaderprogram.hpp new file mode 100644 index 0000000..f4111e5 --- /dev/null +++ b/ffmpeg/videorender/openglshaderprogram.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +namespace Ffmpeg { + +class OpenGLShaderProgram : public QOpenGLShaderProgram, public QOpenGLFunctions +{ +public: + explicit OpenGLShaderProgram(QObject *parent = nullptr); + ~OpenGLShaderProgram() override; + + void initVertex(const QString &pos, const QString &texCord); + + void clear(); + +private: + class OpenGLShaderProgramPrivate; + QScopedPointer d_ptr; +}; + +} // namespace Ffmpeg diff --git a/ffmpeg/videorender/shader/sub.frag b/ffmpeg/videorender/shader/sub.frag new file mode 100644 index 0000000..6089ddc --- /dev/null +++ b/ffmpeg/videorender/shader/sub.frag @@ -0,0 +1,10 @@ +#version 330 core +in vec2 TexCord; // 纹理坐标 +out vec4 FragColor; // 输出颜色 + +uniform sampler2D tex; + +void main() +{ + FragColor = texture(tex, TexCord); +} diff --git a/ffmpeg/videorender/shaders.qrc b/ffmpeg/videorender/shaders.qrc index d305725..71ab616 100644 --- a/ffmpeg/videorender/shaders.qrc +++ b/ffmpeg/videorender/shaders.qrc @@ -4,5 +4,6 @@ shader/video.vert shader/video_vulkan.frag shader/video_vulkan.vert + shader/sub.frag diff --git a/ffmpeg/videorender/videorender.pri b/ffmpeg/videorender/videorender.pri index f09fb98..09139ab 100644 --- a/ffmpeg/videorender/videorender.pri +++ b/ffmpeg/videorender/videorender.pri @@ -3,6 +3,7 @@ RESOURCES += \ HEADERS += \ $$PWD/openglrender.hpp \ + $$PWD/openglshaderprogram.hpp \ $$PWD/videopreviewwidget.hpp \ $$PWD/videorender.hpp \ $$PWD/videorendercreate.hpp \ @@ -10,6 +11,7 @@ HEADERS += \ SOURCES += \ $$PWD/openglrender.cc \ + $$PWD/openglshaderprogram.cc \ $$PWD/videopreviewwidget.cc \ $$PWD/videorender.cc \ $$PWD/videorendercreate.cc \