<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          跨平臺播放器開發(fā) (五) 如何渲染音視頻裸流數(shù)據(jù)

          共 15251字,需瀏覽 31分鐘

           ·

          2021-07-16 16:51

          前言

          上一篇咱們學(xué)習(xí)了 FFmpeg 解碼、像素格式轉(zhuǎn)換和音頻重采樣 ,該篇我們主要學(xué)習(xí) QT 跨平臺音頻視頻渲染 API 。

          ?

          跨平臺播放器開發(fā) (一) QT for MAC OS & FFmpeg 環(huán)境搭建

          跨平臺播放器開發(fā) (二) QT for Linux & FFmpeg 環(huán)境搭建

          跨平臺播放器開發(fā) (三) QT for Windows & FFmpeg 環(huán)境搭建

          跨平臺播放器開發(fā) (四) 開發(fā)一個播放器需要用到哪些 FFmpeg 知識

          ?

          PCM 渲染

          其實不管是 Android 的 AudioTrack 亦或者是 OpenSL ES 來渲染 PCM ,原理都是一樣的,都是先配置 PCM 的基本信息,比如采樣率、通道數(shù)量、采樣bit數(shù),然后就可以根據(jù)聲卡的回調(diào)來進(jìn)行 write(pcmBuffer) 數(shù)據(jù),我們就根據(jù)這個思路步驟,來進(jìn)行編碼。

          「第一步:設(shè)置 PCM 基本信息」

          配置音頻信息我們會使用到 QAudioFormat 對象,根據(jù)官網(wǎng)提示,我們要進(jìn)行多媒體編程,就要配置 multimedia 模塊

          可以在 CMakeLists.txt 中這樣配置

          set(QT_VERSION 5)
          set(REQUIRED_LIBS Core Gui Widgets Multimedia)
          set(REQUIRED_LIBS_QUALIFIED Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Multimedia)
          find_package(Qt${QT_VERSION} COMPONENTS ${REQUIRED_LIBS} REQUIRED)
          add_executable(qt-audio-debug ${QT_AUDIO_SRC})
          target_link_libraries(qt-audio-debug ${REQUIRED_LIBS_QUALIFIED})

          下面調(diào)用  QAudioFormat 來進(jìn)行配置音頻信息

              QAudioFormat format;
              //設(shè)置采樣率
              format.setSampleRate(this->sampleRate);
              //設(shè)置通道
              format.setChannelCount(this->channelCount);
              //設(shè)置采樣位數(shù)
              format.setSampleSize(this->sampleSize);
              format.setCodec("audio/pcm");
              format.setByteOrder(QAudioFormat::LittleEndian);
              format.setSampleType(QAudioFormat::SignedInt);
              const QAudioDeviceInfo audioDeviceInfo = QAudioDeviceInfo::defaultOutputDevice();
              QAudioDeviceInfo info(audioDeviceInfo);
              //該設(shè)置是否支持
              bool audioDeviceOk = info.isFormatSupported(format);
              if (!audioDeviceOk) {
                  qWarning() << "Default format not supported - trying to use nearest";
                  format = info.nearestFormat(format);
              }

          「第二步: 將音頻數(shù)據(jù)發(fā)送到音頻輸出設(shè)備接口」

          //將上面配置好的音頻數(shù)據(jù)和設(shè)備信息傳遞給音頻輸出對象
          auto *audioOutput = new QAudioOutput(audioDeviceInfo, format)
          //開始播放
          audioOutput->start(QIODevice *device)

          在播放的時候,需要傳入一個 QIODevice 類,這就是咱們前面說的,聲卡會給咱們一個回調(diào),用于寫入 PCM 數(shù)據(jù)。如果我們不使用 QIODevice ,而根據(jù)死循環(huán)一直寫入其實是不行的,它底層有一個緩沖區(qū),等緩沖區(qū)用完咱們在進(jìn)行寫入數(shù)據(jù),這是一個最好的方式。

          「第三步: 給聲卡提供PCM數(shù)據(jù)」

          首先我們要進(jìn)行繼承 QIODevice 然后重寫 readData 函數(shù)

          class PCMPlay : public QIODevice {
          Q_OBJECT

          public:
              PCMPlay();
              ...
              qint64 readData(char *data, qint64 maxlen) override;
              ...
          };

          #pcmplay.cpp
          qint64 PCMPlay::readData(char *data, qint64 maxlen) {
              if (m_pos >= m_buffer.size())
                  return 0;
              qint64 total = 0;
              if (!m_buffer.isEmpty()) {
                  while (maxlen - total > 0) {
                      const qint64 chunk = qMin((m_buffer.size() - m_pos), maxlen - total);
                      memcpy(data + total, m_buffer.constData() + m_pos, chunk);
                      m_pos = (m_pos + chunk) % m_buffer.size();
                      total += chunk;
                  }
              }
              return maxlen;
          }

          上面這一步就相當(dāng)于我們需要將 pcm 數(shù)據(jù) copy 到 readData 的 data 地址中,當(dāng)?shù)讓幼x取到 data 中的 pcm buf 在送入聲卡,那么就會有聲音了。

          之后如果想暫?;蛘咂渌僮鳎敲纯梢哉{(diào)用 QAudioOutput 提供的如下函數(shù):

              void stop();
              void reset();
              void suspend();
              void resume();

          實現(xiàn)音頻播放的代碼還是比較少的,這里為了可讀性并沒有貼出所有代碼。

          訪問完整代碼:https://github.com/yangkun19921001/YKAVStudyPlatform/blob/main/avcore/qt/audio/main.cpp

          YUV 渲染

          在我的了解中其實在任何設(shè)備上都不能直接渲染 YUV 數(shù)據(jù),我們只能將 YUV 轉(zhuǎn)為 RGB 格式的數(shù)據(jù),才能交于顯卡渲染。轉(zhuǎn)換過程上一篇我們使用 ffmpeg 的 sws_getCachedContext sws_scale 該類函數(shù)來進(jìn)行轉(zhuǎn)換,由于使用 ffmpeg 轉(zhuǎn)換太耗內(nèi)存了,所以咱們這里基于 OpenGL shader 來進(jìn)行轉(zhuǎn)換,轉(zhuǎn)換公式如下:

          const char *fString = GET_STR(
                  varying vec2 textureOut;
                  uniform sampler2D tex_y;
                  uniform sampler2D tex_u;
                  uniform sampler2D tex_v;
                  void main(void) {
                      vec3 yuv;
                      vec3 rgb;
                      yuv.x = texture2D(tex_y, textureOut).r;
                      yuv.y = texture2D(tex_u, textureOut).r - 0.5;
                      yuv.z = texture2D(tex_v, textureOut).r - 0.5;
                      rgb = mat3(1.01.01.0,
                                 0.0-0.394652.03211,
                                 1.13983-0.580600.0) * yuv;
                      gl_FragColor = vec4(rgb, 1.0);
                  }

          );

          想要在 QT 中使用 OpenGL 需要在 CMakelist.txt 中添加如下代碼:

          set(QT_VERSION 5)
          set(REQUIRED_LIBS Core Gui Widgets Multimedia OpenGL)
          set(REQUIRED_LIBS_QUALIFIED Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Multimedia Qt5::OpenGL)
          find_package(Qt${QT_VERSION} COMPONENTS ${REQUIRED_LIBS} REQUIRED)
          add_executable(qt-audio-debug ${QT_AUDIO_SRC})
          target_link_libraries(qt-audio-debug ${REQUIRED_LIBS_QUALIFIED})

          根據(jù) QT 中使用 QOpenGLWidget  需要繼承它:

          class QYUVWidget : public QOpenGLWidget, protected QOpenGLFunctions {
          Q_OBJECT

          public:
              QYUVWidget(QWidget *);

              ~QYUVWidget();
            
              //初始化數(shù)據(jù)大小
              void InitDrawBufSize(uint64_t size);

              //繪制
              void DrawVideoFrame(unsigned char *data, int frameWidth, int frameHeight);


          protected:
              //刷新顯示
              void paintGL() override;

              //初始化 gl
              void initializeGL() override;

              //窗口尺寸發(fā)生變化
              void resizeGL(int w, int h) override;
          ...
          }

          定義 cpp 實現(xiàn)函數(shù):

          //用于初始化定義 YUV 大小的 buffer
          void QYUVWidget::InitDrawBufSize(uint64_t size) {
              impl->mFrameSize = size;
              impl->mBufYuv = new unsigned char[size];
          }

          //有新的數(shù)據(jù)就調(diào)用 opengl update 函數(shù),之后會執(zhí)行 paintGL() 
          void QYUVWidget::DrawVideoFrame(unsigned char *data, int frameWidth, int frameHeight) {
              impl->mVideoW = frameWidth;
              impl->mVideoH = frameHeight;
              memcpy(impl->mBufYuv, data, impl->mFrameSize);
              update();
          }

          //初始化 opengl 函數(shù)
          void QYUVWidget::initializeGL() {
            //1、初始化 QT Opengl 功能
             initializeOpenGLFunctions();
            
            //2、加載并編譯頂點和片元 shader
            impl->mVShader = new QOpenGLShader(QOpenGLShader::Vertex, this);
              //編譯頂點 shader program
              if (!impl->mVShader->compileSourceCode(vString)) {
                  throw QYUVException();
              }
             impl->mFShader = new QOpenGLShader(QOpenGLShader::Fragment, this);
              //編譯片元 shader program
              if (!impl->mFShader->compileSourceCode(fString)) {
                  throw QYUVException();
              }
            
            
            //3、創(chuàng)建執(zhí)行 shader 的程序
              impl->mShaderProgram = new QOpenGLShaderProgram(this);
              //將頂點 片元 shader 添加到程序容器中
              impl->mShaderProgram->addShader(impl->mFShader);
              impl->mShaderProgram->addShader(impl->mVShader);

              
            
            //4、設(shè)置頂點片元坐標(biāo)
              impl->mShaderProgram->bindAttributeLocation("vertexIn", A_VER);

              //設(shè)置材質(zhì)坐標(biāo)
              impl->mShaderProgram->bindAttributeLocation("textureIn", T_VER);

              //編譯shader
              qDebug() << "program.link() = " << impl->mShaderProgram->link();

              qDebug() << "program.bind() = " << impl->mShaderProgram->bind();
            //5、拿到shader 中 紋理y,u,v 的材質(zhì)
              impl->textureUniformY = impl->mShaderProgram->uniformLocation("tex_y");
              impl->textureUniformU = impl->mShaderProgram->uniformLocation("tex_u");
              impl->textureUniformV = impl->mShaderProgram->uniformLocation("tex_v");

            
            
            //6、加載頂點片元位置
                //頂點
              glVertexAttribPointer(A_VER, 2, GL_FLOAT, 00, VER);
              glEnableVertexAttribArray(A_VER);

              //材質(zhì)
              glVertexAttribPointer(T_VER, 2, GL_FLOAT, 00, TEX);
              glEnableVertexAttribArray(T_VER);
            
            //7、創(chuàng)建 y,u,v 紋理 id
              glGenTextures(3, texs);
              impl->id_y = texs[0];
              impl->id_u = texs[1];
              impl->id_v = texs[2];
          }

          //主要講 y,u,v 數(shù)據(jù)綁定到對應(yīng)的紋理 id 上并渲染
          void QYUVWidget::paintGL() {
            //1、激活并綁定 y 紋理
             glActiveTexture(GL_TEXTURE0);
             glBindTexture(GL_TEXTURE_2D, impl->id_y);
             glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, impl->mVideoW, impl->mVideoH, 0, GL_LUMINANCE,GL_UNSIGNED_BYTE,
                           impl->mBufYuv);
             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);
            
            //2、激活并綁定 u 紋理
             glActiveTexture(GL_TEXTURE1);//Activate texture unit GL_TEXTURE1
             glBindTexture(GL_TEXTURE_2D, impl->id_u);
             glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, impl->mVideoW / 2, impl->mVideoH / 20, GL_LUMINANCE,
                           GL_UNSIGNED_BYTE, (char *) impl->mBufYuv + impl->mVideoW * impl->mVideoH);
             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);  
            
            //3、激活并綁定 v 紋理
              glActiveTexture(GL_TEXTURE2);//Activate texture unit GL_TEXTURE2
              glBindTexture(GL_TEXTURE_2D, impl->id_v);
              glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, impl->mVideoW / 2, impl->mVideoH / 20, GL_LUMINANCE,
                           GL_UNSIGNED_BYTE, (char *) impl->mBufYuv + impl->mVideoW * impl->mVideoH * 5 / 4);
              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);
            
            //4、渲染
              //指定y紋理要使用新值,只能用0,1,2等表示紋理單元的索引
              glUniform1i(impl->textureUniformY, 0);
              glUniform1i(impl->textureUniformU, 1);
              glUniform1i(impl->textureUniformV, 2);
             //渲染
              glDrawArrays(GL_TRIANGLE_STRIP, 04);
          }

          //框口改變會進(jìn)行更新
          void QYUVWidget::resizeGL(int w, int h) {
              qDebug() << "resizeGL " << width << ":" << height;
              glViewport(00, w, h);
              update();
          }

          因為 「OpenGL 是跨平臺的緣故」 ,所以調(diào)用接口在任何平臺上基本上是一模一樣,只要在一個平臺學(xué)會了,在另一個平臺稍微改一下就可以使用。如果對 OpenGL 比較興趣的可以參考這位大佬總結(jié)的 OpenGL ES 3.0: https://github.com/githubhaohao/NDK_OpenGLES_3_0 系列使用教程。

          程序編譯運行,出現(xiàn)如下畫面就代表成功了

          訪問完整代碼:https://github.com/yangkun19921001/YKAVStudyPlatform/blob/main/avcore/qt/video/main.cpp

          總結(jié)

          利用 QT 跨平臺的 API 我們實現(xiàn)了 YUV & PCM 的渲染,總體來說 「OpenGL」 是不容易上手的,但是只要我們認(rèn)真的敲幾個樣例出來,其實也就那么回事兒, 因為使用步驟都差不多。該篇到此結(jié)束,下一篇主要寫 「如何設(shè)計一個通用的播放器架構(gòu)」, 敬請期待吧! 再會


          瀏覽 81
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美一级操逼 | 毛片在线观看网站 | 日批网站在线播放 | 极品91 | 午夜激情av |