ThreadLocal原理分析與代碼驗證

ThreadLocal提供了線程安全的數據存儲和訪問方式,利用不帶key的get和set方法,居然能做到線程之間隔離,非常神奇。

比如

ThreadLocal<String> threadLocal = new ThreadLocal<>();

in thread 1

//in thread1
treadLocal.set("value1");
.....
//value的值是value1
String value = threadLocal.get();

in thread 2

//in thread2
treadLocal.set("value2");
.....
//value的值是value2
String value = threadLocal.get();

不論thread1和thread2是不是同時執行,都不會有線程安全問題,我們來測試一下。

線程安全測試

開10個線程,每個線程內都對同一個ThreadLocal對象set不同的值,會發現ThreadLocal在每個線程內部get出來的值,只會是自己線程內set進去的值,不會被別的線程影響。

static void testUsage() throws InterruptedException {
    Utils.println("-------------testUsage-------------------");
    ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    AtomicBoolean threadSafe = new AtomicBoolean(true);
    int count = 10;
    CountDownLatch countDownLatch = new CountDownLatch(count);
    Random random = new Random(736832);
    for (int i = 0; i < count; i ++){
        new Thread(() -> {
            try {
                //生成一個隨機數
                Long value = System.nanoTime() + random.nextInt();
                threadLocal.set(value);
                Thread.sleep(1000);

                Long value2 = threadLocal.get();
                if (!value.equals(value2)) {
                    //get和set的value不一致,說明被別的線程修改了,但這是不可能出現的
                    threadSafe.set(false);
                    Utils.println("thread unsafe, this could not be happen!");
                }
            } catch (InterruptedException e) {

            }finally {
                countDownLatch.countDown();
            }

        }).start();
    }

    countDownLatch.await();

    Utils.println("all thread done, and threadSafe is " + threadSafe.get());
    Utils.println("------------------------------------------");
}

輸出:

-------------testUsage------------------
all thread done, and threadSafe is true
-----------------------------------------

原理淺析

翻開ThreadLocal的源碼,會發現ThreadLocal只是一個空殼子,它並不存儲具體的value,而是利用當前線程(Thread.currentThread())的threadLocalMap來存儲value,key就是這個threadLocal對象本身。

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

Thread的threadLocals字段是ThreadLocalMap類型(你可以簡單理解為一個key value的Map),key是ThreadLocal對象,value是我們在外層設置的值

  • 當我們調用threadLocal.set(value)方法的時候,會找到當前線程的threadLocals這個map,然後以this作為key去set key value
  • 當我們調用threadLocal.get()方法的時候,會找到當前線程的threadLocals這個map,然後以this作為key去get value
  • 當我們調用threadLocal.remove()方法的時候,會找到當前線程的threadLocals這個map,然後以this作為key去remove

這就相當於:

Thread.currentThread().threadLocals.set(threadLocal1, "value1");
.....
//value的值是value1
String value = Thread.currentThread().threadLocals.get(threadLocal1);

因為每個Thread都是不同的對象,所以他們的threadLocals也是不同的map,threadLocal在不同的線程里工作時,實際上是從不同的map里get/set,這也就是線程安全的原因了,了解到這一點就差不多了。

再深入一些,ThreadLocalMap的結構

如果繼續翻ThreadLocalMap的源碼,會發現它有個字段table,是Entry類型的數組。

我們不妨寫段代碼,把ThreadLocalMap的結構輸出出來。

由於Thread.threadLocals和ThreadLocalMap類不是public的,我們只有通過反射來獲取它的值。反射的代碼如下(如果嫌長可以不看,直接看輸出):

static Object getThreadLocalMap(Thread thread) throws NoSuchFieldException, IllegalAccessException {        
    //get thread.threadLocals
    Field threadLocals = Thread.class.getDeclaredField("threadLocals");
    threadLocals.setAccessible(true);
    return threadLocals.get(thread);
}

static void printThreadLocalMap(Object threadLocalMap) throws NoSuchFieldException, IllegalAccessException {
    String threadName = Thread.currentThread().getName();
    
    if(threadLocalMap == null){
        Utils.println("threadMap is null, threadName:" + threadName);
        return;
    }

    Utils.println(threadName);

    //get threadLocalMap.table
    Field tableField = threadLocalMap.getClass().getDeclaredField("table");
    tableField.setAccessible(true);
    Object[] table = (Object[])tableField.get(threadLocalMap);
    Utils.println("----threadLocals (ThreadLocalMap), table.length = " + table.length);

    for (int i = 0; i < table.length; i ++){
        WeakReference<ThreadLocal<?>> entry = (WeakReference<ThreadLocal<?>>)table[i];
        printEntry(entry, i);
    }
}
static void printEntry(WeakReference<ThreadLocal<?>> entry, int i) throws NoSuchFieldException, IllegalAccessException {
    if(entry == null){
        Utils.println("--------table[" + i + "] -> null");
        return;
    }
    ThreadLocal key = entry.get();
    //get entry.value
    Field valueField = entry.getClass().getDeclaredField("value");
    valueField.setAccessible(true);
    Object value = valueField.get(entry);

    Utils.println("--------table[" + i + "] -> entry key = " + key + ", value = " + value);
}

測試代碼:

static void testStructure() throws InterruptedException {
    Utils.println("-------------testStructure----------------");
    ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
    ThreadLocal<String> threadLocal2 = new ThreadLocal<>();

    Thread thread1 = new Thread(() -> {
        threadLocal1.set("threadLocal1-value");
        threadLocal2.set("threadLocal2-value");

        try {
            Object threadLocalMap = getThreadLocalMap(Thread.currentThread());
            printThreadLocalMap(threadLocalMap);

        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

    }, "thread1");

    thread1.start();

    //wait thread1 done
    thread1.join();

    Thread thread2 = new Thread(() -> {
        threadLocal1.set("threadLocal1-value");
        try {
            Object threadLocalMap = getThreadLocalMap(Thread.currentThread());
            printThreadLocalMap(threadLocalMap);

        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

    }, "thread2");

    thread2.start();
    thread2.join();
    Utils.println("------------------------------------------");
}

我們在創建了兩個ThreadLocal的對象threadLocal1和threadLocal2,在線程1里為這兩個對象設置值,在線程2里只為threadLocal1設置值。然後分別打印出這兩個線程的threadLocalMap。

輸出結果為:

-------------testStructure----------------
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = java.lang.ThreadLocal@33baa315, value = threadLocal2-value
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> null
--------table[9] -> null
--------table[10] -> entry key = java.lang.ThreadLocal@4d42db5c, value = threadLocal1-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
thread2
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> null
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> null
--------table[9] -> null
--------table[10] -> entry key = java.lang.ThreadLocal@4d42db5c, value = threadLocal1-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
------------------------------------------

從結果上可以看出:

  • 線程1和線程2的threadLocalMap對象的table字段,是個數組,長度都是16
  • 由於線程1里給兩個threadLocal對象設置了值,所以線程1的ThreadLocalMap里有兩個entry,數組下標分別是1和10,其餘的是null(如果你自己寫代碼驗證,下標不一定是1和10,不需要糾結這個問題,只要前後對的上就行)
  • 由於線程2里只給一個threadLocal對象設置了值,所以線程1的ThreadLocalMap里只有一個entry,數組下標是10,其餘的是null
  • threadLocal1這個對象在兩個線程里都設置了值,所以當它作為key加入二者的threadLocalMap時,key是一樣的,都是java.lang.ThreadLocal@4d42db5c;下標也是一樣的,都是10。

為什麼是WeakReference

查看Entry的源碼,會發現Entry繼承自WeakReference:

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

構造函數里把key傳給了super,也就是說,ThreadLocalMap中對key的引用,是WeakReference的。

Weak reference objects, which do not prevent their referents from being
made finalizable, finalized, and then reclaimed. Weak references are most
often used to implement canonicalizing mappings.

通俗點解釋:

當一個對象僅僅被weak reference(弱引用), 而沒有任何其他strong reference(強引用)的時候, 不論當前的內存空間是否足夠,當GC運行的時候, 這個對象就會被回收。

看不明白沒關係,還是寫代碼測試一下什麼是WeakReference吧…

static void testWeakReference(){
    Object obj1 = new Object();
    Object obj2 = new Object();
    WeakReference<Object> obj1WeakRef = new WeakReference<>(obj1);
    WeakReference<Object> obj2WeakRf = new WeakReference<>(obj2);
    //obj32StrongRef是強引用
    Object obj2StrongRef = obj2;
    Utils.println("before gc: obj1WeakRef = " + obj1WeakRef.get() + ", obj2WeakRef = " + obj2WeakRf.get() + ", obj2StrongRef = " + obj2StrongRef);

    //把obj1和obj2設為null
    obj1 = null;
    obj2 = null;
    //強制gc
    forceGC();

    Utils.println("after gc: obj1WeakRef = " + obj1WeakRef.get() + ", obj2WeakRef = " + obj2WeakRf.get() + ", obj2StrongRef = " + obj2StrongRef);
}

結果輸出:

before gc: obj1WeakRef = java.lang.Object@4554617c, obj2WeakRef = java.lang.Object@74a14482, obj2StrongRef = java.lang.Object@74a14482
after gc: obj1WeakRef = null, obj2WeakRef = java.lang.Object@74a14482, obj2StrongRef = java.lang.Object@74a14482

從結果上可以看出:

  • 我們先new了兩個對象(為避免混淆,稱他們為Object1和Object2),分別用變量obj1和obj2指向它們,同時定義了一個obj2StrongRef,也指向Object2,最後把obj1和obj2均指向null
  • 由於Object1沒有變量強引用它了,所以在gc后,Object1被回收了,obj1WeakRef.get()返回了null
  • 由於Object2還有obj2StrongRef在引用它,所以gc后,Object2依然存在,沒有被回收。

那麼,ThreadLocalMap中對key的引用,為什麼是WeakReference的呢?

因為大部分情況下,線程不死

大部分情況下,線程不會頻繁的創建和銷毀,一般都會用線程池。所以線程對象一般不會被清除,線程的threadLocalMap就一直存在。
如果key對ThreadLocal是強引用,那麼key永遠不會被回收,即使我們程序里再也不用它了。

但是key是弱引用的話,情況就會得到改善:只要沒有指向threadLocal的強引用了,這個ThreadLocal對象就會被清理。

我們還是寫代碼測試一下吧。

/**
 * 測試ThreadLocal對象什麼時候被回收
 * @throws InterruptedException
 */
static void testGC() throws InterruptedException {
    Utils.println("-----------------testGC-------------------");
    Thread thread1 = new Thread(() -> {
        ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
        ThreadLocal<String> threadLocal2 = new ThreadLocal<>();

        threadLocal1.set("threadLocal1-value");
        threadLocal2.set("threadLocal2-value");

        try {
            Object threadLocalMap = getThreadLocalMap(Thread.currentThread());
            Utils.println("print threadLocalMap before gc");
            printThreadLocalMap(threadLocalMap);

            //set threadLocal1 unreachable
            threadLocal1 = null;

            forceGC();

            Utils.println("print threadLocalMap after gc");
            printThreadLocalMap(threadLocalMap);


        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

    }, "thread1");

    thread1.start();
    thread1.join();
    Utils.println("------------------------------------------");
}

我們在一個線程里為兩個ThreadLocal對象賦值,最後把其中一個對象的強引用移除,gc后打印當前線程的threadLocalMap。
輸出結果如下:

-----------------testGC-------------------
print threadLocalMap before gc
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = java.lang.ThreadLocal@7bf9cebf, value = threadLocal2-value
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> null
--------table[9] -> null
--------table[10] -> entry key = java.lang.ThreadLocal@56342d38, value = threadLocal1-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
print threadLocalMap after gc
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = java.lang.ThreadLocal@7bf9cebf, value = threadLocal2-value
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> null
--------table[9] -> null
--------table[10] -> entry key = null, value = threadLocal1-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
------------------------------------------

從輸出結果可以看到,當我們把threadLocal1的強引用移除並gc之後,table[10]的key變成了null,說明threadLocal1這個對象被回收了;threadLocal2的強引用還在,所以table[1]的key不是null,沒有被回收。

但是你發現沒有,table[10]的key雖然是null了,但value還活着! table[10]這個entry對象,也活着!

是的,因為只有key是WeakReference….

無用的entry什麼時候被回收?

通過查看ThreadLocal的源碼,發現在ThreadLocal對象的get/set/remove方法執行時,都有機會清除掉map中已經無用的entry。

最容易驗證清除無用entry的場景分別是:

  • remove:這個不用說了,這哥們本來就是做這個的
  • get:當一個新的threadLocal對象(沒有set過value)發生get調用時,也會作為新的entry加入map,在加入的過程中,有機會清除掉無用的entry,邏輯和下面的set相同。
  • set: 當一個新的threadLocal對象(沒有set過value)發生set調用時,會在map中加入新的entry,此時有機會清除掉無用的entry,清除的邏輯是:
    • 清除掉table數組中的那些無用entry中的一部分,記住是一部分,這個一部分可能全部,也可能是0,具體算法請看ThreadLocalMap.cleanSomeSlots,這裏不解釋了。
    • 如果上一步的”一部分”是0(即清除了0個),並且map的size(是真實size,不是table.length)大於等於threshold(table.length的2/3),會執行一次rehash,在rehash的過程中,清理掉所有無用的entry,並減小size,清理后的size如果還大於等於threshold – threshold/4,則把table擴容為原來的兩倍大小。

還有其他場景,但不好驗證,這裏就不提了。

ThreadLocal源碼就不貼了,貼了也講不明白,相關邏輯在setInitialValue、cleanSomeSlots、expungeStaleEntries、rehash、resize等方法里。

在我們寫代碼驗證entry回收邏輯之前,還需要簡單的提一下ThreadLocalMap的hash算法。

entry數組的下標如何確定?

每個ThreadLocal對象,都有一個threadLocalHashCode變量,在加入ThreadLocalMap的時候,根據這個threadLocalHashCode的值,對entry數組的長度取余(hash & (len – 1)),餘數作為下標。

那麼threadLocalHashCode是怎麼計算的呢?看源碼:

public class ThreadLocal<T>{
    private final int threadLocalHashCode = nextHashCode();
    private static AtomicInteger nextHashCode = new AtomicInteger();

    private static final int HASH_INCREMENT = 0x61c88647;

    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    ...
}

ThreadLocal類維護了一個全局靜態字段nextHashCode,每new一個ThreadLocal對象,nextHashCode都會遞增0x61c88647,作為下一個ThreadLocal對象的threadLocalHashCode。

這個0x61c88647,是個神奇的数字,只要以它為遞增值,那麼和2的N次方取余時,在有限的次數內不會發生重複。
比如和16取余,那麼在16次遞增內,不會發生重複。還是寫代碼驗證一下吧。

int hashCode = 0;
int HASH_INCREMENT = 0x61c88647;
int length = 16;

for(int i = 0; i < length ; i ++){
    int h = hashCode & (length - 1);
    hashCode += HASH_INCREMENT;
    System.out.println("h = " + h + ", i = " + i);
}

輸出結果為:

h = 0, i = 0
h = 7, i = 1
h = 14, i = 2
h = 5, i = 3
h = 12, i = 4
h = 3, i = 5
h = 10, i = 6
h = 1, i = 7
h = 8, i = 8
h = 15, i = 9
h = 6, i = 10
h = 13, i = 11
h = 4, i = 12
h = 11, i = 13
h = 2, i = 14
h = 9, i = 15

你看,h的值在16次遞增內,沒有發生重複。 但是要記住,2的N次方作為長度才會有這個效果,這也解釋了為什麼ThreadLocalMap的entry數組初始長度是16,每次都是2倍的擴容。

驗證新threadLocal的get和set時回收部分無效的entry

為了驗證出結果,我們需要先給ThreadLocal的nextHashCode重置一個初始值,這樣在測試的時候,每個threadLocal的數組下標才會按照我們設計的思路走。

static void resetNextHashCode() throws NoSuchFieldException, IllegalAccessException {
    Field nextHashCodeField = ThreadLocal.class.getDeclaredField("nextHashCode");
    nextHashCodeField.setAccessible(true);
    nextHashCodeField.set(null, new AtomicInteger(1253254570));
}

然後在測試代碼里,我們先調用resetNextHashCode方法,然後加兩個ThreadLocal對象並set值,gc前把強引用去除,gc后再new兩個新的theadLocal對象,分別調用他們的get和set方法。
在每個關鍵點打印出threadLocalMap做比較。

static void testExpungeSomeEntriesWhenGetOrSet() throws InterruptedException {
    Utils.println("----------testExpungeStaleEntries----------");
    Thread thread1 = new Thread(() -> {
        try {
            resetNextHashCode();

            //注意,這裏必須有兩個ThreadLocal,才能驗證出threadLocal1被清理
            ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
            ThreadLocal<String> threadLocal2 = new ThreadLocal<>();

            threadLocal1.set("threadLocal1-value");
            threadLocal2.set("threadLocal2-value");


            Object threadLocalMap = getThreadLocalMap(Thread.currentThread());
            //set threadLocal1 unreachable
            threadLocal1 = null;
            threadLocal2 = null;
            forceGC();

            Utils.println("print threadLocalMap after gc");
            printThreadLocalMap(threadLocalMap);

            ThreadLocal<String> newThreadLocal1 = new ThreadLocal<>();
            newThreadLocal1.get();
            Utils.println("print threadLocalMap after call a new newThreadLocal1.get");
            printThreadLocalMap(threadLocalMap);

            ThreadLocal<String> newThreadLocal2 = new ThreadLocal<>();
            newThreadLocal2.set("newThreadLocal2-value");
            Utils.println("print threadLocalMap after call a new newThreadLocal2.set");
            printThreadLocalMap(threadLocalMap);


        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

    }, "thread1");

    thread1.start();
    thread1.join();
    Utils.println("------------------------------------------");
}

程序輸出結果為:

----------testExpungeStaleEntries----------
print threadLocalMap after gc
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = null, value = threadLocal2-value
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> null
--------table[9] -> null
--------table[10] -> entry key = null, value = threadLocal1-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
print threadLocalMap after call a new newThreadLocal1.get
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = null, value = threadLocal2-value
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> entry key = java.lang.ThreadLocal@2b63dc81, value = null
--------table[9] -> null
--------table[10] -> null
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
print threadLocalMap after call a new newThreadLocal2.set
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> null
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> entry key = java.lang.ThreadLocal@2b63dc81, value = null
--------table[9] -> null
--------table[10] -> null
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> entry key = java.lang.ThreadLocal@2e93c547, value = newThreadLocal2-value
------------------------------------------

從結果上來看,

  • gc后table[1]和table[10]的key變成了null
  • new newThreadLocal1.get后,新增了table[8],table[10]被清理了,但table[1]還在(這就是cleanSomeSlots中some的意思)
  • new newThreadLocal2.set后,新增了table[15],table[1]被清理了。

驗證map的size大於等於table.length的2/3時回收所有無效的entry

    static void testExpungeAllEntries() throws InterruptedException {
        Utils.println("----------testExpungeStaleEntries----------");
        Thread thread1 = new Thread(() -> {
            try {
                resetNextHashCode();

                int threshold = 16 * 2 / 3;
                ThreadLocal[] threadLocals = new ThreadLocal[threshold - 1];
                for(int i = 0; i < threshold - 1; i ++){
                    threadLocals[i] = new ThreadLocal<String>();
                    threadLocals[i].set("threadLocal" + i + "-value");
                }

                Object threadLocalMap = getThreadLocalMap(Thread.currentThread());

                threadLocals[1] = null;
                threadLocals[8] = null;
                //threadLocals[6] = null;
                //threadLocals[4] = null;
                //threadLocals[2] = null;
                forceGC();

                Utils.println("print threadLocalMap after gc");
                printThreadLocalMap(threadLocalMap);

                ThreadLocal<String> newThreadLocal1 = new ThreadLocal<>();
                newThreadLocal1.set("newThreadLocal1-value");
                Utils.println("print threadLocalMap after call a new newThreadLocal1.get");
                printThreadLocalMap(threadLocalMap);

            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }

        }, "thread1");

        thread1.start();
        thread1.join();
        Utils.println("------------------------------------------");
    }

我們先創建了9個threadLocal對象並設置了值,然後去掉了其中2個的強引用(注意這2個可不是隨意挑選的)。
gc后再添加一個新的threadLocal,最後打印出最新的map。輸出為:

----------testExpungeStaleEntries----------
print threadLocalMap after gc
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = null, value = threadLocal1-value
--------table[2] -> entry key = null, value = threadLocal8-value
--------table[3] -> null
--------table[4] -> entry key = java.lang.ThreadLocal@60523912, value = threadLocal6-value
--------table[5] -> null
--------table[6] -> entry key = java.lang.ThreadLocal@48fccd7a, value = threadLocal4-value
--------table[7] -> null
--------table[8] -> entry key = java.lang.ThreadLocal@188bbe72, value = threadLocal2-value
--------table[9] -> null
--------table[10] -> entry key = java.lang.ThreadLocal@19e0ebe8, value = threadLocal0-value
--------table[11] -> entry key = java.lang.ThreadLocal@688bcb6f, value = threadLocal7-value
--------table[12] -> null
--------table[13] -> entry key = java.lang.ThreadLocal@46324c19, value = threadLocal5-value
--------table[14] -> null
--------table[15] -> entry key = java.lang.ThreadLocal@38f1283, value = threadLocal3-value
print threadLocalMap after call a new newThreadLocal1.get
thread1
----threadLocals (ThreadLocalMap), table.length = 32
--------table[0] -> null
--------table[1] -> null
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> entry key = java.lang.ThreadLocal@48fccd7a, value = threadLocal4-value
--------table[7] -> null
--------table[8] -> null
--------table[9] -> entry key = java.lang.ThreadLocal@1dae16b1, value = newThreadLocal1-value
--------table[10] -> entry key = java.lang.ThreadLocal@19e0ebe8, value = threadLocal0-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> entry key = java.lang.ThreadLocal@46324c19, value = threadLocal5-value
--------table[14] -> null
--------table[15] -> null
--------table[16] -> null
--------table[17] -> null
--------table[18] -> null
--------table[19] -> null
--------table[20] -> entry key = java.lang.ThreadLocal@60523912, value = threadLocal6-value
--------table[21] -> null
--------table[22] -> null
--------table[23] -> null
--------table[24] -> entry key = java.lang.ThreadLocal@188bbe72, value = threadLocal2-value
--------table[25] -> null
--------table[26] -> null
--------table[27] -> entry key = java.lang.ThreadLocal@688bcb6f, value = threadLocal7-value
--------table[28] -> null
--------table[29] -> null
--------table[30] -> null
--------table[31] -> entry key = java.lang.ThreadLocal@38f1283, value = threadLocal3-value
------------------------------------------

從結果上看:

  • gc后table[1]和table[2](即threadLocal1和threadLocal8)的key變成了null
  • 加入新的threadLocal后,table的長度從16變成了32(因為此時的size是8,正好等於10 – 10/4,所以擴容),並且threadLocal1和threadLocal8這兩個entry不見了。

如果在gc前,我們把threadLocals[1、8、6、4、2]都去掉強引用,加入新threadLocal後會發現1、8、6、4、2被清除了,但沒有擴容,因為此時size是5,小於10-10/4。這個邏輯就不貼測試結果了,你可以取消註釋上面代碼中相關的邏輯試試。

大部分場景下,ThreadLocal對象的生命周期是和app一致的,弱引用形同虛設

回到現實中。

我們用ThreadLocal的目的,無非是在跨方法調用時更方便的線程安全地存儲和使用變量。這就意味着ThreadLocal的生命周期很長,甚至和app是一起存活的,強引用一直在。

既然強引用一直存在,那麼弱引用就形同虛設了。

所以在確定不再需要ThreadLocal中的值的情況下,還是老老實實的調用remove方法吧!

代碼地址

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

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

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

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

【故障公告】數據庫服務器 CPU 近 100% 引發的故障(源於 .NET Core 3.0 的一個 bug),雲計算之路-阿里雲上:數據庫連接數過萬的真相,從阿里雲RDS到微軟.NET Core

非常抱歉,這次故障給您帶來麻煩了,請您諒解。

今天早上 10:54 左右,我們所使用的數據庫服務(阿里雲 RDS 實例 SQL Server 2016 標準版)CPU 突然飆升至 90% 以上,應用日誌中出現大量數據庫查詢超時的錯誤。

Microsoft.Data.SqlClient.SqlException (0x80131904): Execution Timeout Expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.
 ---> System.ComponentModel.Win32Exception (258): Unknown error 258

我們收到告警通知並確認問題后,在 11:06 啟動了阿里雲 RDS 的主備切換, 11:08 完成切換,數據庫 CPU 恢復正常。但是關鍵時候 docker swarm 總是雪上加霜,在數據庫恢復正常后,部署博客站點的 docker swarm 集群有一個節點出現異常情況,部分請求會出現 50x 錯誤,將這個異常節點退出集群並啟動新的節點后在 11:15 左右才恢復正常。

通過阿里雲 RDS 控制台的 CloudDBA 發現了 CPU 近 100% 期間執行次數異常多的 SQL 語句。

SELECT TOP @__p_1 [b].[TagName] AS [Name], [b].[TagID] AS [Id], [b].[UseCount], [b].[BlogId]
FROM [blog_Tag] [b]
WHERE [b].[BlogId] = @__blogId_0
    AND @__blogId_0 IS NOT NULL
    AND [b].[UseCount] > ?
ORDER BY [b].[UseCount] DESC

上面的 SQL 語句是 EF Core 3.0 生成的,其中加粗的  IS NOT NULL  就是 EF Core 3.0 的一個臭名還沒昭著的 bug —— 生成 SQL 語句時會生成額外的  IS NOT NULL  查詢條件。

誰也沒想到(連微軟自己也沒想到)這個看似無傷大雅的多此一舉卻存在致命隱患 —— 在某些情況下會讓整個數據庫服務器 CPU 持續 100% (或者近 100%)。一開始遇到這個問題時,我們也沒想到,還因此錯怪了阿里雲(),後來在阿里雲數據庫專家分析了我們遇到的問題后才發現原來罪魁禍首是 EF Core 生成的多餘的 “IS NOT NULL” ,它會在某些情況下會造成 SQL Server 緩存了性能極其低下(很耗CPU)的執行計劃,然後後續的查詢都走這個執行計劃,CPU 就會居高不下。這個錯誤的執行計劃有雙重殺傷力,一邊巨耗數據庫 CPU ,一邊造成對應的查詢無法正常完成從而查詢結果不能被緩存到 memcached ,於是針對這個執行計劃的查詢就越多,雪崩效應就發生了。唯一的解決方法就是清除這個錯誤的執行計劃緩存,主備切換或者重啟服務器只是清除執行計劃緩存的一種簡單粗暴的方法。

在我們開始遇到這個問題,就已經有人在 github 上了這個問題:

Yeah this needs to be fixed asap. We just deployed code that uses 3.0 and had to immediately revert to 2.2 because simple queries blew up our SQL Azure CPU usage. Went from under 50% to 100% and stayed there until we rolled back.

但當時沒有引起微軟的足夠重視,在我們知道錯怪了阿里雲實際是微軟的問題之後,我們向微軟 .NET 團隊反饋了這個問題,這次得到了微軟的重視,很快就修復了,但是是通過 .NET Core 3.0 Preview 版發布的,我們在非生產環境下驗證了  IS NOT NULL 的確修復了,由於是 Preview 版,再加上 .NET Core 3.1 正式版年底前會發布,所以我們沒有在生產環境中更新這個修復,只是將上次出現問題的複雜 SQL 語句改為用 Dapper 調用存儲過程。後來阿里雲數據庫專家進一步對我們的數據庫進行分析,連平時數據庫 CPU 的毛刺(偶爾跑高的波動)都與  IS NOT NULL  有關。

這就是這次故障的背景,在我們等待 .NET Core 3.1 正式版修復這個 bug 的過程中又被坑了一次,與上次不同的是這次出現問題的 SQL 語句非常簡單,而且只有一個 “IS NOT NULL” ,由此可見這個坑的殺傷力。

這個坑足以載入 .NET Core 的史冊,另一個讓我們記憶猶新的那次也讓我們錯怪阿里雲的 .NET Core 坑是正式版的 .NET Core 中 SqlClient 竟然漏寫了 Dispose ,詳見 。

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

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

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

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

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

使用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!馬斯克大秀美照

千呼萬喚始出來!特斯拉(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 年停售汽柴油車,能源部長:這會是一場革命

不同於美國退出巴黎協定的決定,法國總統馬克宏曾提到要「讓地球再次偉大」,如今看來,法國似乎正朝目標一步步邁進中。法國能源部長余洛(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、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電動汽車發展指數 中國首度躍居整體排名第一

中證網報導,羅蘭貝格與德國汽車研究機構亞琛汽車工程技術公司共同發佈《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/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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