欧美vvv,亚洲第一成人在线,亚洲成人欧美日韩在线观看,日本猛少妇猛色XXXXX猛叫

新聞資訊

    . 前言

    WebAssembly 就是運行在 Web 平臺上的 Assembly。Assembly 是指匯編代碼,是直接操作 CPU 的指令代碼,比如 x86 指令集上的匯編代碼有指令集、寄存器、棧等等設計,CPU 根據匯編代碼的指導進行運算。匯編代碼相當于 CPU 執行的機器碼能夠轉換成的人類適合讀的一種語言。

    Wasm的技術優勢: 性能高效:WASM采用二進制編碼,在程序執行過程中的性能優越; 存儲成本低:相對于文本格式,二進制編碼的文本占用的存儲空間更小; 多語言支持:用戶可以使用 C/C++/RUST/Go等多種語言編寫智能合約并編譯成WASM格式的字節碼;

    2. emcc編譯的ffmpeg靜態庫

    (1)CSDN上的下載地址

    下載地址: https://download.csdn.net/download/xiaolong1126626497/82868215

    (2)GitHub倉庫下載地址

    https://github.com/wang-bin/avbuild

    https://sourceforge.net/projects/avbuild/files/

    https://sourceforge.net/projects/avbuild/files/wasm/

    (3)這里有編譯好的ffmpeg.wasm文件,前端JS可以直接調用完成視頻轉碼等功能 https://github.com/ffmpegwasm/ffmpeg.wasm

    const fs = require('fs');
    const { createFFmpeg, fetchFile } = require('@ffmpeg/ffmpeg');
    
    const ffmpeg = createFFmpeg({ log: true });
    
    (async () => {
      await ffmpeg.load();
      ffmpeg.FS('writeFile', 'test.avi', await fetchFile('./test.avi'));
      await ffmpeg.run('-i', 'test.avi', 'test.mp4');
      await fs.promises.writeFile('./test.mp4', ffmpeg.FS('readFile', 'test.mp4'));
      process.exit(0);
    })();

    (4)ffmpeg編譯wasm文件的源碼,可以自行編譯wasm文件: https://github.com/ffmpegwasm/ffmpeg.wasm-core

    3. 調用ffmpeg庫-打印版本號

    3.1 準備ffmpeg庫文件

    3.2 編寫C語言代碼

    下面只是編寫了一個打印版本號的函數,用于測試ffmpeg的庫和相關函數是否可以正常調用。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libavutil/imgutils.h>
    #include <libswresample/swresample.h>
    #include <emscripten/emscripten.h>
    #include <libavcodec/version.h>
    
    //獲取版本號
    void print_version()
    {
        unsigned codecVer = avcodec_version();
        int ver_major, ver_minor, ver_micro;
        ver_major = (codecVer >> 16) & 0xff;
        ver_minor = (codecVer >> 8) & 0xff;
        ver_micro = (codecVer) & 0xff;
        printf("當前ffmpeg的版本:avcodec version is: %d=%d.%d.%d\n", codecVer, ver_major, ver_minor, ver_micro);
    
    }

    3.3 編譯生成wasm和js文件

    emcc wasm_ffmpeg/wasm_ffmpeg.c ffmpeg-4.4-wasm/lib/libavformat.a ffmpeg-4.4-wasm/lib/libavcodec.a  ffmpeg-4.4-wasm/lib/libswresample.a ffmpeg-4.4-wasm/lib/libavutil.a -I "ffmpeg-4.4-wasm/include" -s EXPORTED_FUNCTIONS="['_malloc','_free','ccall','allocate','UTF8ToString','_print_version']" -s WASM=1 -s ASSERTIONS=0 -s TOTAL_MEMORY=167772160 -s ALLOW_MEMORY_GROWTH=1 -o out/ffmpeg_decoder.js

    編譯成功后生成的wasm和js文件:

    3.3 編寫index.html代碼

    編寫HTML文件調用js文件里的接口。

    <!doctype html>
    <html lang="en-us">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title>js調用c語言函數示例</title>
      </head>
      
      <body>  
      
        <script type='text/javascript'>   
          function run1()
          {
            _print_version();
          }
          
        </script>
        
        <input type="button" value="打印版本號" onclick="run1()" />
        <script async type="text/javascript" src="ffmpeg_decoder.js"></script>
      </body>
    </html>
    

    3.4 開啟服務器

    cmd命令行運行python,開啟http服務器。

    python -m http.server

    3.5 訪問測試

    打開谷歌瀏覽器,輸入http://127.0.0.1:8000/index.html地址,按下F12打開控制臺,點擊頁面上的按鈕看控制臺輸出。

    完成調用,已成功打印版本號。

    4. 調用ffmpeg庫-解碼視頻信息

    wasm編譯的ffmpeg代碼,不能使用avformat_open_input 直接打開文件地址,打開網絡地址,只能從內存中讀取數據進行解碼。前端js加載了本地磁盤文件后,需要通過內存方式傳遞給wasm-ffmpeg接口里,然后ffmpeg再進行解碼。

    下面C語言代碼里演示了調用ffmpeg解碼內存里視頻文件過程,解碼讀取分辨率、總時間,解幀數據等。代碼只是為了演示如何調用ffmpeg的測試代碼,代碼比較簡單,只是解碼了第一幀數據,得到了YUV420P數據,然后保存在文件中。

    4.1 編寫C語言代碼

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libavutil/imgutils.h>
    #include <libswresample/swresample.h>
    #include <emscripten/emscripten.h>
    #include <libavcodec/version.h>
    
    //EMSCRIPTEN_KEEPALIVE
    
    /*
    存儲視頻文件到磁盤
    參數:
    char *name  文件名稱
    char *buf    寫入的數據
    unsigned int len  寫入長度
    */
    int write_file(char *name, char *buf, unsigned int len)
    {
        //創建文件
        FILE *new_fp = fopen(name, "wb");
        if (new_fp == NULL)
        {
            printf("%s 文件創建失敗.\n", name);
            return -1;
        }
        else
        {
            printf("%s 文件創建成功.\n", name);
        }
        //寫入磁盤
        int cnt = fwrite(buf, 1, len, new_fp);
        printf("成功寫入=%d 字節\n", cnt);
    
        //關閉文件
        fclose(new_fp);
    
        return cnt;
    }
    
    
    /*
    獲取文件大小
    */
    long get_FileSize(char *name)
    {
        /*1. 打開文件*/
        FILE *fp = fopen(name, "rb");
        if (fp == NULL)
        {
            printf("% 文件不存在.\n", name);
            return -1;
        }
        /*2. 將文件指針偏移到文件結尾*/
        fseek(fp, 0, SEEK_END);
    
        /*3. 獲取當前文件指針距離文件頭的字節偏移量*/
        long byte = ftell(fp);
    
        /*4. 關閉文件*/
        fclose(fp);
    
        return byte;
    }
    
    
    /*
    讀文件
    char *buf
    */
    unsigned char *read_file(char *name)
    {
        //創建文件
        FILE *fp = fopen(name, "rb");
        if (fp == NULL)
        {
            printf("%s 文件打開失敗.\n", name);
            return -1;
        }
    
        //獲取文件大小
        int size = get_FileSize(name);
    
        //申請空間
        unsigned char *buf = (unsigned char *)malloc(size);
        if (buf == NULL)
        {
            printf("空間申請失敗:%d byte.\n", size);
            return NULL;
        }
        //讀取文件到內存
        int cnt = fread(buf, 1, size, fp);
        printf("成功讀取=%d 字節\n", cnt);
    
        //關閉文件
        fclose(fp);
    
        return buf;
    }
    
    
    //獲取版本號
    void print_version()
    {
        unsigned codecVer = avcodec_version();
        int ver_major, ver_minor, ver_micro;
        ver_major = (codecVer >> 16) & 0xff;
        ver_minor = (codecVer >> 8) & 0xff;
        ver_micro = (codecVer) & 0xff;
        printf("當前ffmpeg的版本:avcodec version is: %d=%d.%d.%d\n", codecVer, ver_major, ver_minor, ver_micro);
    
    }
    
    
    int ffmpeg_laliu_run_flag = 1;
    
    /*
    功能: 這是FFMPEG回調函數,返回1表示超時  0表示正常
    ffmpeg阻塞完成一些任務的時候,可以快速強制退出.
    */
    static int interrupt_cb(void *ctx)
    {
        if (ffmpeg_laliu_run_flag == 0)return 1;
        return 0;
    }
    
    
    //存放視頻解碼的詳細信息
    struct M_VideoInfo
    {
        int64_t duration;
        int video_width;
        int video_height;
    };
    
    struct M_VideoInfo m_VideoInfo;
    
    
    //讀取數據的回調函數-------------------------  
    //AVIOContext使用的回調函數!  
    //注意:返回值是讀取的字節數  
    //手動初始化AVIOContext只需要兩個東西:內容來源的buffer,和讀取這個Buffer到FFmpeg中的函數  
    //回調函數,功能就是:把buf_size字節數據送入buf即可  
    //第一個參數(void *opaque)一般情況下可以不用  
    
    /*正確方式*/
    struct buffer_data
    {
        uint8_t *ptr; /* 文件中對應位置指針 */
        size_t size;  ///< size left in the buffer /* 文件當前指針到末尾 */
    };
    
    // 重點,自定的buffer數據要在外面這里定義
    struct buffer_data bd = { 0 };
    
    //用來將內存buffer的數據拷貝到buf
    int read_packet(void *opaque, uint8_t *buf, int buf_size)
    {
    
        buf_size = FFMIN(buf_size, bd.size);
    
        if (!buf_size)
            return AVERROR_EOF;
        printf("ptr:%p size:%zu bz%zu\n", bd.ptr, bd.size, buf_size);
    
        /* copy internal buffer data to buf */
        memcpy(buf, bd.ptr, buf_size);
        bd.ptr += buf_size;
        bd.size -= buf_size;
    
        return buf_size;
    }
    
    
    //ffmpeg解碼使用的全局變量
    unsigned char * iobuffer;
    AVFormatContext * format_ctx;
    int video_width = 0;
    int video_height = 0;
    int video_stream_index = -1;
    char* video_buffer;
    
    
    /*
    函數功能: 初始化解碼環境
    函數參數:
    unsigned char *buf  視頻文件的內存地址
    unsigned int len   視頻文件長度
    */ 
    int initDecoder(unsigned char *buf,unsigned int len)
    {
        int ret = 0;
    
        bd.ptr = buf;  /* will be grown as needed by the realloc above */
        bd.size = len; /* no data at this point */
    
        //注冊ffmpeg
        av_register_all();
    
        unsigned int version = avformat_version();
        
        printf("ffmpeg版本: %d\r\n",version);
    
        // Allocate an AVFormatContext
        format_ctx = avformat_alloc_context();
        if (format_ctx == NULL)
        {
            printf("avformat_alloc_context 失敗.\n");
            return -1;
        }
    
        iobuffer = (unsigned char *)av_malloc(32768);
        AVIOContext *avio = avio_alloc_context(iobuffer, 32768, 0, NULL, read_packet, NULL, NULL);
        format_ctx->pb = avio;
        ret = avformat_open_input(&format_ctx, "nothing", NULL, NULL);
    
        format_ctx->interrupt_callback.callback = interrupt_cb; //--------注冊回調函數
    
        AVDictionary* options = NULL;
    
        //ret = avformat_open_input(&format_ctx, url, NULL, NULL);
        
        if (ret != 0)
        {
            char buf[1024];
            av_strerror(ret, buf, 1024);
            printf("無法打開視頻內存,return value: %d \n",ret);
            return -1;
        }
    
        printf("正在讀取媒體文件的數據包以獲取流信息.\n");
    
        // 讀取媒體文件的數據包以獲取流信息
        ret = avformat_find_stream_info(format_ctx, NULL);
        if (ret < 0)
        {
            printf("無法獲取流信息: %d\n",ret);
            return -1;
        }
    
        AVCodec  *video_pCodec;
        // audio/video stream index
        printf("視頻中流的數量: %d\n",format_ctx->nb_streams);
        printf("視頻總時間:%lld 秒\n",format_ctx->duration / AV_TIME_BASE);
    
    
        //得到秒單位的總時間
        m_VideoInfo.duration = format_ctx->duration / AV_TIME_BASE;
    
        for (int i = 0; i < format_ctx->nb_streams; ++i)
        {
            const AVStream* stream = format_ctx->streams[i];
            printf("編碼數據的類型: %d\n",stream->codecpar->codec_id);
    
            if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
            {
                //查找解碼器
                video_pCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
                //打開解碼器
                int err = avcodec_open2(stream->codec, video_pCodec, NULL);
                if (err != 0)
                {
                    printf("H264解碼器打開失敗.\n");
                    return 0;
                }
                video_stream_index = i;
                
                //得到視頻幀的寬高
                video_width = stream->codecpar->width;
                video_height = stream->codecpar->height;
                
                //保存寬和高
                m_VideoInfo.video_height = video_height;
                m_VideoInfo.video_width  = video_width;
    
                //解碼后的YUV數據存放空間
                video_buffer = malloc(video_height * video_width * 3 / 2);
    
                printf("視頻幀的尺寸(以像素為單位): (寬X高)%dx%d 像素格式: %d\n",
                    stream->codecpar->width,stream->codecpar->height,stream->codecpar->format);
            }
            else if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
            {
                
            }
        }
    
        if (video_stream_index == -1)
        {
            printf("沒有檢測到視頻流.\n");
            return -1;
        }
    
        printf("初始化成功.\n");
        return 0;
    }
    
    
    //獲取視頻總時長
    int64_t GetVideoDuration()
    {
        return m_VideoInfo.duration;
    }
    
    //獲取視頻寬
    int64_t GetVideoWidth()
    {
        return m_VideoInfo.video_width;
    }
    
    //獲取視頻高
    int64_t GetVideoHeight()
    {
        return m_VideoInfo.video_height;
    }
    
    
    //獲取視頻幀
    //傳入參數時間單位--秒
    unsigned char *GetVideoFrame(int time)
    {
        AVPacket pkt;
        double video_clock;
        AVFrame *SRC_VIDEO_pFrame = av_frame_alloc();
    
        printf("開始解碼.\n");
    
        printf("跳轉狀態:%d\n",av_seek_frame(format_ctx, -1, time*AV_TIME_BASE, AVSEEK_FLAG_ANY));
    
        while (1)
        {
            int var = av_read_frame(format_ctx, &pkt);
            //讀取一幀數據
            if (var < 0)
            {
                printf("數據讀取完畢:%d\n", var);
                break;
            }
    
            printf("開始..\n");
            //如果是視頻流節點
            if (pkt.stream_index == video_stream_index)
            {
                //當前時間
                video_clock = av_q2d(format_ctx->streams[video_stream_index]->time_base) * pkt.pts;
                printf("pkt.pts=%0.2f,video_clock=%0.2f\n", pkt.pts, video_clock);
    
                //解碼視頻 frame
                //發送視頻幀
                if (avcodec_send_packet(format_ctx->streams[video_stream_index]->codec, &pkt) != 0)
                {
                    av_packet_unref(&pkt);//不成功就釋放這個pkt
                    continue;
                }
    
                //接受后對視頻幀進行解碼
                if (avcodec_receive_frame(format_ctx->streams[video_stream_index]->codec, SRC_VIDEO_pFrame) != 0)
                {
                    av_packet_unref(&pkt);//不成功就釋放這個pkt
                    continue;
                }
    
                //轉格式
            /*  sws_scale(img_convert_ctx,
                    (uint8_t const **)SRC_VIDEO_pFrame->data,
                    SRC_VIDEO_pFrame->linesize, 0,video_height, RGB24_pFrame->data,
                    RGB24_pFrame->linesize);*/
    
                memset(video_buffer, 0, video_height * video_width * 3 / 2);
                int height = video_height;
                int width = video_width;
    
                printf("decode video ok\n");
                int a = 0, i;
                for (i = 0; i < height; i++)
                {
                    memcpy(video_buffer + a, SRC_VIDEO_pFrame->data[0] + i * SRC_VIDEO_pFrame->linesize[0], width);
                    a += width;
                }
                for (i = 0; i < height / 2; i++)
                {
                    memcpy(video_buffer + a, SRC_VIDEO_pFrame->data[1] + i * SRC_VIDEO_pFrame->linesize[1], width / 2);
                    a += width / 2;
                }
                for (i = 0; i < height / 2; i++)
                {
                    memcpy(video_buffer + a, SRC_VIDEO_pFrame->data[2] + i * SRC_VIDEO_pFrame->linesize[2], width / 2);
                    a += width / 2;
                }
    
            
                //保存在文件中: 
                //write_file("./666.yuv", video_buffer, video_height * video_width * 3 / 2);
    
                printf("退出成功....\n");
                break;
            }
    
            //釋放包
            av_packet_unref(&pkt);
        }
        av_free(SRC_VIDEO_pFrame);
        return video_buffer;
    }
    
    
    //銷毀內存
    void DeleteMemory()
    {
        //釋放空間
        av_free(iobuffer);
    }

    4.2 編譯生成wasm和js文件

    emcc wasm_ffmpeg/wasm_ffmpeg.c ffmpeg-4.4-wasm/lib/libavformat.a ffmpeg-4.4-wasm/lib/libavcodec.a  ffmpeg-4.4-wasm/lib/libswresample.a ffmpeg-4.4-wasm/lib/libavutil.a -I "ffmpeg-4.4-wasm/include" -s EXPORTED_FUNCTIONS="['_malloc','_free','ccall','allocate','UTF8ToString','_initDecoder','_write_file','_print_version','_get_FileSize','_read_file','_GetVideoFrame','_GetVideoWidth','_GetVideoDuration','_GetVideoHeight','_DeleteMemory']" -s WASM=1 -s ASSERTIONS=0 -s TOTAL_MEMORY=167772160 -s ALLOW_MEMORY_GROWTH=1 -o out/ffmpeg_decoder.js

    編譯成功后生成的wasm和js文件:

    4.3 編寫index.html代碼

    完成了視頻選擇,播放,調用了C語言編寫的接口完成解碼返回,但是沒有渲染。

    <!doctype html>
    <html lang="en-us">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title>js調用c語言函數示例</title>
      </head>
      
      <body>  
        
        <input id="myfile" type="file"/>
        <video id="output-video" width="300" controls></video>
         <div><canvas id="glcanvas" width="640" height="480"></canvas></div>
           <script>
            //代碼摘自:https://github.com/ivan-94/video-push/blob/master/yuv/index.html#L312
              const video = document.getElementById('glcanvas');
              let renderer;
                
              class WebglScreen {
                  constructor(canvas) {
                  this.canvas = canvas;
                  this.gl =
                      canvas.getContext('webgl') ||
                      canvas.getContext('experimental-webgl');
                  this._init();
                  }
    
                  _init() {
                  let gl = this.gl;
                  if (!gl) {
                      console.log('gl not support!');
                      return;
                  }
    
                  // 圖像預處理
                  gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
                  // GLSL 格式的頂點著色器代碼
                  let vertexShaderSource = `
                          attribute lowp vec4 a_vertexPosition;
                          attribute vec2 a_texturePosition;
                          varying vec2 v_texCoord;
                          void main() {
                              gl_Position = a_vertexPosition;
                              v_texCoord = a_texturePosition;
                          }
                      `;
    
                  let fragmentShaderSource = `
                          precision lowp float;
                          uniform sampler2D samplerY;
                          uniform sampler2D samplerU;
                          uniform sampler2D samplerV;
                          varying vec2 v_texCoord;
                          void main() {
                              float r,g,b,y,u,v,fYmul;
                              y = texture2D(samplerY, v_texCoord).r;
                              u = texture2D(samplerU, v_texCoord).r;
                              v = texture2D(samplerV, v_texCoord).r;
                              fYmul = y * 1.1643828125;
                              r = fYmul + 1.59602734375 * v - 0.870787598;
                              g = fYmul - 0.39176171875 * u - 0.81296875 * v + 0.52959375;
                              b = fYmul + 2.01723046875 * u - 1.081389160375;
                              gl_FragColor = vec4(r, g, b, 1.0);
                          }
                      `;
    
                      let vertexShader = this._compileShader(
                          vertexShaderSource,
                          gl.VERTEX_SHADER,
                      );
                      let fragmentShader = this._compileShader(
                          fragmentShaderSource,
                          gl.FRAGMENT_SHADER,
                      );
    
                  let program = this._createProgram(vertexShader, fragmentShader);
    
                  this._initVertexBuffers(program);
    
                  // 激活指定的紋理單元
                  gl.activeTexture(gl.TEXTURE0);
                  gl.y = this._createTexture();
                  gl.uniform1i(gl.getUniformLocation(program, 'samplerY'), 0);
    
                  gl.activeTexture(gl.TEXTURE1);
                  gl.u = this._createTexture();
                  gl.uniform1i(gl.getUniformLocation(program, 'samplerU'), 1);
    
                  gl.activeTexture(gl.TEXTURE2);
                  gl.v = this._createTexture();
                  gl.uniform1i(gl.getUniformLocation(program, 'samplerV'), 2);
              }
              /**
               * 初始化頂點 buffer
               * @param {glProgram} program 程序
               */
    
              _initVertexBuffers(program) 
              {
                  let gl = this.gl;
                  let vertexBuffer = gl.createBuffer();
                  let vertexRectangle = new Float32Array([
                      1.0,
                      1.0,
                      0.0,
                      -1.0,
                      1.0,
                      0.0,
                      1.0,
                      -1.0,
                      0.0,
                      -1.0,
                      -1.0,
                      0.0,
                  ]);
                  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
                  // 向緩沖區寫入數據
                  gl.bufferData(gl.ARRAY_BUFFER, vertexRectangle, gl.STATIC_DRAW);
                  // 找到頂點的位置
                  let vertexPositionAttribute = gl.getAttribLocation(
                      program,
                      'a_vertexPosition',
                  );
                  // 告訴顯卡從當前綁定的緩沖區中讀取頂點數據
                  gl.vertexAttribPointer(
                      vertexPositionAttribute,
                      3,
                      gl.FLOAT,
                      false,
                      0,
                      0,
                  );
                  // 連接vertexPosition 變量與分配給它的緩沖區對象
                  gl.enableVertexAttribArray(vertexPositionAttribute);
    
                  let textureRectangle = new Float32Array([
                      1.0,
                      0.0,
                      0.0,
                      0.0,
                      1.0,
                      1.0,
                      0.0,
                      1.0,
                  ]);
                  let textureBuffer = gl.createBuffer();
                  gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer);
                  gl.bufferData(gl.ARRAY_BUFFER, textureRectangle, gl.STATIC_DRAW);
                  let textureCoord = gl.getAttribLocation(program, 'a_texturePosition');
                  gl.vertexAttribPointer(textureCoord, 2, gl.FLOAT, false, 0, 0);
                  gl.enableVertexAttribArray(textureCoord);
              }
    
              /**
               * 創建并編譯一個著色器
               * @param {string} shaderSource GLSL 格式的著色器代碼
               * @param {number} shaderType 著色器類型, VERTEX_SHADER 或 FRAGMENT_SHADER。
               * @return {glShader} 著色器。
               */
              _compileShader(shaderSource, shaderType) 
              {
                  // 創建著色器程序
                  let shader = this.gl.createShader(shaderType);
                  // 設置著色器的源碼
                  this.gl.shaderSource(shader, shaderSource);
                  // 編譯著色器
                  this.gl.compileShader(shader);
                  const success = this.gl.getShaderParameter(
                      shader,
                      this.gl.COMPILE_STATUS,
                  );
                  if (!success) {
                      let err = this.gl.getShaderInfoLog(shader);
                      this.gl.deleteShader(shader);
                      console.error('could not compile shader', err);
                      return;
                  }
    
                  return shader;
              }
    
              /**
               * 從 2 個著色器中創建一個程序
               * @param {glShader} vertexShader 頂點著色器。
               * @param {glShader} fragmentShader 片斷著色器。
               * @return {glProgram} 程序
               */
              _createProgram(vertexShader, fragmentShader) 
              {
                  const gl = this.gl;
                  let program = gl.createProgram();
    
                  // 附上著色器
                  gl.attachShader(program, vertexShader);
                  gl.attachShader(program, fragmentShader);
    
                  gl.linkProgram(program);
                  // 將 WebGLProgram 對象添加到當前的渲染狀態中
                  gl.useProgram(program);
                  const success = this.gl.getProgramParameter(
                      program,
                      this.gl.LINK_STATUS,
                  );
    
                  if (!success) {
                      console.err(
                      'program fail to link' + this.gl.getShaderInfoLog(program),
                      );
                      return;
                  }
    
                  return program;
              }
    
              /**
               * 設置紋理
               */
              _createTexture(filter = this.gl.LINEAR) 
              {
                  let gl = this.gl;
                  let t = gl.createTexture();
                  // 將給定的 glTexture 綁定到目標(綁定點
                  gl.bindTexture(gl.TEXTURE_2D, t);
                  // 紋理包裝 參考https://github.com/fem-d/webGL/blob/master/blog/WebGL基礎學習篇(Lesson%207).md -> Texture wrapping
                  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
                  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
                  // 設置紋理過濾方式
                  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
                  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
                  return t;
              }
    
              /**
               * 渲染圖片出來
               * @param {number} width 寬度
               * @param {number} height 高度
               */
              renderImg(width, height, data) 
              {
                  let gl = this.gl;
                  // 設置視口,即指定從標準設備到窗口坐標的x、y仿射變換
                  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
                  // 設置清空顏色緩沖時的顏色值
                  gl.clearColor(0, 0, 0, 0);
                  // 清空緩沖
                  gl.clear(gl.COLOR_BUFFER_BIT);
    
                  let uOffset = width * height;
                  let vOffset = (width >> 1) * (height >> 1);
    
                  gl.bindTexture(gl.TEXTURE_2D, gl.y);
                  // 填充紋理
                  gl.texImage2D(
                      gl.TEXTURE_2D,
                      0,
                      gl.LUMINANCE,
                      width,
                      height,
                      0,
                      gl.LUMINANCE,
                      gl.UNSIGNED_BYTE,
                      data.subarray(0, uOffset),
                  );
    
                  gl.bindTexture(gl.TEXTURE_2D, gl.u);
                  gl.texImage2D(
                      gl.TEXTURE_2D,
                      0,
                      gl.LUMINANCE,
                      width >> 1,
                      height >> 1,
                      0,
                      gl.LUMINANCE,
                      gl.UNSIGNED_BYTE,
                      data.subarray(uOffset, uOffset + vOffset),
                  );
    
                  gl.bindTexture(gl.TEXTURE_2D, gl.v);
                  gl.texImage2D(
                      gl.TEXTURE_2D,
                      0,
                      gl.LUMINANCE,
                      width >> 1,
                      height >> 1,
                      0,
                      gl.LUMINANCE,
                      gl.UNSIGNED_BYTE,
                      data.subarray(uOffset + vOffset, data.length),
                  );
    
                  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
              }
    
              /**
               * 根據重新設置 canvas 大小
               * @param {number} width 寬度
               * @param {number} height 高度
               * @param {number} maxWidth 最大寬度
               */
              setSize(width, height, maxWidth) 
              {
                  let canvasWidth = Math.min(maxWidth, width);
                  this.canvas.width = canvasWidth;
                  this.canvas.height = (canvasWidth * height) / width;
              }
    
              destroy() 
              {
                  const { gl } = this;
    
                  gl.clear(
                      gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT,
                  );
              }
          } // end of webgl
          
          const initialCanvas = (canvas, width, height) => {
              canvas.width  = width;
              canvas.height = height;
              return new WebglScreen(canvas);
           };
    
          const render = (buff,width,height) =>
          {
              if (renderer == null) {
              return;
              }
              renderer.renderImg(width, height, buff);
          };
        </script>
        <script type='text/javascript'>   
          function run1()
          {
          
          }
          
          function run2()
          {
             
          }
         
         
           //加載本地文件
            var file=document.getElementById("myfile");
            file.onchange=function(event){  
                let fileReader = new FileReader();
                fileReader.onload = function(){
                // 當 FileReader 讀取文件時候,讀取的結果會放在 FileReader.result 屬性中
                    var fileArray= this.result;
                    console.log(fileArray);
                    let fileBuffer = new  Uint8Array(this.result);
                    console.log(fileBuffer);
                    //申請空間
                    var fileBufferPtr = _malloc(fileBuffer.length)
                    //將fileBuffer里的內容拷貝到fileBufferPtr里
                    Module.HEAP8.set(fileBuffer,fileBufferPtr)
                    
                    //1. 寫文件
                    //申請空間,存放字符串
                    //var name = allocate(intArrayFromString("./tmp.mp4"), ALLOC_NORMAL);
                    //var run_var=_write_file(name,fileBufferPtr,fileBuffer.length);
                    //console.log('寫文件成功字節數:',run_var);
                    
                    //2. 獲取文件大小
                    //var file_size=_get_FileSize(name);
                    //console.log('獲取文件大小:',file_size);
                    
                   //const data = ffmpeg.FS('readFile', 'output.mp4');
    
                    //3. 讀取文文件
                   //const data =  _read_file(name);
                  // const video = document.getElementById('output-video');
                   //video.src = URL.createObjectURL(new Blob([fileBuffer.buffer], { type: 'video/mp4' }));
                
                   //加載內存數據
                  // Module.HEAPU8.subarray(imgBufferPtr, data);  
    
                    //4. 初始化解碼器,加載文件
                    _initDecoder(fileBufferPtr,fileBuffer.length);
                    
                    //5. 獲取總時間
                    var time=_GetVideoDuration();
                    console.log('視頻總時間:'+time);
                    
                    
                    //6. 獲取視頻寬
                    var Width=_GetVideoWidth();
                    console.log('視頻寬:'+Width);
                    
                    //7. 獲取視頻高
                    var Height=_GetVideoHeight();
                    console.log('視頻高:'+Height);
                    
                    renderer = initialCanvas(video,Width,Height);
                    
                    //申請空間,存放字符串
                    //var name_file = allocate(intArrayFromString("./666.yuv"), ALLOC_NORMAL);
    
                    //讀取文件
                    //var yuv_wasm_data=_read_file(name_file);
                    
                   //8. 獲取視頻幀
                   var yuv_wasm_data=_GetVideoFrame(10);
                    
                   var renderlength=Width*Height*3/2;
                   var RenderBuffer = new Uint8Array (Module.HEAPU8.subarray(yuv_wasm_data,yuv_wasm_data + renderlength + 1) ); 
                   
                   console.log(RenderBuffer);
                   
                   render(RenderBuffer,Width,Height); 
     
                };
                fileReader.readAsArrayBuffer(this.files[0]);
            }
            
        </script>
    
        <input type="button" value="載入文件初始化解碼器" onclick="run1()" />
        <script async type="text/javascript" src="ffmpeg_decoder.js"></script>
      </body>
    </html>

    4.4 開啟服務器

    命令行運行命令,開啟HTTP服務器,方便測試:

    python -m http.server

    4.5 測試效果

    打開谷歌瀏覽器,輸入http://127.0.0.1:8000/index.html地址,按下F12打開控制臺,點擊頁面上的按鈕看控制臺輸出。

    (1)輸入地址,打開網頁

    (2)按下F12,打開控制臺

    (3)選擇一個MP4文件載入測試。獲取一幀圖片。

    一、項目背景

    隨著物聯網技術不斷發展,視頻監控系統在各個領域的應用越來越廣泛。其中,RTSP(Real Time Streaming Protocol)是一種常用的流媒體傳輸協議,可以實現對實時音視頻數據的傳輸和播放。為了實現視頻監控系統的網絡化和智能化,需要開發一個基于RTSP協議的視頻流服務器,能夠接收前端設備的視頻流,并提供RTSP協議的服務,方便客戶端進行實時的視頻瀏覽、回放等操作。

    在開發過程中,為了提高開發效率、減少開發難度和成本,同時具備良好的可擴展性和可維護性,我選擇使用Qt和Live555庫來搭建RTSP服務器。Qt是一個跨平臺的C++應用程序開發框架,具有完善的GUI界面設計工具和豐富的功能模塊,可以大大簡化開發過程;而Live555是一個跨平臺的流媒體開發庫,支持多種流媒體協議,包括RTSP、SIP、RTP等,可以幫助我們快速實現視頻流的傳輸和處理。

    該項目將主要實現以下功能:

    1. 實現Qt+Live555環境下的RTSP服務器搭建,支持多路視頻流的傳輸和播放。
    2. 基于Qt的GUI界面設計,方便用戶進行配置和管理。
    3. 實現視頻編碼格式的自適應性,支持H.264、H.265等常用視頻編碼格式。
    4. 實現視頻流的加密和解密,并支持RTSP over HTTPS安全通信協議。
    5. 實現基本的用戶權限管理和日志記錄功能。

    二、RTSP介紹

    RTSP服務器是一種提供流媒體服務的服務器,它采用RTSP協議與客戶端進行通信,支持音視頻數據的傳輸和控制。RTSP(Real-Time Streaming Protocol)實時流傳輸協議是一個應用層協議,通過TCP或UDP傳輸數據,用于實現多媒體數據的實時傳輸。

    RTSP服務器主要用于流媒體直播、點播、錄像等應用場景,可以讓用戶通過網絡實時觀看視頻、聽取音頻等。RTSP服務器一般具有以下功能:

    1. 實現流媒體數據的傳輸和控制,包括建立連接、傳輸媒體數據、暫停播放、快進快退等;
    2. 支持多種編解碼格式和媒體容器格式,如H.264、MPEG-4、AAC、MP3等;
    3. 支持多種網絡傳輸協議,如UDP、TCP、HTTP、HTTPS等;
    4. 支持多種安全性認證方式,如用戶名密碼認證、數字證書認證等;
    5. 支持擴展功能,如實時轉碼、負載均衡、集群部署等。

    常見的RTSP服務器軟件包括Live555、Wowza Media Server、Darwin Streaming Server等。使用RTSP服務器可以輕松實現基于網絡的流媒體服務,滿足直播、視頻會議、遠程監控等應用場景需求。

    三、Live555庫介紹

    Live555庫是一個開源的多媒體流媒體服務框架,它提供了一系列的C++類和庫函數,用于開發基于標準網絡協議的流媒體應用程序。該庫主要用于實現RTP/RTCP、RTSP、SIP以及SDP等標準協議,可以方便地實現視頻/音頻的網絡傳輸、播放、錄制和轉碼等功能。

    Live555庫具有以下特點:

    1. 適用于各種平臺和操作系統,包括Windows、Linux、Mac OS X等;
    2. 支持常見的視頻和音頻格式,如H.264、MPEG-4、MP3、AAC等;
    3. 支持UDP、TCP、HTTP、HTTPS等多種網絡傳輸協議;
    4. 支持IPv4和IPv6雙棧網絡環境;
    5. 可以通過插件機制擴展功能。

    使用Live555庫進行開發,可以快速搭建基于網絡的流媒體應用程序。例如,可以使用該庫實現RTSP服務器或客戶端,實現視頻直播、遠程監控、視頻會議等應用場景。同時,Live555庫也可以作為其他流媒體服務器軟件的基礎組件,例如: Wowza Media Server、Darwin Streaming Server等。

    四、實現流程

    在Qt中搭建RTSP服務器,可以使用開源的Live555庫。Live555是一個跨平臺、C++語言編寫的多媒體開發庫,支持RTSP和SIP等常見協議,并且有完整的服務器和客戶端實現。

    下面是在Qt中基于Live555庫實現簡單的RTSP服務器的步驟:

    【1】下載并安裝Live555庫。從官網(http://www.live555.com/liveMedia/#download)下載最新版本的庫。

    并按照文檔說明進行安裝。將Live555庫的路徑添加到Qt Creator的項目配置文件中。

    【2】創建一個Qt控制臺應用程序。在Qt Creator中創建一個空的控制臺應用程序,并在項目的.pro文件中添加Live555庫的鏈接選項,例如:

    LIBS += -LLive555庫的路徑 -lliveMedia -lgroupsock -lUsageEnvironment -lBasicUsageEnvironment

    【3】編寫RTSP服務器代碼。創建一個類繼承自live555庫中的RTSPServer類,并實現相應的虛函數,createNewSession()和deleteStream()。

    【4】啟動RTSP服務器。在main()函數中創建RTSP服務器對象,并調用start()函數啟動服務器,如下所示:

    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        
        RTSPServer* server = new MyRTSPServer();
        server->start();
        
        return a.exec();
    }

    【5】測試。使用RTSP客戶端工具(如VLC播放器)連接本地的RTSP服務器,并播放視頻流。

    五、實現代碼

    下面是使用Qt+Live555搭建RTSP服務器的核心代碼示例:

    #include <liveMedia.hh>
    #include <BasicUsageEnvironment.hh>
    
    class VideoStreamSource : public FramedSource {
    public:
      static VideoStreamSource* createNew(UsageEnvironment& env) {
        return new VideoStreamSource(env);
      }
      virtual void doGetNextFrame() {
        // 將視頻數據幀復制到fTo處并設置fFrameSize和fNumTruncatedBytes,然后調用afterGetting()函數通知視頻幀可用。
        if (condition1 && condition2) {
            memcpy(fTo, fVideoFrame, fVideoFrameSize);
            afterGetting(this);
        } else {
            handleClosure(this);
        }
      }
    protected:
      VideoStreamSource(UsageEnvironment& env) : FramedSource(env) {
        // 初始化一些變量
      }
      virtual ~VideoStreamSource() {}
    private:
      // 一些成員變量
      char* fVideoFrame;
      unsigned fVideoFrameSize;
    };
    
    class MyRTSPServer : public RTSPServer {
    public:
      static MyRTSPServer* createNew(UsageEnvironment& env, Port ourPort) {
        return new MyRTSPServer(env, ourPort);
      }
    protected:
      MyRTSPServer(UsageEnvironment& env, Port ourPort)
        : RTSPServer(env, ourPort, NULL) {}
      virtual ~MyRTSPServer() {}
      virtual ServerMediaSession* lookupSession(char const* streamName, Boolean isFirstLookup) {
        ServerMediaSession* session = RTSPServer::lookupSession(streamName, isFirstLookup);
        if (session == NULL) {
          // 創建一個新的會話以支持RTSP客戶端請求的視頻流
          session = ServerMediaSession::createNew(envir(), streamName);
          // 將視頻幀添加到會話中
          VideoStreamSource* videoSource = VideoStreamSource::createNew(envir());
          session->addSubsession(MPEG4VideoStreamDiscreteFramer::createNew(envir(), videoSource, false));
          addServerMediaSession(session);
        }
        return session;
      }
    };
    
    int main(int argc, char *argv[]) {
      // 創建一個QT應用程序實例
      QCoreApplication app(argc, argv);
    
      // 創建一個RTSP服務器實例,并監聽9090端口
      MyRTSPServer* rtspServer = MyRTSPServer::createNew(*(app.instance()), 9090);
      if (rtspServer == NULL) {
        qDebug() << "Failed to create RTSP server: " << env.getResultMsg() << endl;
        exit(1);
      }
    
      // 啟動Qt事件循環
      return app.exec();
    }

    上面的代碼實現了以下幾個功能:

    • 創建了一個VideoStreamSource類,用于獲取視頻數據幀并封裝成FramedSource對象。
    • 創建了一個MyRTSPServer類繼承自RTSPServer,重寫了lookupSession()函數,用于創建和添加新的視頻流會話到RTSP服務器中。
    • main()函數中創建了一個Qt應用程序實例,以及一個RTSP服務器實例,并啟動Qt事件循環。
網站首頁   |    關于我們   |    公司新聞   |    產品方案   |    用戶案例   |    售后服務   |    合作伙伴   |    人才招聘   |   

友情鏈接: 餐飲加盟

地址:北京市海淀區    電話:010-     郵箱:@126.com

備案號:冀ICP備2024067069號-3 北京科技有限公司版權所有