分類: 3C資訊

  • 使用C#+FFmpeg+DirectX+dxva2硬件解碼播放h264流

    使用C#+FFmpeg+DirectX+dxva2硬件解碼播放h264流

    本文門檻較高,因此行文看起來會亂一些,如果你看到某處能會心一笑請馬上聯繫我開始擺龍門陣
    如果你跟隨這篇文章實現了播放器,那你會得到一個高效率,低cpu佔用(單路720p視頻解碼播放佔用1%左右cpu),且代碼和引用精簡(無其他託管和非託管的dll依賴,更無需安裝任何插件,你的程序完全綠色運行);並且如果硬解不可用,切換到軟件是自動過程

      首先需要準備好visual studio/msys2/ffmpeg源碼/dx9sdk。因為我們要自己編譯ffmpeg,並且是改動代碼后編譯,ffmpeg我們編譯時會裁剪。

    • ffmpeg源碼大家使用4.2.1,和我保持同步,這樣比較好對應,下載地址為
    • msys2安裝好后不需要裝mingw和其他東西,只需要安裝make(見下方圖片;我們編譯工具鏈會用msvc而非mingw-gcc)
    • visual studio版本按道理是不需要新版本的,應該是2008-2019都可以(不過還是得看看ffmpeg代碼里是否用了c99 c11等低版本不支持的東西),vs需要安裝c++和c#的模塊(見下方圖片;應該也不需要特意去打開什麼功能)
    • dx9的sdk理論上是不用安裝的(如果你是高手,可以用c#的ilgenerator直接寫calli;亦或者寫unsafe代碼直接進行內存call,文章最後我會為大家揭秘如何用c#調用c++甚至com組件)。我用了directx的managecode,由官方為我們做了dx的調用(見下方圖片)

      第二步是修改ffmpeg源碼並編譯,我們要修改的源碼只有一個文件的十餘行,而且是增量修改。

    修改的文件位於libavutil/hwcontext_dxva2.c文件,我先將修改部分貼出來然後再給大家解釋

    hwcontext_dxva2.c修改部分


    static int dxva2_device_create9_extend(AVHWDeviceContext ctx, UINT adapter, HWND hWnd)
    {
    DXVA2DevicePriv
    priv = ctx->user_opaque;
    D3DPRESENT_PARAMETERS d3dpp = {0};
    D3DDISPLAYMODE d3ddm;
    HRESULT hr;
    pDirect3DCreate9 createD3D = (pDirect3DCreate9 )dlsym(priv->d3dlib, "Direct3DCreate9");
    if (!createD3D) {
    av_log(ctx, AV_LOG_ERROR, "Failed to locate Direct3DCreate9\n");
    return AVERROR_UNKNOWN;
    }

    priv->d3d9 = createD3D(D3D_SDK_VERSION);
    if (!priv->d3d9) {
        av_log(ctx, AV_LOG_ERROR, "Failed to create IDirect3D object\n");
        return AVERROR_UNKNOWN;
    }
    
    IDirect3D9_GetAdapterDisplayMode(priv->d3d9, adapter, &d3ddm);
    
    d3dpp.BackBufferFormat = d3ddm.Format;
    d3dpp.Windowed = TRUE;           // 是否窗口显示   
    d3dpp.hDeviceWindow = hWnd;    // 显示窗口句柄
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;    // 交換鏈設置,後台緩衝使用后直接丟棄
    d3dpp.Flags = D3DPRESENTFLAG_VIDEO;          // 附加特性,显示視頻
    
    DWORD behaviorFlags = D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE;
    D3DDEVTYPE devType = D3DDEVTYPE_HAL;
    D3DCAPS9 caps;
    
    if (IDirect3D9_GetDeviceCaps(priv->d3d9, D3DADAPTER_DEFAULT, devType, &caps) >= 0)
    {
        if (caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
        {
            behaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
        }
        else
        {
            behaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
        }
    }
    
    if(!hWnd)
        hWnd = GetDesktopWindow();
    hr = IDirect3D9_CreateDevice(priv->d3d9, adapter, D3DDEVTYPE_HAL, hWnd,
                                 behaviorFlags,
                                 &d3dpp, &priv->d3d9device);
    if (FAILED(hr)) {
        av_log(ctx, AV_LOG_ERROR, "Failed to create Direct3D device\n");
        return AVERROR_UNKNOWN;
    }
    
    return 0;

    }

    static int dxva2_device_create(AVHWDeviceContext ctx, const char device,
    AVDictionary opts, int flags)
    {
    AVDXVA2DeviceContext
    hwctx = ctx->hwctx;
    DXVA2DevicePriv priv;
    pCreateDeviceManager9
    createDeviceManager = NULL;
    unsigned resetToken = 0;
    UINT adapter = D3DADAPTER_DEFAULT;
    HRESULT hr;
    int err;
    AVDictionaryEntry *t = NULL;
    HWND hWnd = NULL;

    if (device)
        adapter = atoi(device);
    
    priv = av_mallocz(sizeof(*priv));
    if (!priv)
        return AVERROR(ENOMEM);
    
    ctx->user_opaque = priv;
    ctx->free        = dxva2_device_free;
    
    priv->device_handle = INVALID_HANDLE_VALUE;
    
    priv->d3dlib = dlopen("d3d9.dll", 0);
    if (!priv->d3dlib) {
        av_log(ctx, AV_LOG_ERROR, "Failed to load D3D9 library\n");
        return AVERROR_UNKNOWN;
    }
    priv->dxva2lib = dlopen("dxva2.dll", 0);
    if (!priv->dxva2lib) {
        av_log(ctx, AV_LOG_ERROR, "Failed to load DXVA2 library\n");
        return AVERROR_UNKNOWN;
    }
    
    createDeviceManager = (pCreateDeviceManager9 *)dlsym(priv->dxva2lib,
                                                         "DXVA2CreateDirect3DDeviceManager9");
    if (!createDeviceManager) {
        av_log(ctx, AV_LOG_ERROR, "Failed to locate DXVA2CreateDirect3DDeviceManager9\n");
        return AVERROR_UNKNOWN;
    }
    
    t = av_dict_get(opts, "hWnd", NULL, 0);
    if(t) {
        hWnd = (HWND)atoi(t->value);
    }
    if(hWnd) {
        if((err = dxva2_device_create9_extend(ctx, adapter, hWnd)) < 0)
            return err;
    } else {
        if (dxva2_device_create9ex(ctx, adapter) < 0) {
            // Retry with "classic" d3d9
            err = dxva2_device_create9(ctx, adapter);
            if (err < 0)
                return err;
        }
    }
    
    hr = createDeviceManager(&resetToken, &hwctx->devmgr);
    if (FAILED(hr)) {
        av_log(ctx, AV_LOG_ERROR, "Failed to create Direct3D device manager\n");
        return AVERROR_UNKNOWN;
    }
    
    hr = IDirect3DDeviceManager9_ResetDevice(hwctx->devmgr, priv->d3d9device, resetToken);
    if (FAILED(hr)) {
        av_log(ctx, AV_LOG_ERROR, "Failed to bind Direct3D device to device manager\n");
        return AVERROR_UNKNOWN;
    }
    
    hr = IDirect3DDeviceManager9_OpenDeviceHandle(hwctx->devmgr, &priv->device_handle);
    if (FAILED(hr)) {
        av_log(ctx, AV_LOG_ERROR, "Failed to open device handle\n");
        return AVERROR_UNKNOWN;
    }
    
    return 0;

    }

      代碼中dxva2_device_create9_extend函數是我新加入的,並且在dxva2_device_create函數(這個函數是ffmpeg原始流程中的,我的改動不影響原本任何功能)中適時調用;簡單來說,原來的ffmpeg也能基於dxva2硬件解碼,但是它沒法將解碼得到的surface用於前台播放,因為它創建device時並未指定窗口和其他相關參數,大家可以參考我代碼實現,我將窗口句柄傳入后創建過程完全改變(其他人如果使用我們編譯的代碼,他沒有傳入窗口句柄,就執行原來的創建,因此百分百兼容)。

      (ps:在這裏我講一下網絡上另外一種寫法(兩年前我也用的他們的,因為沒時間詳細看ffmpeg源碼),他們是在外面創建的device和surface然後想辦法傳到ffmpeg內部進行替換,這樣做有好處,就是不用自己修改和編譯ffmpeg,壞處是得自己維護device和surface。至於二進制兼容方面考慮,兩種做法都不是太好)

    代碼修改完成后我們使用msys2編譯

    • 首先是需要把編譯器設置為msvc,這個步驟通過使用vs的命令行工具即可,如下圖
    • 然後是設置msys2繼承環境變量(這樣make時才能找到cl/link)

    • 打開msys,查看變量是否正確
    • 編譯ffmpeg
    ./configure --enable-shared --enable-small --disable-all --disable-autodetect --enable-avcodec --enable-decoder=h264 --enable-dxva2 --enable-hwaccel=h264_dxva2 --toolchain=msvc --prefix=host
    make && make install

    編譯完成後頭文件和dll在host文件夾內(編譯產出的dll也是clear的,不依賴msvc**.dll)

      在C#中使用我們產出的方式需要使用p/invoke和unsafe代碼。

    我先貼出我針對ffmpeg寫的一個工具類,然後給大家稍微講解一下

    FFHelper.cs

    
    using System;
    using System.Runtime.InteropServices;
    
    namespace MultiPlayer
    {
        public enum AVCodecID
        {
            AV_CODEC_ID_NONE,
    
            /* video codecs */
            AV_CODEC_ID_MPEG1VIDEO,
            AV_CODEC_ID_MPEG2VIDEO, ///< preferred ID for MPEG-1/2 video decoding
            AV_CODEC_ID_H261,
            AV_CODEC_ID_H263,
            AV_CODEC_ID_RV10,
            AV_CODEC_ID_RV20,
            AV_CODEC_ID_MJPEG,
            AV_CODEC_ID_MJPEGB,
            AV_CODEC_ID_LJPEG,
            AV_CODEC_ID_SP5X,
            AV_CODEC_ID_JPEGLS,
            AV_CODEC_ID_MPEG4,
            AV_CODEC_ID_RAWVIDEO,
            AV_CODEC_ID_MSMPEG4V1,
            AV_CODEC_ID_MSMPEG4V2,
            AV_CODEC_ID_MSMPEG4V3,
            AV_CODEC_ID_WMV1,
            AV_CODEC_ID_WMV2,
            AV_CODEC_ID_H263P,
            AV_CODEC_ID_H263I,
            AV_CODEC_ID_FLV1,
            AV_CODEC_ID_SVQ1,
            AV_CODEC_ID_SVQ3,
            AV_CODEC_ID_DVVIDEO,
            AV_CODEC_ID_HUFFYUV,
            AV_CODEC_ID_CYUV,
            AV_CODEC_ID_H264,
            AV_CODEC_ID_INDEO3,
            AV_CODEC_ID_VP3,
            AV_CODEC_ID_THEORA,
            AV_CODEC_ID_ASV1,
            AV_CODEC_ID_ASV2,
            AV_CODEC_ID_FFV1,
            AV_CODEC_ID_4XM,
            AV_CODEC_ID_VCR1,
            AV_CODEC_ID_CLJR,
            AV_CODEC_ID_MDEC,
            AV_CODEC_ID_ROQ,
            AV_CODEC_ID_INTERPLAY_VIDEO,
            AV_CODEC_ID_XAN_WC3,
            AV_CODEC_ID_XAN_WC4,
            AV_CODEC_ID_RPZA,
            AV_CODEC_ID_CINEPAK,
            AV_CODEC_ID_WS_VQA,
            AV_CODEC_ID_MSRLE,
            AV_CODEC_ID_MSVIDEO1,
            AV_CODEC_ID_IDCIN,
            AV_CODEC_ID_8BPS,
            AV_CODEC_ID_SMC,
            AV_CODEC_ID_FLIC,
            AV_CODEC_ID_TRUEMOTION1,
            AV_CODEC_ID_VMDVIDEO,
            AV_CODEC_ID_MSZH,
            AV_CODEC_ID_ZLIB,
            AV_CODEC_ID_QTRLE,
            AV_CODEC_ID_TSCC,
            AV_CODEC_ID_ULTI,
            AV_CODEC_ID_QDRAW,
            AV_CODEC_ID_VIXL,
            AV_CODEC_ID_QPEG,
            AV_CODEC_ID_PNG,
            AV_CODEC_ID_PPM,
            AV_CODEC_ID_PBM,
            AV_CODEC_ID_PGM,
            AV_CODEC_ID_PGMYUV,
            AV_CODEC_ID_PAM,
            AV_CODEC_ID_FFVHUFF,
            AV_CODEC_ID_RV30,
            AV_CODEC_ID_RV40,
            AV_CODEC_ID_VC1,
            AV_CODEC_ID_WMV3,
            AV_CODEC_ID_LOCO,
            AV_CODEC_ID_WNV1,
            AV_CODEC_ID_AASC,
            AV_CODEC_ID_INDEO2,
            AV_CODEC_ID_FRAPS,
            AV_CODEC_ID_TRUEMOTION2,
            AV_CODEC_ID_BMP,
            AV_CODEC_ID_CSCD,
            AV_CODEC_ID_MMVIDEO,
            AV_CODEC_ID_ZMBV,
            AV_CODEC_ID_AVS,
            AV_CODEC_ID_SMACKVIDEO,
            AV_CODEC_ID_NUV,
            AV_CODEC_ID_KMVC,
            AV_CODEC_ID_FLASHSV,
            AV_CODEC_ID_CAVS,
            AV_CODEC_ID_JPEG2000,
            AV_CODEC_ID_VMNC,
            AV_CODEC_ID_VP5,
            AV_CODEC_ID_VP6,
            AV_CODEC_ID_VP6F,
            AV_CODEC_ID_TARGA,
            AV_CODEC_ID_DSICINVIDEO,
            AV_CODEC_ID_TIERTEXSEQVIDEO,
            AV_CODEC_ID_TIFF,
            AV_CODEC_ID_GIF,
            AV_CODEC_ID_DXA,
            AV_CODEC_ID_DNXHD,
            AV_CODEC_ID_THP,
            AV_CODEC_ID_SGI,
            AV_CODEC_ID_C93,
            AV_CODEC_ID_BETHSOFTVID,
            AV_CODEC_ID_PTX,
            AV_CODEC_ID_TXD,
            AV_CODEC_ID_VP6A,
            AV_CODEC_ID_AMV,
            AV_CODEC_ID_VB,
            AV_CODEC_ID_PCX,
            AV_CODEC_ID_SUNRAST,
            AV_CODEC_ID_INDEO4,
            AV_CODEC_ID_INDEO5,
            AV_CODEC_ID_MIMIC,
            AV_CODEC_ID_RL2,
            AV_CODEC_ID_ESCAPE124,
            AV_CODEC_ID_DIRAC,
            AV_CODEC_ID_BFI,
            AV_CODEC_ID_CMV,
            AV_CODEC_ID_MOTIONPIXELS,
            AV_CODEC_ID_TGV,
            AV_CODEC_ID_TGQ,
            AV_CODEC_ID_TQI,
            AV_CODEC_ID_AURA,
            AV_CODEC_ID_AURA2,
            AV_CODEC_ID_V210X,
            AV_CODEC_ID_TMV,
            AV_CODEC_ID_V210,
            AV_CODEC_ID_DPX,
            AV_CODEC_ID_MAD,
            AV_CODEC_ID_FRWU,
            AV_CODEC_ID_FLASHSV2,
            AV_CODEC_ID_CDGRAPHICS,
            AV_CODEC_ID_R210,
            AV_CODEC_ID_ANM,
            AV_CODEC_ID_BINKVIDEO,
            AV_CODEC_ID_IFF_ILBM,
            //#define AV_CODEC_ID_IFF_BYTERUN1 AV_CODEC_ID_IFF_ILBM
            AV_CODEC_ID_KGV1,
            AV_CODEC_ID_YOP,
            AV_CODEC_ID_VP8,
            AV_CODEC_ID_PICTOR,
            AV_CODEC_ID_ANSI,
            AV_CODEC_ID_A64_MULTI,
            AV_CODEC_ID_A64_MULTI5,
            AV_CODEC_ID_R10K,
            AV_CODEC_ID_MXPEG,
            AV_CODEC_ID_LAGARITH,
            AV_CODEC_ID_PRORES,
            AV_CODEC_ID_JV,
            AV_CODEC_ID_DFA,
            AV_CODEC_ID_WMV3IMAGE,
            AV_CODEC_ID_VC1IMAGE,
            AV_CODEC_ID_UTVIDEO,
            AV_CODEC_ID_BMV_VIDEO,
            AV_CODEC_ID_VBLE,
            AV_CODEC_ID_DXTORY,
            AV_CODEC_ID_V410,
            AV_CODEC_ID_XWD,
            AV_CODEC_ID_CDXL,
            AV_CODEC_ID_XBM,
            AV_CODEC_ID_ZEROCODEC,
            AV_CODEC_ID_MSS1,
            AV_CODEC_ID_MSA1,
            AV_CODEC_ID_TSCC2,
            AV_CODEC_ID_MTS2,
            AV_CODEC_ID_CLLC,
            AV_CODEC_ID_MSS2,
            AV_CODEC_ID_VP9,
            AV_CODEC_ID_AIC,
            AV_CODEC_ID_ESCAPE130,
            AV_CODEC_ID_G2M,
            AV_CODEC_ID_WEBP,
            AV_CODEC_ID_HNM4_VIDEO,
            AV_CODEC_ID_HEVC,
            //#define AV_CODEC_ID_H265 AV_CODEC_ID_HEVC
            AV_CODEC_ID_FIC,
            AV_CODEC_ID_ALIAS_PIX,
            AV_CODEC_ID_BRENDER_PIX,
            AV_CODEC_ID_PAF_VIDEO,
            AV_CODEC_ID_EXR,
            AV_CODEC_ID_VP7,
            AV_CODEC_ID_SANM,
            AV_CODEC_ID_SGIRLE,
            AV_CODEC_ID_MVC1,
            AV_CODEC_ID_MVC2,
            AV_CODEC_ID_HQX,
            AV_CODEC_ID_TDSC,
            AV_CODEC_ID_HQ_HQA,
            AV_CODEC_ID_HAP,
            AV_CODEC_ID_DDS,
            AV_CODEC_ID_DXV,
            AV_CODEC_ID_SCREENPRESSO,
            AV_CODEC_ID_RSCC,
            AV_CODEC_ID_AVS2,
    
            AV_CODEC_ID_Y41P = 0x8000,
            AV_CODEC_ID_AVRP,
            AV_CODEC_ID_012V,
            AV_CODEC_ID_AVUI,
            AV_CODEC_ID_AYUV,
            AV_CODEC_ID_TARGA_Y216,
            AV_CODEC_ID_V308,
            AV_CODEC_ID_V408,
            AV_CODEC_ID_YUV4,
            AV_CODEC_ID_AVRN,
            AV_CODEC_ID_CPIA,
            AV_CODEC_ID_XFACE,
            AV_CODEC_ID_SNOW,
            AV_CODEC_ID_SMVJPEG,
            AV_CODEC_ID_APNG,
            AV_CODEC_ID_DAALA,
            AV_CODEC_ID_CFHD,
            AV_CODEC_ID_TRUEMOTION2RT,
            AV_CODEC_ID_M101,
            AV_CODEC_ID_MAGICYUV,
            AV_CODEC_ID_SHEERVIDEO,
            AV_CODEC_ID_YLC,
            AV_CODEC_ID_PSD,
            AV_CODEC_ID_PIXLET,
            AV_CODEC_ID_SPEEDHQ,
            AV_CODEC_ID_FMVC,
            AV_CODEC_ID_SCPR,
            AV_CODEC_ID_CLEARVIDEO,
            AV_CODEC_ID_XPM,
            AV_CODEC_ID_AV1,
            AV_CODEC_ID_BITPACKED,
            AV_CODEC_ID_MSCC,
            AV_CODEC_ID_SRGC,
            AV_CODEC_ID_SVG,
            AV_CODEC_ID_GDV,
            AV_CODEC_ID_FITS,
            AV_CODEC_ID_IMM4,
            AV_CODEC_ID_PROSUMER,
            AV_CODEC_ID_MWSC,
            AV_CODEC_ID_WCMV,
            AV_CODEC_ID_RASC,
            AV_CODEC_ID_HYMT,
            AV_CODEC_ID_ARBC,
            AV_CODEC_ID_AGM,
            AV_CODEC_ID_LSCR,
            AV_CODEC_ID_VP4,
    
            /* various PCM "codecs" */
            AV_CODEC_ID_FIRST_AUDIO = 0x10000,     ///< A dummy id pointing at the start of audio codecs
            AV_CODEC_ID_PCM_S16LE = 0x10000,
            AV_CODEC_ID_PCM_S16BE,
            AV_CODEC_ID_PCM_U16LE,
            AV_CODEC_ID_PCM_U16BE,
            AV_CODEC_ID_PCM_S8,
            AV_CODEC_ID_PCM_U8,
            AV_CODEC_ID_PCM_MULAW,
            AV_CODEC_ID_PCM_ALAW,
            AV_CODEC_ID_PCM_S32LE,
            AV_CODEC_ID_PCM_S32BE,
            AV_CODEC_ID_PCM_U32LE,
            AV_CODEC_ID_PCM_U32BE,
            AV_CODEC_ID_PCM_S24LE,
            AV_CODEC_ID_PCM_S24BE,
            AV_CODEC_ID_PCM_U24LE,
            AV_CODEC_ID_PCM_U24BE,
            AV_CODEC_ID_PCM_S24DAUD,
            AV_CODEC_ID_PCM_ZORK,
            AV_CODEC_ID_PCM_S16LE_PLANAR,
            AV_CODEC_ID_PCM_DVD,
            AV_CODEC_ID_PCM_F32BE,
            AV_CODEC_ID_PCM_F32LE,
            AV_CODEC_ID_PCM_F64BE,
            AV_CODEC_ID_PCM_F64LE,
            AV_CODEC_ID_PCM_BLURAY,
            AV_CODEC_ID_PCM_LXF,
            AV_CODEC_ID_S302M,
            AV_CODEC_ID_PCM_S8_PLANAR,
            AV_CODEC_ID_PCM_S24LE_PLANAR,
            AV_CODEC_ID_PCM_S32LE_PLANAR,
            AV_CODEC_ID_PCM_S16BE_PLANAR,
    
            AV_CODEC_ID_PCM_S64LE = 0x10800,
            AV_CODEC_ID_PCM_S64BE,
            AV_CODEC_ID_PCM_F16LE,
            AV_CODEC_ID_PCM_F24LE,
            AV_CODEC_ID_PCM_VIDC,
    
            /* various ADPCM codecs */
            AV_CODEC_ID_ADPCM_IMA_QT = 0x11000,
            AV_CODEC_ID_ADPCM_IMA_WAV,
            AV_CODEC_ID_ADPCM_IMA_DK3,
            AV_CODEC_ID_ADPCM_IMA_DK4,
            AV_CODEC_ID_ADPCM_IMA_WS,
            AV_CODEC_ID_ADPCM_IMA_SMJPEG,
            AV_CODEC_ID_ADPCM_MS,
            AV_CODEC_ID_ADPCM_4XM,
            AV_CODEC_ID_ADPCM_XA,
            AV_CODEC_ID_ADPCM_ADX,
            AV_CODEC_ID_ADPCM_EA,
            AV_CODEC_ID_ADPCM_G726,
            AV_CODEC_ID_ADPCM_CT,
            AV_CODEC_ID_ADPCM_SWF,
            AV_CODEC_ID_ADPCM_YAMAHA,
            AV_CODEC_ID_ADPCM_SBPRO_4,
            AV_CODEC_ID_ADPCM_SBPRO_3,
            AV_CODEC_ID_ADPCM_SBPRO_2,
            AV_CODEC_ID_ADPCM_THP,
            AV_CODEC_ID_ADPCM_IMA_AMV,
            AV_CODEC_ID_ADPCM_EA_R1,
            AV_CODEC_ID_ADPCM_EA_R3,
            AV_CODEC_ID_ADPCM_EA_R2,
            AV_CODEC_ID_ADPCM_IMA_EA_SEAD,
            AV_CODEC_ID_ADPCM_IMA_EA_EACS,
            AV_CODEC_ID_ADPCM_EA_XAS,
            AV_CODEC_ID_ADPCM_EA_MAXIS_XA,
            AV_CODEC_ID_ADPCM_IMA_ISS,
            AV_CODEC_ID_ADPCM_G722,
            AV_CODEC_ID_ADPCM_IMA_APC,
            AV_CODEC_ID_ADPCM_VIMA,
    
            AV_CODEC_ID_ADPCM_AFC = 0x11800,
            AV_CODEC_ID_ADPCM_IMA_OKI,
            AV_CODEC_ID_ADPCM_DTK,
            AV_CODEC_ID_ADPCM_IMA_RAD,
            AV_CODEC_ID_ADPCM_G726LE,
            AV_CODEC_ID_ADPCM_THP_LE,
            AV_CODEC_ID_ADPCM_PSX,
            AV_CODEC_ID_ADPCM_AICA,
            AV_CODEC_ID_ADPCM_IMA_DAT4,
            AV_CODEC_ID_ADPCM_MTAF,
            AV_CODEC_ID_ADPCM_AGM,
    
            /* AMR */
            AV_CODEC_ID_AMR_NB = 0x12000,
            AV_CODEC_ID_AMR_WB,
    
            /* RealAudio codecs*/
            AV_CODEC_ID_RA_144 = 0x13000,
            AV_CODEC_ID_RA_288,
    
            /* various DPCM codecs */
            AV_CODEC_ID_ROQ_DPCM = 0x14000,
            AV_CODEC_ID_INTERPLAY_DPCM,
            AV_CODEC_ID_XAN_DPCM,
            AV_CODEC_ID_SOL_DPCM,
    
            AV_CODEC_ID_SDX2_DPCM = 0x14800,
            AV_CODEC_ID_GREMLIN_DPCM,
    
            /* audio codecs */
            AV_CODEC_ID_MP2 = 0x15000,
            AV_CODEC_ID_MP3, ///< preferred ID for decoding MPEG audio layer 1, 2 or 3
            AV_CODEC_ID_AAC,
            AV_CODEC_ID_AC3,
            AV_CODEC_ID_DTS,
            AV_CODEC_ID_VORBIS,
            AV_CODEC_ID_DVAUDIO,
            AV_CODEC_ID_WMAV1,
            AV_CODEC_ID_WMAV2,
            AV_CODEC_ID_MACE3,
            AV_CODEC_ID_MACE6,
            AV_CODEC_ID_VMDAUDIO,
            AV_CODEC_ID_FLAC,
            AV_CODEC_ID_MP3ADU,
            AV_CODEC_ID_MP3ON4,
            AV_CODEC_ID_SHORTEN,
            AV_CODEC_ID_ALAC,
            AV_CODEC_ID_WESTWOOD_SND1,
            AV_CODEC_ID_GSM, ///< as in Berlin toast format
            AV_CODEC_ID_QDM2,
            AV_CODEC_ID_COOK,
            AV_CODEC_ID_TRUESPEECH,
            AV_CODEC_ID_TTA,
            AV_CODEC_ID_SMACKAUDIO,
            AV_CODEC_ID_QCELP,
            AV_CODEC_ID_WAVPACK,
            AV_CODEC_ID_DSICINAUDIO,
            AV_CODEC_ID_IMC,
            AV_CODEC_ID_MUSEPACK7,
            AV_CODEC_ID_MLP,
            AV_CODEC_ID_GSM_MS, /* as found in WAV */
            AV_CODEC_ID_ATRAC3,
            AV_CODEC_ID_APE,
            AV_CODEC_ID_NELLYMOSER,
            AV_CODEC_ID_MUSEPACK8,
            AV_CODEC_ID_SPEEX,
            AV_CODEC_ID_WMAVOICE,
            AV_CODEC_ID_WMAPRO,
            AV_CODEC_ID_WMALOSSLESS,
            AV_CODEC_ID_ATRAC3P,
            AV_CODEC_ID_EAC3,
            AV_CODEC_ID_SIPR,
            AV_CODEC_ID_MP1,
            AV_CODEC_ID_TWINVQ,
            AV_CODEC_ID_TRUEHD,
            AV_CODEC_ID_MP4ALS,
            AV_CODEC_ID_ATRAC1,
            AV_CODEC_ID_BINKAUDIO_RDFT,
            AV_CODEC_ID_BINKAUDIO_DCT,
            AV_CODEC_ID_AAC_LATM,
            AV_CODEC_ID_QDMC,
            AV_CODEC_ID_CELT,
            AV_CODEC_ID_G723_1,
            AV_CODEC_ID_G729,
            AV_CODEC_ID_8SVX_EXP,
            AV_CODEC_ID_8SVX_FIB,
            AV_CODEC_ID_BMV_AUDIO,
            AV_CODEC_ID_RALF,
            AV_CODEC_ID_IAC,
            AV_CODEC_ID_ILBC,
            AV_CODEC_ID_OPUS,
            AV_CODEC_ID_COMFORT_NOISE,
            AV_CODEC_ID_TAK,
            AV_CODEC_ID_METASOUND,
            AV_CODEC_ID_PAF_AUDIO,
            AV_CODEC_ID_ON2AVC,
            AV_CODEC_ID_DSS_SP,
            AV_CODEC_ID_CODEC2,
    
            AV_CODEC_ID_FFWAVESYNTH = 0x15800,
            AV_CODEC_ID_SONIC,
            AV_CODEC_ID_SONIC_LS,
            AV_CODEC_ID_EVRC,
            AV_CODEC_ID_SMV,
            AV_CODEC_ID_DSD_LSBF,
            AV_CODEC_ID_DSD_MSBF,
            AV_CODEC_ID_DSD_LSBF_PLANAR,
            AV_CODEC_ID_DSD_MSBF_PLANAR,
            AV_CODEC_ID_4GV,
            AV_CODEC_ID_INTERPLAY_ACM,
            AV_CODEC_ID_XMA1,
            AV_CODEC_ID_XMA2,
            AV_CODEC_ID_DST,
            AV_CODEC_ID_ATRAC3AL,
            AV_CODEC_ID_ATRAC3PAL,
            AV_CODEC_ID_DOLBY_E,
            AV_CODEC_ID_APTX,
            AV_CODEC_ID_APTX_HD,
            AV_CODEC_ID_SBC,
            AV_CODEC_ID_ATRAC9,
            AV_CODEC_ID_HCOM,
    
            /* subtitle codecs */
            AV_CODEC_ID_FIRST_SUBTITLE = 0x17000,          ///< A dummy ID pointing at the start of subtitle codecs.
            AV_CODEC_ID_DVD_SUBTITLE = 0x17000,
            AV_CODEC_ID_DVB_SUBTITLE,
            AV_CODEC_ID_TEXT,  ///< raw UTF-8 text
            AV_CODEC_ID_XSUB,
            AV_CODEC_ID_SSA,
            AV_CODEC_ID_MOV_TEXT,
            AV_CODEC_ID_HDMV_PGS_SUBTITLE,
            AV_CODEC_ID_DVB_TELETEXT,
            AV_CODEC_ID_SRT,
    
            AV_CODEC_ID_MICRODVD = 0x17800,
            AV_CODEC_ID_EIA_608,
            AV_CODEC_ID_JACOSUB,
            AV_CODEC_ID_SAMI,
            AV_CODEC_ID_REALTEXT,
            AV_CODEC_ID_STL,
            AV_CODEC_ID_SUBVIEWER1,
            AV_CODEC_ID_SUBVIEWER,
            AV_CODEC_ID_SUBRIP,
            AV_CODEC_ID_WEBVTT,
            AV_CODEC_ID_MPL2,
            AV_CODEC_ID_VPLAYER,
            AV_CODEC_ID_PJS,
            AV_CODEC_ID_ASS,
            AV_CODEC_ID_HDMV_TEXT_SUBTITLE,
            AV_CODEC_ID_TTML,
            AV_CODEC_ID_ARIB_CAPTION,
    
            /* other specific kind of codecs (generally used for attachments) */
            AV_CODEC_ID_FIRST_UNKNOWN = 0x18000,           ///< A dummy ID pointing at the start of various fake codecs.
            AV_CODEC_ID_TTF = 0x18000,
    
            AV_CODEC_ID_SCTE_35, ///< Contain timestamp estimated through PCR of program stream.
            AV_CODEC_ID_BINTEXT = 0x18800,
            AV_CODEC_ID_XBIN,
            AV_CODEC_ID_IDF,
            AV_CODEC_ID_OTF,
            AV_CODEC_ID_SMPTE_KLV,
            AV_CODEC_ID_DVD_NAV,
            AV_CODEC_ID_TIMED_ID3,
            AV_CODEC_ID_BIN_DATA,
    
    
            AV_CODEC_ID_PROBE = 0x19000, ///< codec_id is not known (like AV_CODEC_ID_NONE) but lavf should attempt to identify it
    
            AV_CODEC_ID_MPEG2TS = 0x20000, /**< _FAKE_ codec to indicate a raw MPEG-2 TS
                                    * stream (only used by libavformat) */
            AV_CODEC_ID_MPEG4SYSTEMS = 0x20001, /**< _FAKE_ codec to indicate a MPEG-4 Systems
                                    * stream (only used by libavformat) */
            AV_CODEC_ID_FFMETADATA = 0x21000,   ///< Dummy codec for streams containing only metadata information.
            AV_CODEC_ID_WRAPPED_AVFRAME = 0x21001, ///< Passthrough codec, AVFrames wrapped in AVPacket
        }
    
        public enum AVHWDeviceType
        {
            AV_HWDEVICE_TYPE_NONE,
            AV_HWDEVICE_TYPE_VDPAU,
            AV_HWDEVICE_TYPE_CUDA,
            AV_HWDEVICE_TYPE_VAAPI,
            AV_HWDEVICE_TYPE_DXVA2,
            AV_HWDEVICE_TYPE_QSV,
            AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
            AV_HWDEVICE_TYPE_D3D11VA,
            AV_HWDEVICE_TYPE_DRM,
            AV_HWDEVICE_TYPE_OPENCL,
            AV_HWDEVICE_TYPE_MEDIACODEC,
        }
    
        public enum AVPixelFormat
        {
            AV_PIX_FMT_NONE = -1,
            AV_PIX_FMT_YUV420P,   ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)
            AV_PIX_FMT_YUYV422,   ///< packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr
            AV_PIX_FMT_RGB24,     ///< packed RGB 8:8:8, 24bpp, RGBRGB...
            AV_PIX_FMT_BGR24,     ///< packed RGB 8:8:8, 24bpp, BGRBGR...
            AV_PIX_FMT_YUV422P,   ///< planar YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples)
            AV_PIX_FMT_YUV444P,   ///< planar YUV 4:4:4, 24bpp, (1 Cr & Cb sample per 1x1 Y samples)
            AV_PIX_FMT_YUV410P,   ///< planar YUV 4:1:0,  9bpp, (1 Cr & Cb sample per 4x4 Y samples)
            AV_PIX_FMT_YUV411P,   ///< planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1 Y samples)
            AV_PIX_FMT_GRAY8,     ///<        Y        ,  8bpp
            AV_PIX_FMT_MONOWHITE, ///<        Y        ,  1bpp, 0 is white, 1 is black, in each byte pixels are ordered from the msb to the lsb
            AV_PIX_FMT_MONOBLACK, ///<        Y        ,  1bpp, 0 is black, 1 is white, in each byte pixels are ordered from the msb to the lsb
            AV_PIX_FMT_PAL8,      ///< 8 bits with AV_PIX_FMT_RGB32 palette
            AV_PIX_FMT_YUVJ420P,  ///< planar YUV 4:2:0, 12bpp, full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV420P and setting color_range
            AV_PIX_FMT_YUVJ422P,  ///< planar YUV 4:2:2, 16bpp, full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV422P and setting color_range
            AV_PIX_FMT_YUVJ444P,  ///< planar YUV 4:4:4, 24bpp, full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV444P and setting color_range
            AV_PIX_FMT_UYVY422,   ///< packed YUV 4:2:2, 16bpp, Cb Y0 Cr Y1
            AV_PIX_FMT_UYYVYY411, ///< packed YUV 4:1:1, 12bpp, Cb Y0 Y1 Cr Y2 Y3
            AV_PIX_FMT_BGR8,      ///< packed RGB 3:3:2,  8bpp, (msb)2B 3G 3R(lsb)
            AV_PIX_FMT_BGR4,      ///< packed RGB 1:2:1 bitstream,  4bpp, (msb)1B 2G 1R(lsb), a byte contains two pixels, the first pixel in the byte is the one composed by the 4 msb bits
            AV_PIX_FMT_BGR4_BYTE, ///< packed RGB 1:2:1,  8bpp, (msb)1B 2G 1R(lsb)
            AV_PIX_FMT_RGB8,      ///< packed RGB 3:3:2,  8bpp, (msb)2R 3G 3B(lsb)
            AV_PIX_FMT_RGB4,      ///< packed RGB 1:2:1 bitstream,  4bpp, (msb)1R 2G 1B(lsb), a byte contains two pixels, the first pixel in the byte is the one composed by the 4 msb bits
            AV_PIX_FMT_RGB4_BYTE, ///< packed RGB 1:2:1,  8bpp, (msb)1R 2G 1B(lsb)
            AV_PIX_FMT_NV12,      ///< planar YUV 4:2:0, 12bpp, 1 plane for Y and 1 plane for the UV components, which are interleaved (first byte U and the following byte V)
            AV_PIX_FMT_NV21,      ///< as above, but U and V bytes are swapped
    
            AV_PIX_FMT_ARGB,      ///< packed ARGB 8:8:8:8, 32bpp, ARGBARGB...
            AV_PIX_FMT_RGBA,      ///< packed RGBA 8:8:8:8, 32bpp, RGBARGBA...
            AV_PIX_FMT_ABGR,      ///< packed ABGR 8:8:8:8, 32bpp, ABGRABGR...
            AV_PIX_FMT_BGRA,      ///< packed BGRA 8:8:8:8, 32bpp, BGRABGRA...
    
            AV_PIX_FMT_GRAY16BE,  ///<        Y        , 16bpp, big-endian
            AV_PIX_FMT_GRAY16LE,  ///<        Y        , 16bpp, little-endian
            AV_PIX_FMT_YUV440P,   ///< planar YUV 4:4:0 (1 Cr & Cb sample per 1x2 Y samples)
            AV_PIX_FMT_YUVJ440P,  ///< planar YUV 4:4:0 full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV440P and setting color_range
            AV_PIX_FMT_YUVA420P,  ///< planar YUV 4:2:0, 20bpp, (1 Cr & Cb sample per 2x2 Y & A samples)
            AV_PIX_FMT_RGB48BE,   ///< packed RGB 16:16:16, 48bpp, 16R, 16G, 16B, the 2-byte value for each R/G/B component is stored as big-endian
            AV_PIX_FMT_RGB48LE,   ///< packed RGB 16:16:16, 48bpp, 16R, 16G, 16B, the 2-byte value for each R/G/B component is stored as little-endian
    
            AV_PIX_FMT_RGB565BE,  ///< packed RGB 5:6:5, 16bpp, (msb)   5R 6G 5B(lsb), big-endian
            AV_PIX_FMT_RGB565LE,  ///< packed RGB 5:6:5, 16bpp, (msb)   5R 6G 5B(lsb), little-endian
            AV_PIX_FMT_RGB555BE,  ///< packed RGB 5:5:5, 16bpp, (msb)1X 5R 5G 5B(lsb), big-endian   , X=unused/undefined
            AV_PIX_FMT_RGB555LE,  ///< packed RGB 5:5:5, 16bpp, (msb)1X 5R 5G 5B(lsb), little-endian, X=unused/undefined
    
            AV_PIX_FMT_BGR565BE,  ///< packed BGR 5:6:5, 16bpp, (msb)   5B 6G 5R(lsb), big-endian
            AV_PIX_FMT_BGR565LE,  ///< packed BGR 5:6:5, 16bpp, (msb)   5B 6G 5R(lsb), little-endian
            AV_PIX_FMT_BGR555BE,  ///< packed BGR 5:5:5, 16bpp, (msb)1X 5B 5G 5R(lsb), big-endian   , X=unused/undefined
            AV_PIX_FMT_BGR555LE,  ///< packed BGR 5:5:5, 16bpp, (msb)1X 5B 5G 5R(lsb), little-endian, X=unused/undefined
    
            /** @name Deprecated pixel formats */
            /**@{*/
            AV_PIX_FMT_VAAPI_MOCO, ///< HW acceleration through VA API at motion compensation entry-point, Picture.data[3] contains a vaapi_render_state struct which contains macroblocks as well as various fields extracted from headers
            AV_PIX_FMT_VAAPI_IDCT, ///< HW acceleration through VA API at IDCT entry-point, Picture.data[3] contains a vaapi_render_state struct which contains fields extracted from headers
            AV_PIX_FMT_VAAPI_VLD,  ///< HW decoding through VA API, Picture.data[3] contains a VASurfaceID
            /**@}*/
            AV_PIX_FMT_VAAPI = AV_PIX_FMT_VAAPI_VLD,
    
            AV_PIX_FMT_YUV420P16LE,  ///< planar YUV 4:2:0, 24bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian
            AV_PIX_FMT_YUV420P16BE,  ///< planar YUV 4:2:0, 24bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian
            AV_PIX_FMT_YUV422P16LE,  ///< planar YUV 4:2:2, 32bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian
            AV_PIX_FMT_YUV422P16BE,  ///< planar YUV 4:2:2, 32bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian
            AV_PIX_FMT_YUV444P16LE,  ///< planar YUV 4:4:4, 48bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian
            AV_PIX_FMT_YUV444P16BE,  ///< planar YUV 4:4:4, 48bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian
            AV_PIX_FMT_DXVA2_VLD,    ///< HW decoding through DXVA2, Picture.data[3] contains a LPDIRECT3DSURFACE9 pointer
    
            AV_PIX_FMT_RGB444LE,  ///< packed RGB 4:4:4, 16bpp, (msb)4X 4R 4G 4B(lsb), little-endian, X=unused/undefined
            AV_PIX_FMT_RGB444BE,  ///< packed RGB 4:4:4, 16bpp, (msb)4X 4R 4G 4B(lsb), big-endian,    X=unused/undefined
            AV_PIX_FMT_BGR444LE,  ///< packed BGR 4:4:4, 16bpp, (msb)4X 4B 4G 4R(lsb), little-endian, X=unused/undefined
            AV_PIX_FMT_BGR444BE,  ///< packed BGR 4:4:4, 16bpp, (msb)4X 4B 4G 4R(lsb), big-endian,    X=unused/undefined
            AV_PIX_FMT_YA8,       ///< 8 bits gray, 8 bits alpha
    
            AV_PIX_FMT_Y400A = AV_PIX_FMT_YA8, ///< alias for AV_PIX_FMT_YA8
            AV_PIX_FMT_GRAY8A = AV_PIX_FMT_YA8, ///< alias for AV_PIX_FMT_YA8
    
            AV_PIX_FMT_BGR48BE,   ///< packed RGB 16:16:16, 48bpp, 16B, 16G, 16R, the 2-byte value for each R/G/B component is stored as big-endian
            AV_PIX_FMT_BGR48LE,   ///< packed RGB 16:16:16, 48bpp, 16B, 16G, 16R, the 2-byte value for each R/G/B component is stored as little-endian
    
            /**
             * The following 12 formats have the disadvantage of needing 1 format for each bit depth.
             * Notice that each 9/10 bits sample is stored in 16 bits with extra padding.
             * If you want to support multiple bit depths, then using AV_PIX_FMT_YUV420P16* with the bpp stored separately is better.
             */
            AV_PIX_FMT_YUV420P9BE, ///< planar YUV 4:2:0, 13.5bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian
            AV_PIX_FMT_YUV420P9LE, ///< planar YUV 4:2:0, 13.5bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian
            AV_PIX_FMT_YUV420P10BE,///< planar YUV 4:2:0, 15bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian
            AV_PIX_FMT_YUV420P10LE,///< planar YUV 4:2:0, 15bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian
            AV_PIX_FMT_YUV422P10BE,///< planar YUV 4:2:2, 20bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian
            AV_PIX_FMT_YUV422P10LE,///< planar YUV 4:2:2, 20bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian
            AV_PIX_FMT_YUV444P9BE, ///< planar YUV 4:4:4, 27bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian
            AV_PIX_FMT_YUV444P9LE, ///< planar YUV 4:4:4, 27bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian
            AV_PIX_FMT_YUV444P10BE,///< planar YUV 4:4:4, 30bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian
            AV_PIX_FMT_YUV444P10LE,///< planar YUV 4:4:4, 30bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian
            AV_PIX_FMT_YUV422P9BE, ///< planar YUV 4:2:2, 18bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian
            AV_PIX_FMT_YUV422P9LE, ///< planar YUV 4:2:2, 18bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian
            AV_PIX_FMT_GBRP,      ///< planar GBR 4:4:4 24bpp
            AV_PIX_FMT_GBR24P = AV_PIX_FMT_GBRP, // alias for #AV_PIX_FMT_GBRP
            AV_PIX_FMT_GBRP9BE,   ///< planar GBR 4:4:4 27bpp, big-endian
            AV_PIX_FMT_GBRP9LE,   ///< planar GBR 4:4:4 27bpp, little-endian
            AV_PIX_FMT_GBRP10BE,  ///< planar GBR 4:4:4 30bpp, big-endian
            AV_PIX_FMT_GBRP10LE,  ///< planar GBR 4:4:4 30bpp, little-endian
            AV_PIX_FMT_GBRP16BE,  ///< planar GBR 4:4:4 48bpp, big-endian
            AV_PIX_FMT_GBRP16LE,  ///< planar GBR 4:4:4 48bpp, little-endian
            AV_PIX_FMT_YUVA422P,  ///< planar YUV 4:2:2 24bpp, (1 Cr & Cb sample per 2x1 Y & A samples)
            AV_PIX_FMT_YUVA444P,  ///< planar YUV 4:4:4 32bpp, (1 Cr & Cb sample per 1x1 Y & A samples)
            AV_PIX_FMT_YUVA420P9BE,  ///< planar YUV 4:2:0 22.5bpp, (1 Cr & Cb sample per 2x2 Y & A samples), big-endian
            AV_PIX_FMT_YUVA420P9LE,  ///< planar YUV 4:2:0 22.5bpp, (1 Cr & Cb sample per 2x2 Y & A samples), little-endian
            AV_PIX_FMT_YUVA422P9BE,  ///< planar YUV 4:2:2 27bpp, (1 Cr & Cb sample per 2x1 Y & A samples), big-endian
            AV_PIX_FMT_YUVA422P9LE,  ///< planar YUV 4:2:2 27bpp, (1 Cr & Cb sample per 2x1 Y & A samples), little-endian
            AV_PIX_FMT_YUVA444P9BE,  ///< planar YUV 4:4:4 36bpp, (1 Cr & Cb sample per 1x1 Y & A samples), big-endian
            AV_PIX_FMT_YUVA444P9LE,  ///< planar YUV 4:4:4 36bpp, (1 Cr & Cb sample per 1x1 Y & A samples), little-endian
            AV_PIX_FMT_YUVA420P10BE, ///< planar YUV 4:2:0 25bpp, (1 Cr & Cb sample per 2x2 Y & A samples, big-endian)
            AV_PIX_FMT_YUVA420P10LE, ///< planar YUV 4:2:0 25bpp, (1 Cr & Cb sample per 2x2 Y & A samples, little-endian)
            AV_PIX_FMT_YUVA422P10BE, ///< planar YUV 4:2:2 30bpp, (1 Cr & Cb sample per 2x1 Y & A samples, big-endian)
            AV_PIX_FMT_YUVA422P10LE, ///< planar YUV 4:2:2 30bpp, (1 Cr & Cb sample per 2x1 Y & A samples, little-endian)
            AV_PIX_FMT_YUVA444P10BE, ///< planar YUV 4:4:4 40bpp, (1 Cr & Cb sample per 1x1 Y & A samples, big-endian)
            AV_PIX_FMT_YUVA444P10LE, ///< planar YUV 4:4:4 40bpp, (1 Cr & Cb sample per 1x1 Y & A samples, little-endian)
            AV_PIX_FMT_YUVA420P16BE, ///< planar YUV 4:2:0 40bpp, (1 Cr & Cb sample per 2x2 Y & A samples, big-endian)
            AV_PIX_FMT_YUVA420P16LE, ///< planar YUV 4:2:0 40bpp, (1 Cr & Cb sample per 2x2 Y & A samples, little-endian)
            AV_PIX_FMT_YUVA422P16BE, ///< planar YUV 4:2:2 48bpp, (1 Cr & Cb sample per 2x1 Y & A samples, big-endian)
            AV_PIX_FMT_YUVA422P16LE, ///< planar YUV 4:2:2 48bpp, (1 Cr & Cb sample per 2x1 Y & A samples, little-endian)
            AV_PIX_FMT_YUVA444P16BE, ///< planar YUV 4:4:4 64bpp, (1 Cr & Cb sample per 1x1 Y & A samples, big-endian)
            AV_PIX_FMT_YUVA444P16LE, ///< planar YUV 4:4:4 64bpp, (1 Cr & Cb sample per 1x1 Y & A samples, little-endian)
    
            AV_PIX_FMT_VDPAU,     ///< HW acceleration through VDPAU, Picture.data[3] contains a VdpVideoSurface
    
            AV_PIX_FMT_XYZ12LE,      ///< packed XYZ 4:4:4, 36 bpp, (msb) 12X, 12Y, 12Z (lsb), the 2-byte value for each X/Y/Z is stored as little-endian, the 4 lower bits are set to 0
            AV_PIX_FMT_XYZ12BE,      ///< packed XYZ 4:4:4, 36 bpp, (msb) 12X, 12Y, 12Z (lsb), the 2-byte value for each X/Y/Z is stored as big-endian, the 4 lower bits are set to 0
            AV_PIX_FMT_NV16,         ///< interleaved chroma YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples)
            AV_PIX_FMT_NV20LE,       ///< interleaved chroma YUV 4:2:2, 20bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian
            AV_PIX_FMT_NV20BE,       ///< interleaved chroma YUV 4:2:2, 20bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian
    
            AV_PIX_FMT_RGBA64BE,     ///< packed RGBA 16:16:16:16, 64bpp, 16R, 16G, 16B, 16A, the 2-byte value for each R/G/B/A component is stored as big-endian
            AV_PIX_FMT_RGBA64LE,     ///< packed RGBA 16:16:16:16, 64bpp, 16R, 16G, 16B, 16A, the 2-byte value for each R/G/B/A component is stored as little-endian
            AV_PIX_FMT_BGRA64BE,     ///< packed RGBA 16:16:16:16, 64bpp, 16B, 16G, 16R, 16A, the 2-byte value for each R/G/B/A component is stored as big-endian
            AV_PIX_FMT_BGRA64LE,     ///< packed RGBA 16:16:16:16, 64bpp, 16B, 16G, 16R, 16A, the 2-byte value for each R/G/B/A component is stored as little-endian
    
            AV_PIX_FMT_YVYU422,   ///< packed YUV 4:2:2, 16bpp, Y0 Cr Y1 Cb
    
            AV_PIX_FMT_YA16BE,       ///< 16 bits gray, 16 bits alpha (big-endian)
            AV_PIX_FMT_YA16LE,       ///< 16 bits gray, 16 bits alpha (little-endian)
    
            AV_PIX_FMT_GBRAP,        ///< planar GBRA 4:4:4:4 32bpp
            AV_PIX_FMT_GBRAP16BE,    ///< planar GBRA 4:4:4:4 64bpp, big-endian
            AV_PIX_FMT_GBRAP16LE,    ///< planar GBRA 4:4:4:4 64bpp, little-endian
            /**
             *  HW acceleration through QSV, data[3] contains a pointer to the
             *  mfxFrameSurface1 structure.
             */
            AV_PIX_FMT_QSV,
            /**
             * HW acceleration though MMAL, data[3] contains a pointer to the
             * MMAL_BUFFER_HEADER_T structure.
             */
            AV_PIX_FMT_MMAL,
    
            AV_PIX_FMT_D3D11VA_VLD,  ///< HW decoding through Direct3D11 via old API, Picture.data[3] contains a ID3D11VideoDecoderOutputView pointer
    
            /**
             * HW acceleration through CUDA. data[i] contain CUdeviceptr pointers
             * exactly as for system memory frames.
             */
            AV_PIX_FMT_CUDA,
    
            AV_PIX_FMT_0RGB,        ///< packed RGB 8:8:8, 32bpp, XRGBXRGB...   X=unused/undefined
            AV_PIX_FMT_RGB0,        ///< packed RGB 8:8:8, 32bpp, RGBXRGBX...   X=unused/undefined
            AV_PIX_FMT_0BGR,        ///< packed BGR 8:8:8, 32bpp, XBGRXBGR...   X=unused/undefined
            AV_PIX_FMT_BGR0,        ///< packed BGR 8:8:8, 32bpp, BGRXBGRX...   X=unused/undefined
    
            AV_PIX_FMT_YUV420P12BE, ///< planar YUV 4:2:0,18bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian
            AV_PIX_FMT_YUV420P12LE, ///< planar YUV 4:2:0,18bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian
            AV_PIX_FMT_YUV420P14BE, ///< planar YUV 4:2:0,21bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian
            AV_PIX_FMT_YUV420P14LE, ///< planar YUV 4:2:0,21bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian
            AV_PIX_FMT_YUV422P12BE, ///< planar YUV 4:2:2,24bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian
            AV_PIX_FMT_YUV422P12LE, ///< planar YUV 4:2:2,24bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian
            AV_PIX_FMT_YUV422P14BE, ///< planar YUV 4:2:2,28bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian
            AV_PIX_FMT_YUV422P14LE, ///< planar YUV 4:2:2,28bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian
            AV_PIX_FMT_YUV444P12BE, ///< planar YUV 4:4:4,36bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian
            AV_PIX_FMT_YUV444P12LE, ///< planar YUV 4:4:4,36bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian
            AV_PIX_FMT_YUV444P14BE, ///< planar YUV 4:4:4,42bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian
            AV_PIX_FMT_YUV444P14LE, ///< planar YUV 4:4:4,42bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian
            AV_PIX_FMT_GBRP12BE,    ///< planar GBR 4:4:4 36bpp, big-endian
            AV_PIX_FMT_GBRP12LE,    ///< planar GBR 4:4:4 36bpp, little-endian
            AV_PIX_FMT_GBRP14BE,    ///< planar GBR 4:4:4 42bpp, big-endian
            AV_PIX_FMT_GBRP14LE,    ///< planar GBR 4:4:4 42bpp, little-endian
            AV_PIX_FMT_YUVJ411P,    ///< planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1 Y samples) full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV411P and setting color_range
    
            AV_PIX_FMT_BAYER_BGGR8,    ///< bayer, BGBG..(odd line), GRGR..(even line), 8-bit samples */
            AV_PIX_FMT_BAYER_RGGB8,    ///< bayer, RGRG..(odd line), GBGB..(even line), 8-bit samples */
            AV_PIX_FMT_BAYER_GBRG8,    ///< bayer, GBGB..(odd line), RGRG..(even line), 8-bit samples */
            AV_PIX_FMT_BAYER_GRBG8,    ///< bayer, GRGR..(odd line), BGBG..(even line), 8-bit samples */
            AV_PIX_FMT_BAYER_BGGR16LE, ///< bayer, BGBG..(odd line), GRGR..(even line), 16-bit samples, little-endian */
            AV_PIX_FMT_BAYER_BGGR16BE, ///< bayer, BGBG..(odd line), GRGR..(even line), 16-bit samples, big-endian */
            AV_PIX_FMT_BAYER_RGGB16LE, ///< bayer, RGRG..(odd line), GBGB..(even line), 16-bit samples, little-endian */
            AV_PIX_FMT_BAYER_RGGB16BE, ///< bayer, RGRG..(odd line), GBGB..(even line), 16-bit samples, big-endian */
            AV_PIX_FMT_BAYER_GBRG16LE, ///< bayer, GBGB..(odd line), RGRG..(even line), 16-bit samples, little-endian */
            AV_PIX_FMT_BAYER_GBRG16BE, ///< bayer, GBGB..(odd line), RGRG..(even line), 16-bit samples, big-endian */
            AV_PIX_FMT_BAYER_GRBG16LE, ///< bayer, GRGR..(odd line), BGBG..(even line), 16-bit samples, little-endian */
            AV_PIX_FMT_BAYER_GRBG16BE, ///< bayer, GRGR..(odd line), BGBG..(even line), 16-bit samples, big-endian */
    
            AV_PIX_FMT_XVMC,///< XVideo Motion Acceleration via common packet passing
    
            AV_PIX_FMT_YUV440P10LE, ///< planar YUV 4:4:0,20bpp, (1 Cr & Cb sample per 1x2 Y samples), little-endian
            AV_PIX_FMT_YUV440P10BE, ///< planar YUV 4:4:0,20bpp, (1 Cr & Cb sample per 1x2 Y samples), big-endian
            AV_PIX_FMT_YUV440P12LE, ///< planar YUV 4:4:0,24bpp, (1 Cr & Cb sample per 1x2 Y samples), little-endian
            AV_PIX_FMT_YUV440P12BE, ///< planar YUV 4:4:0,24bpp, (1 Cr & Cb sample per 1x2 Y samples), big-endian
            AV_PIX_FMT_AYUV64LE,    ///< packed AYUV 4:4:4,64bpp (1 Cr & Cb sample per 1x1 Y & A samples), little-endian
            AV_PIX_FMT_AYUV64BE,    ///< packed AYUV 4:4:4,64bpp (1 Cr & Cb sample per 1x1 Y & A samples), big-endian
    
            AV_PIX_FMT_VIDEOTOOLBOX, ///< hardware decoding through Videotoolbox
    
            AV_PIX_FMT_P010LE, ///< like NV12, with 10bpp per component, data in the high bits, zeros in the low bits, little-endian
            AV_PIX_FMT_P010BE, ///< like NV12, with 10bpp per component, data in the high bits, zeros in the low bits, big-endian
    
            AV_PIX_FMT_GBRAP12BE,  ///< planar GBR 4:4:4:4 48bpp, big-endian
            AV_PIX_FMT_GBRAP12LE,  ///< planar GBR 4:4:4:4 48bpp, little-endian
    
            AV_PIX_FMT_GBRAP10BE,  ///< planar GBR 4:4:4:4 40bpp, big-endian
            AV_PIX_FMT_GBRAP10LE,  ///< planar GBR 4:4:4:4 40bpp, little-endian
    
            AV_PIX_FMT_MEDIACODEC, ///< hardware decoding through MediaCodec
    
            AV_PIX_FMT_GRAY12BE,   ///<        Y        , 12bpp, big-endian
            AV_PIX_FMT_GRAY12LE,   ///<        Y        , 12bpp, little-endian
            AV_PIX_FMT_GRAY10BE,   ///<        Y        , 10bpp, big-endian
            AV_PIX_FMT_GRAY10LE,   ///<        Y        , 10bpp, little-endian
    
            AV_PIX_FMT_P016LE, ///< like NV12, with 16bpp per component, little-endian
            AV_PIX_FMT_P016BE, ///< like NV12, with 16bpp per component, big-endian
    
            /**
             * Hardware surfaces for Direct3D11.
             *
             * This is preferred over the legacy AV_PIX_FMT_D3D11VA_VLD. The new D3D11
             * hwaccel API and filtering support AV_PIX_FMT_D3D11 only.
             *
             * data[0] contains a ID3D11Texture2D pointer, and data[1] contains the
             * texture array index of the frame as intptr_t if the ID3D11Texture2D is
             * an array texture (or always 0 if it's a normal texture).
             */
            AV_PIX_FMT_D3D11,
    
            AV_PIX_FMT_GRAY9BE,   ///<        Y        , 9bpp, big-endian
            AV_PIX_FMT_GRAY9LE,   ///<        Y        , 9bpp, little-endian
    
            AV_PIX_FMT_GBRPF32BE,  ///< IEEE-754 single precision planar GBR 4:4:4,     96bpp, big-endian
            AV_PIX_FMT_GBRPF32LE,  ///< IEEE-754 single precision planar GBR 4:4:4,     96bpp, little-endian
            AV_PIX_FMT_GBRAPF32BE, ///< IEEE-754 single precision planar GBRA 4:4:4:4, 128bpp, big-endian
            AV_PIX_FMT_GBRAPF32LE, ///< IEEE-754 single precision planar GBRA 4:4:4:4, 128bpp, little-endian
    
            /**
             * DRM-managed buffers exposed through PRIME buffer sharing.
             *
             * data[0] points to an AVDRMFrameDescriptor.
             */
            AV_PIX_FMT_DRM_PRIME,
            /**
             * Hardware surfaces for OpenCL.
             *
             * data[i] contain 2D image objects (typed in C as cl_mem, used
             * in OpenCL as image2d_t) for each plane of the surface.
             */
            AV_PIX_FMT_OPENCL,
    
            AV_PIX_FMT_GRAY14BE,   ///<        Y        , 14bpp, big-endian
            AV_PIX_FMT_GRAY14LE,   ///<        Y        , 14bpp, little-endian
    
            AV_PIX_FMT_GRAYF32BE,  ///< IEEE-754 single precision Y, 32bpp, big-endian
            AV_PIX_FMT_GRAYF32LE,  ///< IEEE-754 single precision Y, 32bpp, little-endian
    
            AV_PIX_FMT_YUVA422P12BE, ///< planar YUV 4:2:2,24bpp, (1 Cr & Cb sample per 2x1 Y samples), 12b alpha, big-endian
            AV_PIX_FMT_YUVA422P12LE, ///< planar YUV 4:2:2,24bpp, (1 Cr & Cb sample per 2x1 Y samples), 12b alpha, little-endian
            AV_PIX_FMT_YUVA444P12BE, ///< planar YUV 4:4:4,36bpp, (1 Cr & Cb sample per 1x1 Y samples), 12b alpha, big-endian
            AV_PIX_FMT_YUVA444P12LE, ///< planar YUV 4:4:4,36bpp, (1 Cr & Cb sample per 1x1 Y samples), 12b alpha, little-endian
    
            AV_PIX_FMT_NV24,      ///< planar YUV 4:4:4, 24bpp, 1 plane for Y and 1 plane for the UV components, which are interleaved (first byte U and the following byte V)
            AV_PIX_FMT_NV42,      ///< as above, but U and V bytes are swapped
    
            AV_PIX_FMT_NB         ///< number of pixel formats, DO NOT USE THIS if you want to link with shared libav* because the number of formats might differ between versions
        }
    
        ///  /// ffmpeg中AVFrame結構體的前半部分,因為它太長了我不需要完全移植過來 /// 
        [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 408)]
        public struct AVFrame
        {
            //#define AV_NUM_DATA_POINTERS 8
            //        uint8_t* data[AV_NUM_DATA_POINTERS];
            public IntPtr data1;// 一般是y分量
            public IntPtr data2;// 一般是v分量
            public IntPtr data3;// 一般是u分量
            public IntPtr data4;// 一般是surface(dxva2硬解時)
            public IntPtr data5;
            public IntPtr data6;
            public IntPtr data7;
            public IntPtr data8;
            public int linesize1;// y分量每行長度(stride)
            public int linesize2;// v分量每行長度(stride)
            public int linesize3;// u分量每行長度(stride)
            public int linesize4;
            public int linesize5;
            public int linesize6;
            public int linesize7;
            public int linesize8;
            //uint8_t **extended_data;
            IntPtr extended_data;
            public int width;
            public int height;
            public int nb_samples;
            public AVPixelFormat format;
        }
    
        [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 128)]
        public struct AVCodec { }
    
        [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 72)]
        public unsafe struct AVPacket
        {
            fixed byte frontUnused[24]; // 前部無關數據
            public void* data;
            public int size;
        }
    
        [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 12)]
        public struct AVBufferRef { }
    
        [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 904)]
        public unsafe struct AVCodecContext
        {
            fixed byte frontUnused[880]; // 前部無關數據
            public AVBufferRef* hw_frames_ctx;
        }
    
        [StructLayout(LayoutKind.Sequential)]
        public struct AVDictionary { }
    
        public unsafe static class FFHelper
        {
            const string avcodec = "avcodec-58";
            const string avutil = "avutil-56";
            const CallingConvention callingConvention = CallingConvention.Cdecl;
    
            [DllImport(avcodec, CallingConvention = callingConvention)]
            public extern static void avcodec_register_all();
    
            [DllImport(avcodec, CallingConvention = callingConvention)]
            public extern static AVCodec* avcodec_find_decoder(AVCodecID id);
    
            [DllImport(avcodec, CallingConvention = callingConvention)]
            public extern static AVPacket* av_packet_alloc();
    
            [DllImport(avcodec, CallingConvention = callingConvention)]
            public extern static void av_init_packet(AVPacket* pkt);
    
            //[DllImport(avcodec, CallingConvention = callingConvention)]
            //public extern static void av_packet_unref(AVPacket* pkt);
    
            [DllImport(avcodec, CallingConvention = callingConvention)]
            public extern static void av_packet_free(AVPacket** pkt);
    
            [DllImport(avcodec, CallingConvention = callingConvention)]
            public extern static AVCodecContext* avcodec_alloc_context3(AVCodec* codec);
    
            [DllImport(avcodec, CallingConvention = callingConvention)]
            public extern static int avcodec_open2(AVCodecContext* avctx, AVCodec* codec, AVDictionary** options);
    
            //[DllImport(avcodec, CallingConvention = callingConvention)]
            //public extern static int avcodec_decode_video2(IntPtr avctx, IntPtr picture, ref int got_picture_ptr, IntPtr avpkt);
    
            [DllImport(avcodec, CallingConvention = callingConvention)]
            public extern static void avcodec_free_context(AVCodecContext** avctx);
    
            [DllImport(avcodec, CallingConvention = callingConvention)]
            public extern static int avcodec_send_packet(AVCodecContext* avctx, AVPacket* pkt);
    
            [DllImport(avcodec, CallingConvention = callingConvention)]
            public extern static int avcodec_receive_frame(AVCodecContext* avctx, AVFrame* frame);
    
    
    
    
            [DllImport(avutil, CallingConvention = callingConvention)]
            public extern static int av_hwdevice_ctx_create(AVBufferRef** device_ctx, AVHWDeviceType type, string device, AVDictionary* opts, int flags);
    
            [DllImport(avutil, CallingConvention = callingConvention)]
            public extern static AVBufferRef* av_buffer_ref(AVBufferRef* buf);
    
            [DllImport(avutil, CallingConvention = callingConvention)]
            public extern static void av_buffer_unref(AVBufferRef** buf);
    
            [DllImport(avutil, CallingConvention = callingConvention)]
            public extern static AVFrame* av_frame_alloc();
    
            [DllImport(avutil, CallingConvention = callingConvention)]
            public extern static void av_frame_free(AVFrame** frame);
    
            [DllImport(avutil, CallingConvention = callingConvention)]
            public extern static void av_log_set_level(int level);
    
            [DllImport(avutil, CallingConvention = callingConvention)]
            public extern static int av_dict_set_int(AVDictionary** pm, string key, long value, int flags);
    
            [DllImport(avutil, CallingConvention = callingConvention)]
            public extern static void av_dict_free(AVDictionary** m);
        }
    }
    

    上文中主要有幾個地方是知識點,大家做c#的如果需要和底層交互可以了解一下

    • 結構體的使用
        結構體在c#與c/c++基本一致,都是內存連續變量的一種組合方式。與c/c++相同,在c#中,如果我們不知道(或者可以規避,因為結構體可能很複雜,很多無關字段)結構體細節只知道結構體整體大小時,我們可以用Pack=1,SizeConst=來表示一個大小已知的結構體。
    • 指針的使用
        c#中,有兩種存儲內存地址(指針)的方式,一是使用interop體系中的IntPtr類型(大家可以將其想象成void*),一是在不安全的上下文(unsafe)中使用結構體類型指針(此處不討論c++類指針)
    • unsafe和fixed使用
        簡單來說,有了unsafe你才能用指針;而有了fixed你才能確保指針指向位置不被GC壓縮。我們使用fixed達到的效果就是顯式跳過了結構體中前部無關數據(參考上文中AVCodecContext等結構體定義),後文中我們還會使用fixed。

      現在我們開始編寫解碼和播放部分(即我們的具體應用)代碼

    FFPlayer.cs


    using Microsoft.DirectX;
    using Microsoft.DirectX.Direct3D;
    using System;
    using System.Drawing;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Windows.Forms;
    using static MultiPlayer.FFHelper;

    namespace MultiPlayer
    {
    public unsafe partial class FFPlayer : UserControl
    {
    [DllImport("msvcrt", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
    static extern void memcpy(IntPtr dest, IntPtr src, int count); // 用於在解碼器和directx間拷貝內存的c函數

        private IntPtr contentPanelHandle;                              // 畫面渲染的控件句柄,因為畫面渲染時可能出於非UI線程,因此先保存句柄避免CLR報錯
    
        private int lastIWidth, lastIHeight;                            // 上次控件大小,用於在控件大小改變時做出判定重新初始化渲染上下文
        private Rectangle lastCBounds;                                  // 臨時變量,存儲上次控件區域(屏幕坐標)
        private Rectangle lastVRect;                                    // 臨時變量,存儲上次解碼出的圖像大小
        private Device device;                                          // 當使用軟解時,這個變量生效,它是IDirect3Device9*對象,用於繪製YUV
        private Surface surface;                                        // 當使用軟解時,這個變量生效,它是IDirect3Surface9*對象,用於接受解碼后的YUV數據
        AVPixelFormat lastFmt;                                          // 上次解碼出的圖像數據類型,這個理論上不會變
    
        AVCodec* codec;                                                 // ffmpeg的解碼器
        AVCodecContext* ctx;                                            // ffmpeg的解碼上下文
        AVBufferRef* hw_ctx;                                            // ffmpeg的解碼器硬件加速上下文,作為ctx的擴展存在
        AVPacket* avpkt;                                                // ffmpeg的數據包,用於封送待解碼數據
        IntPtr nalData;                                                 // 一塊預分配內存,作為avpkt中真正存儲數據的內存地址
        AVFrame* frame;                                                 // ffmpeg的已解碼幀,用於回傳解碼后的圖像
    
        private volatile bool _released = false;                        // 資源釋放標識,與鎖配合使用避免重複釋放資源(由於底層是c/c++,多線程下double free會導致程序崩潰)
        private object _codecLocker = new object();                     // 鎖,用於多線程下的互斥
    
        static FFPlayer()
        {
            avcodec_register_all();                                     // 靜態塊中註冊ffmpeg解碼器
        }
    
        public FFPlayer()
        {
            InitializeComponent();
    
            // 過程中,下列對象只需初始化一次
            frame = av_frame_alloc();
            avpkt = av_packet_alloc();
            av_init_packet(avpkt);
            nalData = Marshal.AllocHGlobal(1024 * 1024);
            codec = avcodec_find_decoder(AVCodecID.AV_CODEC_ID_H264);
            avpkt->data = (void*)nalData;
        }
    
        ~FFPlayer()
        {
            // 過程中,下列對象只需釋放一次
            if (null != frame)
                fixed (AVFrame** LPframe = &frame)
                    av_frame_free(LPframe);
            if (null != avpkt)
                fixed (AVPacket** LPpkt = &avpkt)
                    av_packet_free(LPpkt);
            if (default != nalData)
                Marshal.FreeHGlobal(nalData);
        }
    
        // 釋放資源
        // 此函數並非表示“終止”,更多的是表示“改變”和“重置”,實際上對此函數的調用更多的是發生在界面大小發生變化時和網絡掉包導致硬解異常時
        private void Releases()
        {
            // 過程中,下列對象會重複創建和銷毀多次
            lock (_codecLocker)
            {
                if (_released) return;
                if (null != ctx)
                    fixed (AVCodecContext** LPctx = &ctx)
                        avcodec_free_context(LPctx);
                if (null != hw_ctx)
                    fixed (AVBufferRef** LPhw_ctx = &hw_ctx)
                        av_buffer_unref(LPhw_ctx);
                // (PS:device和surface我們將其置為null,讓GC幫我們調用Finalize,它則會自行釋放資源)
                surface = null;
                device = null;
                lastFmt = AVPixelFormat.AV_PIX_FMT_NONE;
                _released = true;
            }
        }
    
        // Load事件中保存控件句柄
        private void FFPlayer_Load(object sender, EventArgs e)
        {
            contentPanelHandle = Handle; // 這個句柄也可以是你控件內真正要渲染畫面的句柄
            lastCBounds = ClientRectangle; // 同理,區域也不一定是自身显示區域
        }
    
        // 解碼函數,由外部調用,送一一個分片好的nal
        public void H264Received(byte[] nal)
        {
            lock (_codecLocker)
            {
                // 判斷界面大小更改了,先重置一波
                // (因為DirectX中界面大小改變是一件大事,沒得法繞過,只能推倒從來)
                // 如果你的显示控件不是當前控件本身,此處需要做修改
                if (!ClientRectangle.Equals(lastCBounds))
                {
                    lastCBounds = ClientRectangle;
                    Releases();
                }
    
                if (null == ctx)
                {
                    // 第一次接收到待解碼數據時初始化一個解碼器上下文
                    ctx = avcodec_alloc_context3(codec);
                    if (null == ctx)
                    {
                        return;
                    }
                    // 通過參數傳遞控件句柄給硬件加速上下文
                    AVDictionary* dic;
                    av_dict_set_int(&dic, "hWnd", contentPanelHandle.ToInt64(), 0);
                    fixed (AVBufferRef** LPhw_ctx = &hw_ctx)
                    {
                        if (av_hwdevice_ctx_create(LPhw_ctx, AVHWDeviceType.AV_HWDEVICE_TYPE_DXVA2,
                                                        null, dic, 0) >= 0)
                        {
                            ctx->hw_frames_ctx = av_buffer_ref(hw_ctx);
                        }
                    }
                    av_dict_free(&dic);
                    ctx->hw_frames_ctx = av_buffer_ref(hw_ctx);
                    if (avcodec_open2(ctx, codec, null) < 0)
                    {
                        fixed (AVCodecContext** LPctx = &ctx)
                            avcodec_free_context(LPctx);
                        fixed (AVBufferRef** LPhw_ctx = &hw_ctx)
                            av_buffer_unref(LPhw_ctx);
                        return;
                    }
                }
                _released = false;
    
                // 開始解碼
                Marshal.Copy(nal, 0, nalData, nal.Length);
                avpkt->size = nal.Length;
                if (avcodec_send_packet(ctx, avpkt) < 0)
                {
                    Releases(); return; // 如果程序走到了這裏,一般是因為網絡掉包導致nal數據不連續,沒辦法, 推倒從來
                }
            receive_frame:
                int err = avcodec_receive_frame(ctx, frame);
                if (err == -11) return; // EAGAIN
                if (err < 0)
                {
                    Releases(); return; // 同上,一般這裏很少出錯,但一旦發生,只能推倒從來
                }
    
                // 嘗試播放一幀畫面
                AVFrame s_frame = *frame;
                // 這裏由於我無論如何都要加速,而一般顯卡最兼容的是yv12格式,因此我只對dxva2和420p做了處理,如果你的h264解出來不是這些,我建議轉成rgb(那你就需要編譯和使用swscale模塊了)
                if (s_frame.format != AVPixelFormat.AV_PIX_FMT_DXVA2_VLD && s_frame.format != AVPixelFormat.AV_PIX_FMT_YUV420P && s_frame.format != AVPixelFormat.AV_PIX_FMT_YUVJ420P) return;
                try
                {
                    int width = s_frame.width;
                    int height = s_frame.height;
                    if (lastIWidth != width || lastIHeight != height || lastFmt != s_frame.format) // 這個if判定的是第一次嘗試渲染,因為一般碼流的寬高和格式不會變
                    {
                        if (s_frame.format != AVPixelFormat.AV_PIX_FMT_DXVA2_VLD)
                        {
                            // 假如硬解不成功(例如h264是baseline的,ffmpeg新版不支持baseline的dxva2硬解)
                            // 我們就嘗試用directx渲染yuv,至少省去yuv轉rgb,可以略微節省一丟丟cpu
                            PresentParameters pp = new PresentParameters();
                            pp.Windowed = true;
                            pp.SwapEffect = SwapEffect.Discard;
                            pp.BackBufferCount = 0;
                            pp.DeviceWindowHandle = contentPanelHandle;
                            pp.BackBufferFormat = Manager.Adapters.Default.CurrentDisplayMode.Format;
                            pp.EnableAutoDepthStencil = false;
                            pp.PresentFlag = PresentFlag.Video;
                            pp.FullScreenRefreshRateInHz = 0;//D3DPRESENT_RATE_DEFAULT
                            pp.PresentationInterval = 0;//D3DPRESENT_INTERVAL_DEFAULT
                            Caps caps = Manager.GetDeviceCaps(Manager.Adapters.Default.Adapter, DeviceType.Hardware);
                            CreateFlags behaviorFlas = CreateFlags.MultiThreaded | CreateFlags.FpuPreserve;
                            if (caps.DeviceCaps.SupportsHardwareTransformAndLight)
                            {
                                behaviorFlas |= CreateFlags.HardwareVertexProcessing;
                            }
                            else
                            {
                                behaviorFlas |= CreateFlags.SoftwareVertexProcessing;
                            }
                            device = new Device(Manager.Adapters.Default.Adapter, DeviceType.Hardware, contentPanelHandle, behaviorFlas, pp);
                            //(Format)842094158;//nv12
                            surface = device.CreateOffscreenPlainSurface(width, height, (Format)842094169, Pool.Default);//yv12,顯卡兼容性最好的格式
                        }
                        lastIWidth = width;
                        lastIHeight = height;
                        lastVRect = new Rectangle(0, 0, lastIWidth, lastIHeight);
                        lastFmt = s_frame.format;
                    }
                    if (lastFmt != AVPixelFormat.AV_PIX_FMT_DXVA2_VLD)
                    {
                        // 如果硬解失敗,我們還需要把yuv拷貝到surface
                        //ffmpeg沒有yv12,只有i420,而一般顯卡又支持的是yv12,因此下文中uv分量是反向的
                        int stride;
                        var gs = surface.LockRectangle(LockFlags.DoNotWait, out stride);
                        if (gs == null) return;
                        for (int i = 0; i < lastIHeight; i++)
                        {
                            memcpy(gs.InternalData + i * stride, s_frame.data1 + i * s_frame.linesize1, lastIWidth);
                        }
                        for (int i = 0; i < lastIHeight / 2; i++)
                        {
                            memcpy(gs.InternalData + stride * lastIHeight + i * stride / 2, s_frame.data3 + i * s_frame.linesize3, lastIWidth / 2);
                        }
                        for (int i = 0; i < lastIHeight / 2; i++)
                        {
                            memcpy(gs.InternalData + stride * lastIHeight + stride * lastIHeight / 4 + i * stride / 2, s_frame.data2 + i * s_frame.linesize2, lastIWidth / 2);
                        }
                        surface.UnlockRectangle();
                    }
    
                    // 下面的代碼開始燒腦了,如果是dxva2硬解出來的圖像數據,則圖像數據本身就是一個surface,並且它就綁定了device
                    // 因此我們可以直接用它,如果是x264軟解出來的yuv,則我們需要用上文創建的device和surface搞事情
                    Surface _surface = lastFmt == AVPixelFormat.AV_PIX_FMT_DXVA2_VLD ? new Surface(s_frame.data4) : surface;
                    if (lastFmt == AVPixelFormat.AV_PIX_FMT_DXVA2_VLD)
                        GC.SuppressFinalize(_surface);// 這一句代碼是點睛之筆,如果不加,程序一會兒就崩潰了,熟悉GC和DX的童鞋估計一下就能看出門道;整篇代碼,就這句折騰了我好幾天,其他都好說
                    Device _device = lastFmt == AVPixelFormat.AV_PIX_FMT_DXVA2_VLD ? _surface.Device : device;
                    _device.Clear(ClearFlags.Target, Color.Black, 1, 0);
                    _device.BeginScene();
                    Surface backBuffer = _device.GetBackBuffer(0, 0, BackBufferType.Mono);
                    _device.StretchRectangle(_surface, lastVRect, backBuffer, lastCBounds, TextureFilter.Linear);
                    _device.EndScene();
                    _device.Present();
                    backBuffer.Dispose();
                }
                catch (DirectXException ex)
                {
                    StringBuilder msg = new StringBuilder();
                    msg.Append("*************************************** \n");
                    msg.AppendFormat(" 異常發生時間: {0} \n", DateTime.Now);
                    msg.AppendFormat(" 導致當前異常的 Exception 實例: {0} \n", ex.InnerException);
                    msg.AppendFormat(" 導致異常的應用程序或對象的名稱: {0} \n", ex.Source);
                    msg.AppendFormat(" 引發異常的方法: {0} \n", ex.TargetSite);
                    msg.AppendFormat(" 異常堆棧信息: {0} \n", ex.StackTrace);
                    msg.AppendFormat(" 異常消息: {0} \n", ex.Message);
                    msg.Append("***************************************");
                    Console.WriteLine(msg);
                    Releases();
                    return;
                }
                goto receive_frame; // 嘗試解出第二幅畫面(實際上不行,因為我們約定了單次傳入nal是一個,當然,代碼是可以改的)
            }
        }
        
        // 外部調用停止解碼以显示釋放資源
        public void Stop()
        {
            Releases();
        }
    }

    }

    下面講解代碼最主要的三個部分

    • 初始化ffmpeg
        主要在靜態塊和構造函數中,過程中我沒有將AVPacket和AVFrame局部化,很多網上的代碼包括官方代碼都是局部化這兩個對象。我對此持保留意見(等我程序報錯了再說)
    • 將收到的數據送入ffmpeg解碼並將拿到的數據進行展示
        這裏值得一提的是get_format,官方有一個示例,下圖

    它有一個get_format過程(詳見215行和63行),我沒有採用。這裏給大家解釋一下原因:

    這個get_format的作用是ffmpeg給你提供了多個解碼器讓你來選一個,而且它內部有一個機制,如果你第一次選的解碼器不生效(初始化錯誤等),它會調用get_format第二次(第三次。。。)讓你再選一個,而我們首先認定了要用dxva2的硬件解碼器,其次,如果dxva2初始化錯誤,ffmpeg內部會自動降級為內置264軟解,因此我們無需多此一舉。

    • 發現解碼和播放過程中出現異常的解決辦法
      • 不支持硬解
        代碼中已經做出了一部分兼容,因為baseline的判定必須解出sps/pps才能知道,因此這個錯誤可能會延遲爆出(不過不用擔心,如果此時報錯,ffmpeg會自動降級為軟解)
      • 窗體大小改變
        基於DirectX中設備後台緩衝的寬高無法動態重設,我們只能在控件大小改變時推倒重來。如若不然,你繪製的畫面會進行意向不到的縮放
      • 網絡掉包導致硬件解碼器錯誤
        見代碼
      • 其他directx底層異常
        代碼中我加了一個try-catch,捕獲的異常類型是DirectXException,在c/c++中,我們一般是調用完函數後會得到一個HRESULT,並通過FAILED宏判定他,而這個步驟在c#自動幫我們做了,取而代之的是一個throw DirectXException過程,我們通過try-catch進行可能的異常處理(實際上還是推倒重來)

      番外篇:C#對DiretX調用的封裝
    上文中我們使用DirectX的方式看起來即非COM組件,又非C-DLL的P/Invoke,難道DirectX真有託管代碼?
    答案是否定的,C#的dll當然也是調用系統的d3d9.dll。不過我們有必要一探究竟,因為這裏面有一個隱藏副本

    首先請大家準備好ildasm和visual studio,我們打開visual studio,創建一個c++工程(類型隨意),然後新建一個cpp文件,然後填入下面的代碼

    如果你能執行,你會發現輸出是136(0x88);然後我們使用ildasm找到StrechRectangle的代碼

    你會發現也有一個+0x88的過程,那麼其實道理就很容易懂了,c#通過calli(CLR指令)可以執行內存call,而得益於微軟com組件的函數表偏移量約定,我們可以通過頭文件知道函數對於對象指針的偏移(其實就是一個簡單的ThisCall)。具體細節大家查閱d3d9.h和calli的網絡文章即可。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

  • 心裏有紅黑樹

    心裏有紅黑樹

    Why 紅黑樹

    為什麼大家都這麼推崇紅黑樹呢? 這就是數據結構的魅力!!! 下面我簡述一下常用數據結構的優缺點

    • 數組

    大家對數組很熟悉, 都知道對數組來說,它底層的存儲空間是連續的,因此如果我們根據index去獲取元素,速度是相當快, 但是對於數組來說有時候查詢也不見得就一定塊, 比如我們查詢數組中名字叫張三的人, 也不得不從開始遍歷這個數組

    如果我們想往數組中插入一個元素, 也不見得一定就慢, 比如我們往數組中最後的位置插入就很快, 但是要是往開始的位置插入的話, 肯定會很慢, 需要將現有數組中所有的元素往後移動一位, 才能空出開始的位置,給新元素用

    • 鏈表

    一說鏈式存儲, 大家也都知道, 這種數據結構僅僅是邏輯連續, 物理存儲不連續, 因此我們有可以通過玩指針或者引用很快的完成元素的刪除和添加

    對鏈表的查詢來說, 一定是慢的, 無論查詢誰, 查哪個, 都得從第一個節點開始遍歷

    • AVL樹

    AVL樹, 就是二叉平衡樹, 這種有序的樹形結果就將鏈式存儲添加刪除塊, 順序存儲的查找快兩大有點進行了一次中和, 在絕大部分情況下, AVL樹在增刪改查方面的性能都原超過數組和鏈表

    • 紅黑樹

    紅黑樹是對AVL樹是又一次重大升級, AVL樹,對於樹的平衡要求太嚴格了, 每當添加,刪除節點時,都不得不進行調整

    對於AVL樹個紅黑樹來說, 每次添加一個新的節點都是最多進行兩次旋轉(左旋右旋)就能重新使樹變的平衡,

    但是當我們刪除一個恭弘=叶 恭弘子節點時, AVL樹重新調整成平衡狀態時最多需要進行旋轉O(logN)次, 而紅黑樹最多旋轉3次就能重新平衡,時間複雜度是O(1)

    還有就是紅黑樹並不是完全意義上的AVL樹, 也就是說它其實並不是真的像AVL樹那樣嚴格要求對一個節點來說左右子樹的高度差不能超過1, 而是選擇使用染成紅色和黑色進行維護

    簡單來說, 因為紅黑樹並不像AVL樹那樣完全平衡, 可能會導致紅黑樹的讀性能略遜於AVL, 但是紅黑樹的維護成本絕對是遠遠低於AVL, 在空間上的開銷和AVL樹基本持平, 因此紅黑樹被大家極力推崇, 和學習java的同學直接相關的就是jdk8的 hashmap

    紅黑樹的特性

    紅黑樹主要存在下面的7條性質

    1. 節點非紅即黑
    2. 根節點必定是黑色
    3. 恭弘=叶 恭弘子節點全部是黑色, (這裏說的恭弘=叶 恭弘子節點是我們想象在肉眼看到的節點上再多加一層子節點)
    4. 紅節點的子節點必定是黑色
    5. 紅節點的父節點必定是黑色
    6. 從根節點到任意子節點的路徑上,都要經歷相同數目的黑節點
    7. 從根節點到任意子節點的路徑上不可能存在兩個連續相同的紅節點

    常見的誤區

    如上圖, 看着挺像紅黑樹, 其實他不是, 看它node10, 並不滿足上面的性質6. 因為我們認為node10的左子節點是黑色的節點, 這樣的話, 從node20到node10的左子節點就經歷了兩個黑節點, 而其他的 node15, node25, node35 經歷的黑色子節點數都是三個

    如上圖它也不是紅黑樹, 因為我們認為node30的右節點是黑色的節點, 這樣的話從node60到node30的右節點就經歷了三個黑色的節點, 而其他的所有子節點都經歷了4個, 故, 他不是紅黑樹

    紅黑樹與2-3-4樹等價

    如上圖中,當我們將一個紅黑樹中的黑色節點和紅色節點融合在一起時,我們會發現, 這個紅黑樹其實就是一顆2-3-4樹, 一顆四階B樹

    並且, 紅黑樹中黑色節點的每一個合併完成后的節點中都有一個黑色的節點, 換句話說就是紅黑樹中黑色節點的個數等於2-3-4樹中節點的個數

    添加

    添加節點其實就是構造紅黑樹的過程, 只要我們嚴格遵循上面的7條限制, 構造出來的樹就是紅黑樹

    通過上圖其實我們發現, 紅黑樹真的可以和四階B樹之間進行等價代換, 換句話說就是 4階B樹的性質對於紅黑樹來書其實也是存在的, 主要是如下兩條性質

    • 所有新添加進去的節點都被放在了恭弘=叶 恭弘子節點上
    • 2-3-4樹中每一個節點中允許承載的元素的個數 [1,3]

    經驗推薦: 就是新添加的節點盡量全部是紅色, 如果你畫一畫就會發現, 如果我們新添加的節點是紅色的話,上面所說的7條性質中, 除了第四條(紅節點的子節點必定的黑節點). 其他的限制都可以滿足

    於是看一下一顆四階B樹插入節點時有哪些種情況

    數一數: 一共 4+3+3+2 = 12種情況, 換句話說, 只要我們處理好了這12種情況, 我們就完成了添加節點的邏輯

    • 情況1, 就是假設我們添加進去的是紅色的節點, 並且這個紅色節點的父節點是黑色節點時, 直接添加進行,不需要其他任何變換, 就想下圖這樣, 直接簡單粗暴的添加就行

    除去第一種情況外, 還剩下8中情況出現了紅紅節點相鄰, 於是繼續往下看, 我們對他進行一次修復

    • 情況2: 如下圖

    插入的node57, node64, 什麼情況呢? 就是當前節點是node5556, 首先這個節點中現存兩個元素, 並且是往這個黑色的節點的左側的左側插入, 或者是右側的右側插入一個紅色節點

    看上圖出現了兩個紅色節點相鄰,於是我們第一件事就是進行重新染色,

    1. 將插入節點的父節點染成黑色
    2. 將插入節點的祖父節點染成紅色
    3. 將祖父節點進行旋轉, 如果這個新節點被插入在父節點的右側. 左旋轉它的祖父節點

    經過上面的變換后, 我們重新得到標準的紅黑樹如下

    • 情況3: 新添加的節點的叔叔節點不是紅色

    第三種情況和第二種情況相似, 還是插入 node57和node64. 判斷的條件是 插入節點的叔叔節點(父節點的兄弟節點)不是紅節點,

    簡稱 LR , 或者是RL , 需要進行如下的調整

    1. 染色: 將自己染成黑色,祖節點染成紅色
    2. LR: 父節點左旋轉, 祖父節點右旋轉
    3. RL: 祖父點右旋轉, 父節點左旋轉

    LR舉例:

    經過上面的變化,我們重新得到平衡的紅黑樹

    接着往下看剩下的四種情況

    • 情況4: 新添加的節點的叔叔節點是紅色, 其實就是需要上溢的情況, 也很好處理

    像上圖這樣, 新添加的紅色節點 node15, 它本身的父節點是node20, 父節點的叔叔節點是紅色的node25, 我們比較node15和node20的大小, 發現node15本來是應該放在node20的左邊的, 但是對於一顆2-3-4樹來說, 單個節點最多就有3個元素, 如果再加上node15 就會出現上溢的情況, 怎麼辦呢? 我們上溢調整, 選擇這個節點中間位置的元素向上和父節點合併, 選擇node20, node30其實都是可以的, 為了方便我們選擇node30

    好,下面開始修復這個紅黑樹

    1. 將插入的節點的父節點和它的叔叔節點染成黑色
    2. 發生了上溢, 將他的父節點的染成紅色, 遞歸插入到根節點上, 這時候根節點可能又會發生上溢

    然後上溢

    當我們將新插入的節點的父節點node30染成紅色時, 再插入到根節點, 實際上就是重複我們枚舉出來的這12種情況中的一種. 紅黑樹一定會被修復, 當然這時候很可能會出現根節點也容納不了新的元素, 需要根節點也進行上溢, 然後將根節點染黑

    還有一種情況是像下面這樣, 同樣是在情況4下的新插入的節點的叔叔節點是紅色

    像下面這樣調整:

    1. 將父節點和叔叔節點染成黑色
    2. 祖父節點上溢

    然後就是這種情況

    調整的思路和前面一樣

    1. 將父節點和叔叔節點染成黑色
    2. 將祖父節點上溢

    至此紅黑樹的添加的12種情況就全部枚舉完成了

    刪除

    對於刪除來說總共兩大種四小種情況

    • 第一種就是刪除的節點就是紅色節點, 如果真是這樣的話,直接刪除就ok
    • 第二種是刪除的節點是黑色節點
      • 刪除擁有1個red節點的黑色節點
      • 刪除擁有2個red節點的黑色節點,
      • 刪除黑色節點

    如果一個像下面這樣, 下面的黑色節點有兩個子節點, 這種情況下,黑色節點肯定不會直接被刪除的, 需要進行變換,讓他的恭弘=叶 恭弘子節點去替換他,進而實現刪除的目的

    • 情況1: 刪除擁有1個紅節點的黑色節點,像下圖這樣

    怎麼判斷這就是我們想刪除的情況呢? 當我們確定用來替代這個被刪除的黑節點是紅色,則符合當前的情況

    也就是說我們想刪除 node40 和 node70, 於是我們這樣做

    1. 讓這個指向被刪除的節點的指針指向這個被刪除的節點的子節點
    2. 將替代它的節點染成黑色

    於是我們接得到下圖這樣的結果

    • 情況2: 刪除的節點是黑色的恭弘=叶 恭弘子節點, 並且可向兄弟節點借

    首先,如果這個恭弘=叶 恭弘子節點就是根節點的話,直接刪除就ok

    看下面的這個圖, 我們就刪除其中node90, 即,刪除黑色恭弘=叶 恭弘子節點

    如果想刪除上圖中的node90也是由竅門的,規律和2-3-4樹是擦不多的

    假設它就是2-3-4樹, 如果我們將node90刪了, 我們計算一下, 對於2-3-4樹來說, 每一個節點位置上至少有 ⌈ 4/2 ⌉ -1 = 1個元素, 但是把node90刪除了這個位置上的節點中沒有元素, 因此產生了 下溢

    出現下溢,我們首先考慮的情況就是看看可不可以向它的兄弟節點借一個,但是和B樹是有取別的, 多了下面的限制

    1. 被刪除的這個節點的兄弟節點必須是黑色的
    2. 被刪除的這個節點的兄弟節點一定的有紅色的子節點才ok, 就像上圖那樣, 可以在左邊,右邊,或者都有
    3. 直接刪除掉指定的node(因為它在恭弘=叶 恭弘子節點的位置上)
    4. 進行旋轉,旋轉時注意, 兩點:第一點: 比如下面的原來根節點位置上的元素88是紅色的, 經過旋轉上來替換它的節點的顏色必須染成紅色, 如果node88是黑色, 那麼經過旋轉上來替換他的節點的顏色必須染成黑色 ,第二點: 旋轉完成后,新的跟節點的直接左右子節點的顏色轉換為黑色

    怎麼進行旋轉呢? 就像下圖這樣

    • 情況3: 刪除的節點是黑色的恭弘=叶 恭弘子節點, 並且它的兄弟是黑色,而且它的兄弟節點不能借給他元素

    像這種情況:我像刪除node99,但是沒辦法像他的兄弟節點借元素,於是

    1. 將父節點向下合併,父節點染成黑色
    2. 將它的兄弟節點染成紅色

    也有特殊的情況, 就是它的父節點只有一個,還是黑色

    這時候,我們將他的父節點下溢, 原位置的節點捨棄

    • 還有最後一種情況就是, 刪除的是黑色的節點, 它的兄弟節點的是紅色的節點

    就像上圖那樣,我們想刪除node99, 但是node99的兄弟節點其實是node55, 而不是node77, 我們怎麼樣才能轉換為前面說的那些情況呢?

    1. 將被刪除節點的父節點染成紅色, 兄弟節點染黑

    2. 讓被刪除的父節點進行右旋轉(node88右轉)

      得到下圖

    於是我們就將這種兄弟節點為紅節點的情況轉化成了兄弟節點為黑色節點的樣子, 按照原來的方式進行刪除修整即可

    1. 讓原父節點下溢
    2. 原染成黑色
    3. 兄弟節點,染成紅色

    至此本文就結束, 歡迎關注我,後續我更新更多的關於開發相關的筆記

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 特斯拉正式打造出第一輛Model 3!馬斯克大秀美照

    特斯拉正式打造出第一輛Model 3!馬斯克大秀美照

    千呼萬喚始出來!特斯拉(Tesla Inc.)終於在上週六(7月8日)順利生產出第一輛平價電動車「Model 3」(見圖),執行長馬斯克(Elon Musk)透過Twitter發布這個訊息後(),還特地秀出兩張官方照片(、)。

    馬斯克在推文中表示,創投機構DBL Partners創辦人Ira Ehrenpreis原本已經下了訂金、買下第一輛Model 3,但Ehrenpreis決定把擁有第一輛Model 3的權利讓給馬斯克,作為他46歲的生日禮物。

    Model 3定價35,000美元,不少人將之視為特斯拉豪華電動車「Model S」的平價版。這款全新轎車體型嬌小,但同樣也會有自駕功能,預料每次充電的里程數將有215英里。

    特斯拉預定7月底生產30輛Model 3,12月會將月產能拉高至2萬輛,等於是一年生產24萬輛。至少已有38萬人支付1,000美元的訂金(可退款),但特斯拉從去年初就未曾更新過這項數據。第一批顧客只有兩種選擇:顏色和輪胎尺寸。

    過去一週對特斯拉來說並不好過,第2季交貨量不如預期,再加上富豪集團(Volvo)宣布2019年起所有車款都會是電動車、成為第一家這麼做的傳統車廠,導致特斯拉股價從兩週前的386.99美元歷史高一路大跌近20%。

    特斯拉才剛於7月3日公布,第2季的電動車交貨量僅略高於22,000台,不如前季的25,000台,主要是受到100 kWh電池組產能嚴重短缺的影響。特斯拉說,截至6月初為止,電池組的生產量平均比需求短少了40%。

    不過,特斯拉股價在連續大跌三個交易日後,上週五(7月7日)反彈1.42%、收313.22美元。路透社報導,特斯拉表示,第二季底大約有3,500輛電動車還在運送途中、尚未交貨給客戶,這些車可在Q3計入交貨量。

    (本文內容由授權使用。圖片出處:Elon Musk Twitter)  

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

  • 法國要在2040 年停售汽柴油車,能源部長:這會是一場革命

    法國要在2040 年停售汽柴油車,能源部長:這會是一場革命

    不同於美國退出巴黎協定的決定,法國總統馬克宏曾提到要「讓地球再次偉大」,如今看來,法國似乎正朝目標一步步邁進中。法國能源部長余洛(Nicolas Hulot)6 日宣布,將在2040 年開始全面停售汽柴油車。

    電訊報報導,除了汽柴油車之外,法國同時也將禁止任何使用汽油、煤氣、煤炭及頁岩油的新計畫,余洛表示,汽油及汽車的銷售將在2040 年前結束,「法國打算在2050 年達到碳中和的目標。」

    為了達成這個目標,法國將開始進行必要的投資,並提供相關獎勵補助措施,這個目標是非常困難的,特別是對於車商來說,但余洛相信汽車產業已經準備好進行轉變。

    根據歐洲經濟區(EEA)的統計,在2016 年大型車商的汽車碳排放量上,法國車廠Peugeot、Citroen、Renault 是碳排放最低的前三名。

    儘管如此,這樣的改變勢必會面臨許多挑戰,余洛表示,「這會是一場名副其實的革命。」

    余洛也引述了Volvo 近期的決定,來說明停售汽柴油車的目標已是勢在必行。

    就在幾天前,傳統車商Volvo 宣布將於2019 年起生產電動車,並停售純內燃機供電的汽車,執行長表示,Volvo 是考量過客戶需求,才做出這項決定。

    法國並不是唯一將開始禁止汽柴油車的國家,德國和印度都打算在2030 年前達到這個目標,荷蘭和挪威更計畫在2025 年前達成。

    根據統計,2017 上半年在法國的新車市場中,使用柴油和汽油的車輛仍佔有95.2%,混合動力車佔3.5%,純電動車僅佔了1.2%。

    余洛強調,儘管法國在這方面,落後於瑞典、哥斯大黎加等國家,但會擁抱綠能的「精神」,期望在2040 年前能達成目標。

    除了停售汽柴油車及停止汽油相關新項目外,法國還計畫在2022 年將火力發電廠停工,並在2025 年將核電的供電比例由現在的75% 降低至50%,同時將採取措施,限制棕櫚油在生物燃料中的使用,來間接減少砍伐森林。

    不僅是中國和印度,在法國的一些城市,巴黎、里昂和格勒諾布爾(Grenoble),也都存在著霧霾的問題。

    「解決問題的方法就在可見之處,國內的製造商有著實現這項承諾的手段,這關係到公共衛生。」

    (合作媒體:。圖片出處:wikipedia CC0)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 二代電動車電池問世!韓廠急追、想搶Panasonic市佔

    二代電動車電池問世!韓廠急追、想搶Panasonic市佔

    目前電動車電池的前三大廠分別是Panasonic、LG Chem、Samsung SDI,今年三家業者相繼推出第二代電池,將引爆新一波市場大戰。

    韓媒etnews 7日報導,特斯拉平價電動車「Model 3」將在本月發布,預料全數採用日廠Panasonic的二代圓柱狀電池—「Panasonic 21700」。據稱Panasonic二代電池容量為4,500mA。

    與此同時,外傳韓廠LG Chem和Samsung SDI的二代圓柱電池預定今年底量產,電池容量略高於Panasonic。報導稱,LG Chem新電池電力在4,700~4900mA之間,Samsung SDI則為4,500mA。

    二代電池性能較一代大為提升,倘若電動車原本需要1千組一代電池才能應付所需,到了二代電池只需700組就足夠。消息指出,新崛起的電動車廠Lucid Motors和Faraday Future將採二代電池,正與LG Chem和Samsung SDI商討供應事宜。另外,歐洲大型車廠也考慮不再分散訂單,韓廠有望搶下更多訂單。

    (本文內容由授權使用)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

  • 全球Q2電動汽車發展指數 中國首度躍居整體排名第一

    全球Q2電動汽車發展指數 中國首度躍居整體排名第一

    中證網報導,羅蘭貝格與德國汽車研究機構亞琛汽車工程技術公司共同發佈《2017年第二季全球電動汽車發展指數》。報告中顯示,中國大陸首次在電動汽車發展指數的整體排名中躍居全球第一,並直指儘管政府新能源政策收緊,補貼力道減弱,中國電動汽車和電池製造市場份額仍將保持強有力的增長,進一步擴大領先優勢。

    該報告對中國、德國、法國、義大利、美國、日本和韓國電動汽車的發展現狀進行比較。整體而言,中國首次躍居指數整體排名第一,美國與德國分居第二、三位,而在上一季指數排名中位列第一的日本則失去領先地位。報告預測,在可預見的未來,中國將統領電動汽車的行業與市場。

    在技術層面,法國超越德國,位居首位,主要由於有更多的德國整車廠大批量生產續航能力和最高電動時速都較低的插電式混合動力汽車,導致其電動汽車技術能力略有下降;日本排名第三,其整車廠的電動汽車技術水準較高且價格更加實惠;中國整車廠則仍主要定位於技術含量較低的領域。

    在行業總量層面,中國正在逐步擴大其領先優勢;在電池製造領域,中國的優勢也更加明顯;反觀日本在電動汽車產量和全球電池產量份額上都處於不利地位,排名維持在第三;美國行業成績有所提升,位居第二。至於在市場規模層面,中國的需求進一步急劇增長,但電動汽車所占市場份額仍略低於法國,排在第二,美國名列第三。

    資料顯示,2016年中國生產了超過35萬輛插電式混合動力和純電動乘用車,銷售額保持兩位數增長,市佔率從0.8%上升至1.3%;同年,德國、法國與美國電動汽車的註冊數量均實現了兩位數的增長。但整體而言,2016年僅有法國與中國兩個國家的純電動和插電式混合動力汽車市場份額超過1%。

    報告認為,中國電動汽車銷量的快速增長主要得益於政府大幅度補貼和主要城市對汽油車的限牌政策,但政府對於汽車廠商的政策正在收緊。對此,羅蘭貝格合夥人鄭贇表示,雖然大陸政府的激勵政策在初期對行業發展起到了重要的推動作用,但難以長久維持,政府需要控制成本,也有意讓本土廠商培育自身能力,電動汽車產業的發展將由政府推動向市場推動轉變,其最新版的新能源汽車雙積分管理意見徵求稿就明確地傳達了此訊號。

    根據羅蘭貝格的估算,要達到新能源汽車積分比例2020年12%的目標,該年電動汽車的總銷量需達到約160萬輛。鄭贇指出,汽車設計、配件整合以及供應商管理能力將成為大陸本土廠商所面臨的重大挑戰,想要在政府退補的情況下實現增長、完成積分目標,成本控制是關鍵;只有成本控制能力和價格競爭力的提升才能幫助其本土廠商在國際電動汽車市場上保持長期的競爭優勢。

    (本文內容由授權使用。圖片出處:pixabay CC0)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

  • 中國電池行業發展全球領先,亞太電池展海外佈局加速

    羅蘭貝格汽車行業中心與德國著名汽車研究機構亞琛汽車工程技術有限公司共同發佈的《2017年第一季度全球電動汽車發展指數》報告(以下簡稱“報告”),對全球七大主要汽車國家電動汽車競爭格局在技術、行業、市場三項指標上進行了詳細分析。

     

    就“行業”指標而言,中國已經確立了領先地位,原因在於中國市場持續快速增長,且超過90%的鋰電池都在本土生產。與中國相比,日本在電動汽車產量和全球電池生產份額這兩方面都處於不利地位,排名滑落至全球第三。美國則攀升至第二位。

     

    從全球電池生產份額來看,中國電池製造商已經處於領先地位。由此可見,在電池製造領域,中國的優勢日益明顯。許多國際企業負責人紛紛表示看好未來中國電池市場,希望借助展會這一平臺進入中國市場,在中國千億元級電池市場中“分一杯羹”。作為全球領先的電池採購交易平臺,亞太電池展吸引了大批國際企業前來參展,目前已有大量海外優質採購商分別通過網站後臺、郵件、社交平臺等進行了參觀登記。

     

     

    除此之外,主辦方還收到了德國汽車協會、美國能源協會、美國汽車零部件協會、巴基斯坦汽車零部件協會(54人觀展團)、印度觀展團(50余人觀展團)等重量級觀展團參觀申請。國際優質採購商的參與有利於幫助國內參展企業拓展海外消費市場,助推我國電池產業的可持續發展,做大做強國內品牌。相信在亞太電池展這一全球領先的電池與儲能行業採購交易平臺的支援下,中國電池與儲能行業國際化的步伐將繼續提速,在全球範圍內掀起“動力風暴”。

     

    作為世界級的動力電池與儲能行業交易盛會,亞太電池展自全面啟動以來,得到了國內外主流媒體的高度關注,多家行業媒體進行宣傳報導,迅速建立起亞太電池展在國際的品牌知名度、美譽度,有效對接目標客戶及潛在買家,也有效幫助國內參展商在全球範圍的品牌傳播。

    專業協會鼎力支持

     

    TURKEY ELECTRIC & HYBRID VEHICLES ASSOCIATION是國際能源機構(IEA)框架下的一個國際成員組織,致力於混合動力和燃料電池汽車的資訊傳播,通過banner、新聞等報導方式使亞太電池展的宣傳效果如虎添翼。

     

    有理由相信,亞太電池展很快將給業界帶來一場電池與儲能產業的航母級盛宴。作為立志於打造國際頂級電池行業採購易與技術交流平臺的亞太電池展,組委會將繼續增大海外買家邀約力度,後期將著重歐美、俄羅斯、中東、中亞以及東南亞等地區優質買家團的組織,以切實説明到國內企業拓展國際市場。

     

     

     

     

    想查詢更多展會訊息,請登陸大會官網:http://www.battery-expo.com/ 瞭解。

     

    預訂展位:+86-20-32373488

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

  • 上海新能源汽車展8月舉行 分時租賃成熱門話題

    共用經濟無疑是當前最為熱門的話題之一,共用單車、共用汽車、共用雨傘、共用充電寶等各種共用話題層出不窮。但是時下最最熱門的,莫過於共用汽車的話題了。

    據瞭解,由充電設施線上網、廣東省充電設施協會、廣東省新能源汽車產業協會、中國土木工程學會城市公共交通學會和振威展覽股份聯合舉辦的2017上海國際新能源汽車產業博覽會將於8月23-25日在上海新國際博覽中心舉行。本次展會邀請了國內200多家分時租賃運營商參會交流,在我國新能源汽車產業快速發展的背景下,本次展會的舉行對於推動新能源汽車分時租賃發展具有重要意義。

    分時租賃的發展不但對於推動新能源汽車的普及應用具有重要的作用,而且有助於緩解交通堵塞,以及公路的磨損,減少空氣污染,降低對能量的依賴性,發展前景極為廣闊。

    據交通運輸部最新統計,截至目前分時租賃企業40餘家,車輛總數超過4萬輛,95%以上為新能源車輛。其中77%的分時租賃車輛出自整車廠背景的分時租賃企業。市場普遍預測,未來5年汽車分時租賃市場將以超過50%的增幅發展,行業有望在2020年之前迎來突破性發展,保守估計在2020年中國整體車隊規模有望達到17萬輛以上,交易金額將從9億元增長到47億元。到2025年中國的分時租賃汽車數量將達到60萬輛。

    日前,由交通運輸部會同住房和城鄉建設部制定的《關於促進汽車租賃業健康發展的指導意見(徵求意見稿)》發佈。該意見稿釋放出的重要資訊是,國家層面開始鼓勵汽車分時租賃業態的發展。地方層面,廣州、深圳、上海、成都等地均發佈了有關於促進分時租賃產業發展的《指導意見》。這些指導意見無一例外都對發展規劃及扶持政策有了明確的指示。

    以成都市為例,為推動成都市分時租賃的發展,由交委牽頭起草的《成都市關於鼓勵和規範新能源汽車分時租賃業發展的指導意見(徵求意見稿)》中明確指出:到2018年底,基本形成新能源汽車分時租賃服務網路,服務網點達到2500個,充電樁達到10000個;到2020年底,形成覆蓋廣泛的新能源汽車分時租賃服務網路,服務網點達到5000個,充電樁達到20000個。扶持政策方面將按照《成都市進一步支持新能源汽車推廣應用的若干政策》等有關規定執行。

    由此可以看出,分時租賃的發展規劃能夠極大的促進新能源汽車推廣及充電基礎設施建設工作。而本次展會恰合時宜的融合了新能源整車生產與運營、核心三電、充電設備製造與運營等環節,為推動新能源汽車產業的發展提供了一個集交流,採購,瞭解新技術、新模式,品牌宣傳推廣等為一體的綜合展示平臺。

    參觀預登記,請點擊:

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

  • 另類思考!電動車熱未必有害原油、說不定恰恰相反?

    另類思考!電動車熱未必有害原油、說不定恰恰相反?

    國際車廠爭相研發電動車,汽油車似乎被宣判死刑,原油也會跟著步上末日。不過有專家大膽推估,電動車發展會削減流入原油業的資金,油市將汰弱留強,油價會維持低檔,也許將阻礙電動車普及之路。

    加拿大能源私募基金龍頭ARC Financial Corp.首席能源經濟學家Peter Tertzakian,13日在OilPrice發文稱,上週Volvo宣布,2019年起只生產電動車、停產汽柴油車。法國政府也宣布,2040年起停售汽柴油車。與此同時,特斯拉創辦人馬斯克貼出平價電動車Model 3照片,讓油市陷入低迷。

    電動車發展迅速,不少人認為原油業末日倒數計時。但是Tertzakian不以為然,指出環保車買氣疲弱。2013~2014年中,油價處於每桶100美元的高點時,美國電動車銷售提高,可是之後油價崩盤,電動車動能隨之消散。能源有兩股相反力量,現在的消費趨勢是原油便宜、使用率高,相對的,電動車滲透率低迷。但是市場對未來的預期恰恰相反,斷言原油將亡。

    未來幾年究竟會如何發展,Tertzakian推測有兩種可能結果,一是資本緊縮,汰除沒有效率的生產商,剩餘業者將不斷創新壓低成本,在低油價時也能存活,油價會繼續低迷,消費者缺乏誘因改買電動車。第二種可能是資本投資緊縮,將使原油供給下降,帶動油價上揚,電動車需求因此大增。諷刺的是,厲害的油商在兩種情況下,都能有好表現。可以確定的是,未來投入原油業的資金勢必減少。

    原油期貨報價從去(2016)年12月開始一路往下盤跌,高盛全球原物料研究部主管Jeffrey Currie對這提出了一個簡單解釋:市場上的錢太多。

    CNBC報導,Currie 12日在接受專訪時指出,市場上的原油其實並不是太多,只是錢潮淹腳目、委實過多。舉例來說,人們對石油探鑽活動的投資,是墨西哥最近能發現十數億桶原油的主因。

    Currie說,石油市場正在試著尋找供需均衡點,而產油商的損益兩平點在跌破高盛2016年估計的50-55美元後,新的平衡點在哪,則變得愈來愈難預測。不過,在被問到油市有沒有可能崩盤時,Currie說雖然他希望看到市場能有些波動,但其實崩盤機率極低。

    (本文內容由授權使用。圖片出處:public domain CC0)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 馬斯克看好電動車市場,Tesla 將在美國建 3 座超級工廠

    馬斯克看好電動車市場,Tesla 將在美國建 3 座超級工廠

    電動車製造商特斯拉(Tesla)和伊隆·馬斯克(Elon Musk)在過去幾個月中都多次提及了投資建造超級工廠的計畫,新製造工廠的選址將在年底公開,據悉 Tesla 將建造 5 座超級工廠,其中美國將有 2 到 3 座全新的超級工廠。   特斯拉將在海外建造超級工廠的消息已經傳了幾個月的時間了,在美國本土的超級工廠建造計畫則一直沒有進展。該公司 CEO 馬斯克確認將有 2 到 3 座超級工廠選址在美國本土。   2017 年 6 月特斯拉公司在股東大會上確認有 3 座超級工廠選址已經啟動,這些工廠包括了電動車和電池生產線。   據之前媒體曝光的消息顯示,特斯拉至少將在海外市場建造兩座超級工廠,分別位於歐洲和中國,特斯拉已經與中國上海市政府簽訂合作協議,共同建造電動車製造工廠。   在美國州長協議的會議上,馬斯克公開表示,將會有 2 到 3 座超級工廠選址在美國本土,他面對所有州長做出這一表態,也是希望政府部門能夠提供工廠建造和電動車生產方便的優惠政策,顯然許多州長都對 Tesla 超級工廠非常感興趣。   特斯拉在內華達州的超級工廠給該州帶來了超過 50 億美元的投資,創造了一萬個工作職缺,馬斯克表示,吸引 Tesla 把超級工廠建造內華達州的因素很多,包括稅收方面的優惠。   馬斯克希望政府部門能夠在立法上做出更多進步,讓新的技術能夠更快地商業化,之前他曾多次公開表示內華達州政府具有前瞻性,在超級工廠的建造過程中展示了前所未有的高效。   Tesla 未來的超級工廠將把電動車製造和電池的生產放在同一座工廠,有效地提升電動車的產能,而不是像現在這樣電池和電動車分開製造,再運往組裝工廠。   (合作媒體:。圖片出處:Tesla)  

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整