分類
發燒車訊

[ASP.NET Core 3框架揭秘] 文件系統[3]:物理文件系統

ASP.NET Core應用中使用得最多的還是具體的物理文件,比如配置文件、View文件以及作為Web資源的靜態文件。物理文件系統由定義在NuGet包“Microsoft.Extensions.FileProviders.Physical”中的PhysicalFileProvider來構建。我們知道System.IO命名空間下定義了一整套針操作物理目錄和文件的API,實際上PhysicalFileProvider最終也是通過調用這些API來完成相關的IO操作。

public class PhysicalFileProvider : IFileProvider, IDisposable
{   
    public PhysicalFileProvider(string root);   
     
    public IFileInfo GetFileInfo(string subpath);  
    public IDirectoryContents GetDirectoryContents(string subpath); 
    public IChangeToken Watch(string filter);

    public void Dispose();   
}

一、PhysicalFileInfo

一個PhysicalFileProvider對象總是映射到某個具體的物理目錄上,被映射的目錄所在的路徑通過構造函數的參數root來提供,該目錄將作為PhysicalFileProvider的根目錄。GetFileInfo方法返回的IFileInfo對象代表指定路徑對應的文件,這是一個類型為PhysicalFileInfo的對象。一個物理文件可以通過一個System.IO.FileInfo對象來表示,一個PhysicalFileInfo對象實際上就是對該對象的封裝,定義在PhysicalFileInfo的所有屬性都來源於這個FileInfo對象。對於創建讀取文件輸出流的CreateReadStream方法來說,它返回的是一個根據物理文件絕對路徑創建的FileStream對象。

public class PhysicalFileInfo : IFileInfo
{
    ...
    public PhysicalFileInfo(FileInfo info);    
}

對於PhysicalFileProvider的GetFileInfo方法來說,即使我們指定的路徑指向一個具體的物理文件,它並不總是會返回一個PhysicalFileInfo對象。PhysicalFileProvider會將一些場景視為“目標文件不存在”,並讓GetFileInfo方法返回一個NotFoundFileInfo對象。具體來說,PhysicalFileProvider的GetFileInfo方法在如下的場景中會返回一個NotFoundFileInfo對象:

  • 確實沒有一個物理文件與指定的路徑相匹配。
  • 如果指定的是一個絕對路徑(比如“c:\foobar”),即Path.IsPathRooted方法返回True。
  • 如果指定的路徑指向一個隱藏文件。

顧名思義,具有如下定義的NotFoundFileInfo類型表示一個“不存在”的文件。NotFoundFileInfo對象的Exists屬性總是返回False,而其他的屬性則變得沒有任何意義。當我們調用它的CreateReadStream試圖讀取一個根本不存在的文件內容時,會拋出一個FileNotFoundException類型的異常。

public class NotFoundFileInfo : IFileInfo
{
    public bool Exists => false;   
    public long Length => throw new NotImplementedException();   
    public string PhysicalPath => null;  
    public string Name { get; }   
    public DateTimeOffset LastModified => DateTimeOffset.MinValue;
    public bool IsDirectory => false; 

    public NotFoundFileInfo(string name) => this.Name = name;
    public Stream CreateReadStream() => throw new FileNotFoundException($"The file {Name} does not exist.");
}

二、PhysicalDirectoryInfo

PhysicalFileProvider利用一個PhysicalFileInfo對象來描述某個具體的物理文件,而一個物理目錄則通過一個PhysicalDirectoryInfo的對象來描述。既然PhysicalFileInfo是對一個FileInfo對象的封裝,那麼我們應該想得到PhysicalDirectoryInfo對象封裝的就是表示目錄的DirectoryInfo對象。如下面的代碼片段所示,我們需要在創建一個PhysicalDirectoryInfo對象時提供這個DirectoryInfo對象,PhysicalDirectoryInfo實現的所有屬性的返回值都來源於這個DirectoryInfo對象。由於CreateReadStream方法的目的總是讀取文件的內容,所以PhysicalDirectoryInfo類型的這個方法會拋出一個InvalidOperationException類型的異常。

public class PhysicalDirectoryInfo : IFileInfo
{   
    ...
    public PhysicalDirectoryInfo(DirectoryInfo info);
}

三、PhysicalDirectoryContents

當我們調用PhysicalFileProvider的GetDirectoryContents方法時,如果指定的路徑指向一個具體的目錄,那麼該方法會返回一個類型為PhysicalDirectoryContents的對象。PhysicalDirectoryContents是一個IFileInfo對象的集合,該集合中包括所有描述子目錄的PhysicalDirectoryInfo對象和描述文件的PhysicalFileInfo對象。PhysicalDirectoryContents的Exists屬性取決於指定的目錄是否存在。

public class PhysicalDirectoryContents : IDirectoryContents
{
    public bool Exists { get; }
    public PhysicalDirectoryContents(string directory);
    public IEnumerator<IFileInfo> GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator();
}

四、NotFoundDirectoryContents

如果指定的路徑並不指向一個存在的目錄,或者指定的是一個絕對路徑,GetDirectoryContents方法都會返回一個Exsits為False的NotFoundDirectoryContents對象。如下所示的代碼片段展示了NotFoundDirectoryContents類型的定義,如果我們需要使用到這麼一個類型,可以直接利用靜態屬性Singleton得到對應的單例對象。

public class NotFoundDirectoryContents : IDirectoryContents
{    
    public static NotFoundDirectoryContents Singleton { get; }  = new NotFoundDirectoryContents();
    public bool Exists => false;
    public IEnumerator<IFileInfo> GetEnumerator()  => Enumerable.Empty<IFileInfo>().GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

五、PhysicalFilesWatcher

我們接着來談談PhysicalFileProvider的Watch方法。當我們調用該方法的時候,PhysicalFileProvider會通過解析我們提供的Globbing Pattern表達式來確定我們期望監控的文件或者目錄,並最終利用FileSystemWatcher對象來對這些文件實施監控。這些文件或者目錄的變化(創建、修改、重命名和刪除等)都會實時地反映到Watch方法返回的IChangeToken上。

PhysicalFileProvider的Watch方法中指定的Globbing Pattern表達式必須是針對當前根目錄的相對路徑,我們可以使用“/”或者“./”前綴,也可以不採用任何前綴。一旦我們使用了絕對路徑(比如“c:\test\*.txt”)或者“../”前綴(比如“../test/*.txt”),不論解析出來的文件是否存在於PhysicalFileProvider的根目錄下,這些文件都不會被監控。除此之外,如果我們沒有指定Globbing Pattern表達式,PhysicalFileProvider也不會有任何的文件會被監控。

PhysicalFileProvider針對物理文件系統變化的監控是通過如下這個PhysicalFilesWatcher對象實現的,其Watch方法內部會直接調用PhysicalFileProvider的CreateFileChangeToken方法,並返回得到的IChangeToken對象。這是一個公共類型,如果我們具有監控物理文件系統變化的需要,可以直接使用這個類型。

public class PhysicalFilesWatcher: IDisposable
{
    public PhysicalFilesWatcher(string root, FileSystemWatcher fileSystemWatcher, bool pollForChanges);
    public IChangeToken CreateFileChangeToken(string filter);
    public void Dispose();
}

從PhysicalFilesWatcher構造函數的定義我們不難看出,它最終是利用一個FileSystemWatcher對象(對應於fileSystemWatcher參數)來完成針對指定根目錄下(對應於root參數)所有子目錄和文件的監控。FileSystemWatcher的CreateFileChangeToken方法返回的IChangeToken對象會幫助我們感知到子目錄或者文件的添加、刪除、修改和重命名,但是它會忽略隱藏的目錄和文件。最後需要提醒的是,當我們不再需要對指定目錄實施監控的時候,記得調用PhysicalFileProvider的Dispose方法,該方法會負責將FileSystemWatcher對象關閉。

六、小結

我們藉助下圖所示的UML來對由PhysicalFileProvider構建物理文件系統的整體設計做一個簡單的總結。首先,該文件系統使用PhysicalDirectoryInfo和PhysicalFileInfo對類型來描述目錄和文件,它們分別是對DirectoryInfo和FileInfo(System.IO.FileInfo)對象的封裝。

PhysicalFileProvider的GetDirectoryContents方法返回一個PhysicalDirectoryContents 對象(如果指定的目錄存在),組成該對象的分別是根據其所有子目錄和文件創建的PhysicalDirectoryInfo和PhysicalFileInfo對象。當我們調用PhysicalFileProvider的GetFileInfo方法時,如果指定的文件存在,返回的是描述該文件的PhysicalFileInfo對象。至於PhysicalFileProvider的Watch方法,它最終利用了FileSystemWatcher來監控指定文件或者目錄的變化。

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

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

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

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

分類
發燒車訊

CSS:CSS彈性盒子布局 Flexible Box

一、簡介

flexbox:全稱Flexible Box, 彈性盒子布局。可以簡單實現各種伸縮性的設計,它是由伸縮容器和伸縮項目組成。任何一個元素都可以指定為flexbox布局。這種新的布局方案在2009年是由W3C組織提出來的,在此之前,Web開發一般使用基於盒子模型的傳統頁面布局,依賴定位屬性、流動屬性和显示屬性來解決,參看鏈接:。彈性盒子布局的出現,極大的方便了開發者,在如今的ReactNative開發中,也已經被引入使用。

伸縮流布局結構圖如下:

彈性盒子布局具備的特徵:

1、伸縮容器的子元素稱為伸縮項目,伸縮項目使用伸縮布局來排版。伸縮布局和傳統布局不一樣,它按照伸縮流方向布局。

2、伸縮容器由兩條軸構成,分別為主軸(main axis)和交叉軸(cross axis)。主軸既可以用水平軸,也可以是豎直軸,根據開發者需要來決定。

3、主軸的起點叫main start,終點叫main end,主軸的空間用main size表示。

4、交叉軸的起點叫cross start,終點叫cross end,交叉軸的空間用cross size表示。

5、默認情況下,伸縮項目總是沿着主軸方向排版,從開始位置到終點位置。至於換行显示,則通過設置伸縮屬性來實現。

6、伸縮容器的屬性有:display、flex-direction、flex-wrap、flex-flow、justify-content、align-items、align-content

7、伸縮項目的屬性有: order、flex-grow、flex-shrink、flex-basis、flex、align-self

 

二、伸縮容器的屬性,全局設置排版

HTML:[注意:下面的演示截圖項目個數會根據需要選擇性註釋“flex-item”,有時用不到5個]

<!DOCTYPE html>
<html>
<head>
    <title>Flexbox</title>
    <!--  採用外聯方式導入css文件 -->
    <link rel="stylesheet" type="text/css" href="./css_test.css">
</head>
<body>
    <span class="flex-container"> 
        <span class="flex-item" id="item1" style="color:white;font-size:20px">1</span>
        <span class="flex-item" id="item2" style="color:white;font-size:20px">2</span>
        <span class="flex-item" id="item3" style="color:white;font-size:20px">3</span>
        <span class="flex-item" id="item4" style="color:white;font-size:20px">4</span>
        <span class="flex-item" id="item5" style="color:white;font-size:20px">5</span>
    </span>
</body>
</html> 

1、display:決定元素是否為伸縮容器

  • flex:產生塊級伸縮容器
    .flex-container {
         display: flex;
     }
  • inline-flex:產生行內塊級伸縮容器
  •  .flex-container {
         display: inline-flex;
     }

2、flex-direction:指定伸縮容器主軸的方向

  • row:水平方向,從左到右
     .flex-container {
         display: flex;
         flex-direction: row;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px; 
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • row-reverse:水平方向,從右到左
     .flex-container {
         display: flex;
         flex-direction: row-reverse;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px; 
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • column:豎直方向,從上到下
     .flex-container {
         display: flex;
         flex-direction: column;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px; 
         background-color: green;
         margin: 1px;
     }

  • column-reverse:豎直方向,從下到上
     .flex-container {
         display: flex;
         flex-direction: column-reverse;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px; 
         background-color: green;
         margin: 1px;
     }

3、flex-wrap:指定伸縮容器主軸方向空間不足時,決定是否換行以及換行方式

  • nowarp:不換行
    .flex-container {
         display: flex;
         flex-direction: row;
         flex-wrap: nowrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px; //下圖單行狀態寬度被重新計算
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • warp:換行,若主軸為水平方向,換行方向是從上到下
     .flex-container {
         display: flex;
         flex-direction: row;
         flex-wrap: wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • wrap-reverse:換行,若主軸為水平方向,換行方向是從下到上
     .flex-container {
         display: flex;
         flex-direction: row;
         flex-wrap: wrap-reverse;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

4、flex-flow:flex-direction和flex-wrap的縮寫,同時指定伸縮容器主軸方向和換行設置

  • row nowrap:默認主軸是水平方向,從左到右,且不換行
     .flex-container {
         display: flex;
         flex-flow: row nowrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px; //下圖單行狀態寬度被重新計算
         height: 50px;
         background-color: green;
         margin: 1px;
     }

5、justify-content:決定伸縮項目沿着主軸線的對齊方式

  • flex-start:與主軸線起始位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         justify-content: flex-start;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • flex-end:與主軸線結束位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         justify-content: flex-end;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • center:與主軸線中間位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         justify-content: center;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • space-between:平均分配到主軸線里,第一個項目靠齊起始位置,最後一個項目靠齊終點位置
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         justify-content: space-between;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • sapce-around:平均分配到主軸線里,兩端保留一半的空間
    .flex-container {
         display: flex;
         flex-flow: row wrap;
         justify-content: space-around;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

6、align-items:決定伸縮項目不能換行時沿着交叉軸線的對齊方式

  • flex-start:與交叉軸線起始位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row nowrap;
         align-items: flex-start;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px; //下圖單行狀態寬度被重新計算
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • flex-end:與交叉軸線結束位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row nowrap;
         align-items: flex-end;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px; //下圖單行狀態寬度被重新計算
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • center:與交叉軸線中間位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row nowrap;
         align-items: center;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • baseline:根據基線對齊
     .flex-container {
         display: flex;
         flex-flow: row nowrap;
         align-items: baseline;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item1 {
         padding-top: 25px;
     }
    
     #item2 {
         padding-top: 20px;
     }
      
     #item3 {
         padding-top: 15px;
     }
    
     #item4 {
         padding-top: 10px;
     }
      
     #item5 {
         padding-top: 5px;
     }

  • stretch:沿着交叉軸線拉伸填充整個伸縮容器
     .flex-container {
         display: flex;
         flex-flow: row nowrap;
         align-items: stretch;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;//此時可以設置寬度,但不能設置高度,否則無法拉伸
         background-color: green;
         margin: 1px;
     }

7、align-content:決定伸縮項目可以換行時沿着交叉軸線的對齊方式,flex-warp:warp一定要開啟

  • flex-start:與交叉軸線起始位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         align-content:flex-start;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • flex-end:與交叉軸線結束位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         align-content:flex-end;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • center:與主軸線中間位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         align-content:center;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • space-between:平均分配到主軸線里,第一行項目靠齊起始位置,最後一行項目靠齊終點位置
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         align-content:space-between;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • sapce-around:所有行平均分配到主軸線里,兩端保留一半的空間
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         align-content:space-around;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • stretch:沿着交叉軸
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         align-content:stretch;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px; //不要設置高度,不然無法拉伸
         background-color: green;
         margin: 1px;
     }

 

三、伸縮項目的屬性,單個設置排版

1、order:定義伸縮項目的排列順序。數值越小,排列越靠前,默認值為0。

  • 表達式 order: integer;
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item4 {
         order: -1;
     }
    
     #item5 {
         order: -2;
     }

2、flex-grow:定義伸縮項目的放大比例,默認值為0,表示即使存在剩餘空間,也不放大。

  • 表達式 flex-grow: number;
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item2 {
         flex-grow: 1; //空間不足,item2不會放大
     }
    
     #item4 {
         flex-grow: 1; //item4放大填滿剩餘空間
     }

3、flex-shrink:定義伸縮項目的收縮比例,默認值為1。

  • 表達式 flex-shrink: numer;
    .flex-container {
         display: flex;
         flex-flow: row nowrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item4 {
         flex-shrink:3; //單行,空間有限,item4縮小為原來的1/3
     }
    
     #item5 {
         flex-shrink:4;  //單行,空間有限,item5縮小為原來的1/5
    }

4、flex-basis:定義伸縮項目的基準值,剩餘空間按照比例進行伸縮,默認auto。

  •  auto
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item5 {
         flex-basis:auto;
     }

            

  • flex-basis: length 
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item5 {
         flex-basis:200px;
     }

5、flex:是flex-grow、flex-shrink、flex-basis的縮寫,默認值 0 1 auto。

  • none: 不設置
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item2 {
         flex: none; /* 等同於 flex: 0 0 auto */
     }

  • flex-grow flex-shrink flex-basis: 設置放大或縮小或基準線
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item2 {
         flex: 1; /* 等同於 flex: 1 1 auto 或者 等同於 flex: auto*/
     }

6、align-self:用來設置伸縮項目在交叉軸的對齊方式。

  • auto:自動對齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item3 {
         align-self: auto;
     }

  • flex-start: 向交叉軸的開始位置對齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item3 {
         align-self: flex-start;
     }

  • flex-end: 向交叉軸的結束位置對齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item3 {
         align-self: flex-end;
     }

  • center: 向交叉軸的中間位置對齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item3 {
         align-self: center;
     }

  • baseline:向交叉軸的基準線對齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item1 {
         align-self: baseline;
         margin-top: 50px;
     }
    
     #item2 {
         align-self: baseline;
     }

  • stretch: 在交叉軸拉伸填滿伸縮容器
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item1 {
         align-self: stretch;
     }
    
     #item2 {
         align-self: stretch;
     }
    
     #item3 {
         align-self: stretch;
     }

 

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

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

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

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

分類
發燒車訊

蘋果的第一個汽車專利由BAE公司授權 像坦克

儘管蘋果公司一直三緘其口,但是對於傳聞中的蘋果電動汽車項目,已經快成為了公開的秘密。現在,蘋果的首個關於汽車技術的專利也被人曝光,不過看起來與我們期望的距離似乎有點遙遠。  
  近日,美國專利商標局通過了一批蘋果公司的新專利,其中一項專利顯示了一種採用履帶以及軌槽設計的交通工具草圖。這項專利其實是兩個貨箱之間的接駁原理,駕駛員可以在極端寒冷空氣條件下,直接,通過加熱裝置,控制第一個車廂的轉向構件及包括一個連接機制的第二個車廂。   據悉,這項專利由瑞典軍用坦克製造商BAE公司授權給蘋果,因此至少目前來看肯定不會被使用在普通的消費和商業領域。   文章來源:騰訊數碼

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

【其他文章推薦】

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

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

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

分類
發燒車訊

【集合系列】- 深入淺出的分析TreeMap

一、摘要

在集合系列的第一章,咱們了解到,Map的實現類有HashMap、LinkedHashMap、TreeMap、IdentityHashMap、WeakHashMap、Hashtable、Properties等等。

本文主要從數據結構和算法層面,探討TreeMap的實現。

二、簡介

Java TreeMap實現了SortedMap接口,也就是說會按照key的大小順序對Map中的元素進行排序,key大小的評判可以通過其本身的自然順序(natural ordering),也可以通過構造時傳入的比較器(Comparator)。

TreeMap底層通過紅黑樹(Red-Black tree)實現,所以要了解TreeMap就必須對紅黑樹有一定的了解,在《集合系列》文章中,如果你已經讀過紅黑樹的講解,其實本文要講解的TreeMap,跟其大同小異。

紅黑樹又稱紅-黑二叉樹,它首先是一顆二叉樹,它具有二叉樹所有的特性。同時紅黑樹更是一顆自平衡的排序二叉樹。

對於一棵有效的紅黑樹二叉樹,主要有以下規則:

  • 1、每個節點要麼是紅色,要麼是黑色,但根節點永遠是黑色的;
  • 2、每個紅色節點的兩個子節點一定都是黑色;
  • 3、紅色節點不能連續(也即是,紅色節點的孩子和父親都不能是紅色);
  • 4、從任一節點到其子樹中每個恭弘=叶 恭弘子節點的路徑都包含相同數量的黑色節點;
  • 5、所有的恭弘=叶 恭弘節點都是是黑色的(注意這裏說恭弘=叶 恭弘子節點其實是上圖中的 NIL 節點);

這些約束強制了紅黑樹的關鍵性質:從根到恭弘=叶 恭弘子的最長的可能路徑不多於最短的可能路徑的兩倍長。結果是這棵樹大致上是平衡的。因為操作比如插入、刪除和查找某個值的最壞情況時間都要求與樹的高度成比例,這個在高度上的理論上限允許紅黑樹在最壞情況下都是高效的,而不同於普通的二叉查找樹。所以紅黑樹它是複雜而高效的,其檢索效率O(log n)。下圖為一顆典型的紅黑二叉樹。

在樹的結構發生改變時(插入或者刪除操作),往往會破壞上述條件3或條件4,需要通過調整使得查找樹重新滿足紅黑樹的條件。

調整方式主要有:左旋、右旋和顏色轉換!

2.1、左旋

左旋的過程是將x的右子樹繞x逆時針旋轉,使得x的右子樹成為x的父親,同時修改相關節點的引用。旋轉之後,二叉查找樹的屬性仍然滿足。

2.2、右旋

右旋的過程是將x的左子樹繞x順時針旋轉,使得x的左子樹成為x的父親,同時修改相關節點的引用。旋轉之後,二叉查找樹的屬性仍然滿足。

2.3、顏色轉換

顏色轉換的過程是將紅色節點轉換為黑色節點,或者將黑色節點轉換為紅色節點,以滿足紅黑樹二叉樹的規則!

三、常用方法介紹

3.1、get方法

get方法根據指定的key值返回對應的value,該方法調用了getEntry(Object key)得到相應的entry,然後返回entry.value

算法思想是根據key的自然順序(或者比較器順序)對二叉查找樹進行查找,直到找到滿足k.compareTo(p.key) == 0entry

源碼如下:

final Entry<K,V> getEntry(Object key) {
        //如果傳入比較器,通過getEntryUsingComparator方法獲取元素
        if (comparator != null)
            return getEntryUsingComparator(key);
        //不允許key值為null
        if (key == null)
            throw new NullPointerException();
        //使用元素的自然順序
            Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = k.compareTo(p.key);
            if (cmp < 0)
                //向左找
                p = p.left;
            else if (cmp > 0)
                //向右找
                p = p.right;
            else
                return p;
        }
        return null;
}

如果傳入比較器,通過getEntryUsingComparator方法獲取元素

final Entry<K,V> getEntryUsingComparator(Object key) {
            K k = (K) key;
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            Entry<K,V> p = root;
            while (p != null) {
                //通過比較器順序,獲取元素
                int cmp = cpr.compare(k, p.key);
                if (cmp < 0)
                    p = p.left;
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }
        }
        return null;
}

測試用例:

public static void main(String[] args) {
        Map initMap = new TreeMap();
        initMap.put("4", "d");
        initMap.put("3", "c");
        initMap.put("1", "a");
        initMap.put("2", "b");
        //默認自然排序,key為升序
        System.out.println("默認 排序結果:" + initMap.toString());

        //自定義排序,在TreeMap初始化階段傳入Comparator 內部對象
        Map comparatorMap = new TreeMap<String, String>(new Comparator<String>() {

            @Override
            public int compare(String o1, String o2){
                //根據key比較大小,採用倒敘,以大到小排序
                return o2.compareTo(o1);
            }
        });
        comparatorMap.put("4", "d");
        comparatorMap.put("3", "c");
        comparatorMap.put("1", "a");
        comparatorMap.put("2", "b");

        System.out.println("自定義 排序結果:" + comparatorMap.toString());
}

輸出結果:

默認 排序結果:{1=a, 2=b, 3=c, 4=d}
自定義 排序結果:{4=d, 3=c, 2=b, 1=a}

3.2、put方法

put方法是將指定的key, value對添加到map里。該方法首先會對map做一次查找,看是否包含該元組,如果已經包含則直接返回,查找過程類似於getEntry()方法;如果沒有找到則會在紅黑樹中插入新的entry,如果插入之後破壞了紅黑樹的約束,還需要進行調整(旋轉,改變某些節點的顏色)。

源碼如下:

public V put(K key, V value) {
        Entry<K,V> t = root;
        //如果紅黑樹根部為空,直接插入
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        //如果比較器,通過比較器順序,找到插入位置
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            //通過自然順序,找到插入位置
            if (key == null)
                throw new NullPointerException();
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        //創建並插入新的entry
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        //紅黑樹調整函數
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
}

紅黑樹調整函數fixAfterInsertion(Entry<K,V> x)

private void fixAfterInsertion(Entry<K,V> x) {
    x.color = RED;
    while (x != null && x != root && x.parent.color == RED) {
        //判斷x是否在樹的左邊,還是右邊
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
            //如果x的父親的父親的右子樹是紅色,違反了紅色節點不能連續
            if (colorOf(y) == RED) {
                //進行顏色調整,以滿足紅色節點不能連續規則
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK); 
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                //如果x的父親的右子樹等於自己,那麼需要進行左旋轉
                if (x == rightOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateLeft(x);
                }
                setColor(parentOf(x), BLACK);  
                setColor(parentOf(parentOf(x)), RED);
                rotateRight(parentOf(parentOf(x)));
            }
        } else {
            //跟上面的流程正好相反
            //獲取x的父親的父親的左子樹節點
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            //如果y是紅色節點,違反了紅色節點不能連續
            if (colorOf(y) == RED) {
                //進行顏色轉換
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                //如果x的父親的左子樹等於自己,那麼需要進行右旋轉
                if (x == leftOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateRight(x);
                }
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateLeft(parentOf(parentOf(x)));
            }
        }
    }
    //根節點一定為黑色
    root.color = BLACK;
}

上述代碼的插入流程:

  • 1、首先在紅黑樹上找到合適的位置;
  • 2、然後創建新的entry並插入;
  • 3、通過函數fixAfterInsertion(),對某些節點進行旋轉、改變某些節點的顏色,進行調整;

調整圖解:

3.3、remove方法

remove的作用是刪除key值對應的entry,該方法首先通過上文中提到的getEntry(Object key)方法找到 key 值對應的 entry,然後調用deleteEntry(Entry<K,V> entry)刪除對應的 entry。由於刪除操作會改變紅黑樹的結構,有可能破壞紅黑樹的約束,因此有可能要進行調整。

源碼如下:

public V remove(Object key) {
        Entry<K,V> p = getEntry(key);
        if (p == null)
            return null;

        V oldValue = p.value;
        deleteEntry(p);
        return oldValue;
}

刪除函數 deleteEntry()

private void deleteEntry(Entry<K,V> p) {
    modCount++;
    size--;
    if (p.left != null && p.right != null) {// 刪除點p的左右子樹都非空。
        Entry<K,V> s = successor(p);// 後繼
        p.key = s.key;
        p.value = s.value;
        p = s;
    }
    Entry<K,V> replacement = (p.left != null ? p.left : p.right);
    if (replacement != null) {// 刪除點p只有一棵子樹非空。
        replacement.parent = p.parent;
        if (p.parent == null)
            root = replacement;
        else if (p == p.parent.left)
            p.parent.left  = replacement;
        else
            p.parent.right = replacement;
        p.left = p.right = p.parent = null;
        if (p.color == BLACK)
            fixAfterDeletion(replacement);// 調整
    } else if (p.parent == null) {
        root = null;
    } else { //刪除點p的左右子樹都為空
        if (p.color == BLACK)
            fixAfterDeletion(p);// 調整
        if (p.parent != null) {
            if (p == p.parent.left)
                p.parent.left = null;
            else if (p == p.parent.right)
                p.parent.right = null;
            p.parent = null;
        }
    }
}

刪除后調整函數fixAfterDeletion()的具體代碼如下:

private void fixAfterDeletion(Entry<K,V> x) {
    while (x != root && colorOf(x) == BLACK) {
        //判斷當前刪除的元素,是在x父親的左邊還是右邊
        if (x == leftOf(parentOf(x))) {
            Entry<K,V> sib = rightOf(parentOf(x));
            //判斷x的父親的右子樹,是紅色還是黑色節點
            if (colorOf(sib) == RED) {
                //進行顏色轉換
                setColor(sib, BLACK);
                setColor(parentOf(x), RED);
                rotateLeft(parentOf(x));
                sib = rightOf(parentOf(x));
            }
            //x的父親的右子樹的左邊是黑色節點,右邊也是黑色節點
            if (colorOf(leftOf(sib))  == BLACK &&
                colorOf(rightOf(sib)) == BLACK) {
                //設置x的父親的右子樹為紅色節點,將x的父親賦值給x
                setColor(sib, RED);
                x = parentOf(x);
            } else {
                //x的父親的右子樹的左邊是紅色節點,右邊也是黑色節點
                if (colorOf(rightOf(sib)) == BLACK) {
                    //x的父親的右子樹的左邊進行顏色調整,右旋調整
                    setColor(leftOf(sib), BLACK);
                    setColor(sib, RED);
                    rotateRight(sib);
                    sib = rightOf(parentOf(x));
                }
                //對x進行左旋,顏色調整
                setColor(sib, colorOf(parentOf(x)));
                setColor(parentOf(x), BLACK);
                setColor(rightOf(sib), BLACK);
                rotateLeft(parentOf(x));
                x = root;
            }
        } else { // 跟前四種情況對稱
            Entry<K,V> sib = leftOf(parentOf(x));
            if (colorOf(sib) == RED) {
                setColor(sib, BLACK);
                setColor(parentOf(x), RED);
                rotateRight(parentOf(x));
                sib = leftOf(parentOf(x));
            }
            if (colorOf(rightOf(sib)) == BLACK &&
                colorOf(leftOf(sib)) == BLACK) {
                setColor(sib, RED);
                x = parentOf(x);
            } else {
                if (colorOf(leftOf(sib)) == BLACK) {
                    setColor(rightOf(sib), BLACK);
                    setColor(sib, RED);
                    rotateLeft(sib);
                    sib = leftOf(parentOf(x));
                }
                setColor(sib, colorOf(parentOf(x)));
                setColor(parentOf(x), BLACK);
                setColor(leftOf(sib), BLACK);
                rotateRight(parentOf(x));
                x = root;
            }
        }
    }
    setColor(x, BLACK);
}

上述代碼的刪除流程:

  • 1、首先在紅黑樹上找到合適的位置;
  • 2、然後刪除entry;
  • 3、通過函數fixAfterDeletion(),對某些節點進行旋轉、改變某些節點的顏色,進行調整;

四、總結

TreeMap 默認是按鍵值的升序排序,如果需要自定義排序,可以通過new Comparator構造參數,重寫compare方法,進行自定義比較。

以上,主要是對 java 集合中的 TreeMap 做了寫講解,如果有理解不當之處,歡迎指正。

五、參考

1、JDK1.7&JDK1.8 源碼
2、
2、

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

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

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

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

分類
發燒車訊

ThreadLocal深度解析和應用示例

開篇明意

  ThreadLocal是JDK包提供的線程本地變量,如果創建了ThreadLocal<T>變量,那麼訪問這個變量的每個線程都會有這個變量的一個副本,在實際多線程操作的時候,操作的是自己本地內存中的變量,從而規避了線程安全問題。

  ThreadLocal很容易讓人望文生義,想當然地認為是一個“本地線程”。其實,ThreadLocal並不是一個Thread,而是Thread的一個局部變量,也許把它命名ThreadLocalVariable更容易讓人理解一些。

  來看看官方的定義:這個類提供線程局部變量。這些變量與正常的變量不同,每個線程訪問一個(通過它的get或set方法)都有它自己的、獨立初始化的變量副本。ThreadLocal實例通常是類中的私有靜態字段,希望將狀態與線程關聯(例如,用戶ID或事務ID)。

源碼解析

  1.核心方法之   set(T t)

 1     /**
 2      * Sets the current thread's copy of this thread-local variable
 3      * to the specified value.  Most subclasses will have no need to
 4      * override this method, relying solely on the {@link #initialValue}
 5      * method to set the values of thread-locals.
 6      *
 7      * @param value the value to be stored in the current thread's copy of
 8      *        this thread-local.
 9      */
10     public void set(T value) {
11         Thread t = Thread.currentThread();
12         ThreadLocalMap map = getMap(t);
13         if (map != null)
14             map.set(this, value);
15         else
16             createMap(t, value);
17     }

解析:

  當調用ThreadLocal的set(T t)的時候,代碼首先會獲取當前線程的 ThreadLocalMap(ThreadLocal中的靜態內部類,同時也作為Thread的成員變量存在,後面會進一步了解ThreadLocalMap),如果ThreadLocalMap存在,將ThreadLocal作為map的key,要保存的值作為value來put進map中(如果map不存在就先創建map,然後再進行put);

  2.核心方法值 get()

/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
       Thread t = Thread.currentThread();
       ThreadLocalMap map = getMap(t);        //此處和set方法一致,也是通過當前線程獲取對應的成員變量ThreadLocalMap,map中存放的是Entry(ThreadLocalMap的內部類(繼承了弱引用))
    
if (map != null) {
      ThreadLocalMap.Entry e = map.getEntry(this);
      if (e != null) {
        @SuppressWarnings("unchecked")
        T result = (T)e.value;
        return result;
      }
    }
    return setInitialValue();
}

解析:

  剛才把對象放到set到map中,現在根據key將其取出來,值得注意的是這裏的map裏面存的可不是鍵值對,而是繼承了WeakReference<ThreadLocal<?>> 的Entry對象,關於ThreadLocalMap.Entry類,後面會有更加詳盡的講述。

核心方法之  remove()

    /**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

解析:

  通過getMap方法獲取Thread中的成員變量ThreadLocalMap,在map中移除對應的ThreadLocal,由於ThreadLocal(key)是一種弱引用,弱引用中key為空,gc會回收變量value,看一下核心的m.remove(this);方法

        /**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1); //定義Entry在數組中的標號 for (Entry e = tab[i];              //通過循環的方式remove掉Thread中所有的Entry
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {   
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        } 

 

 

 

靈魂提問

  問:threadlocal是做什麼用的,用在哪些場景當中?  

    結合官方對ThreadLocal類的定義,threadLocal主要滿足某些變量或者示例是線程隔離的,但是在相同線程的多個類或者方法中都能使用的到,並且當線程結束時該變量也應該銷毀。通俗點講:ThreadLocal保證每個線程有自己的數據副本,當線程結束后可  以獨立回收。由於ThreadLocal的特性,同一線程在某地方進行設置,在隨後的任意地方都可以獲取到。從而可以用來保存線程上下文信息。常用的比如每個請求怎麼把一串後續關聯起來,就可以用ThreadLocal進行set,在後續的任意需要記錄日誌的方法裏面進行get獲取到請求id,從而把整個請求串起來。     使用場景有很多,比如:

  • 基於用戶請求線程的數據隔離(每次請求都綁定userId,userId的值存在於ThreadLoca中)
  • 跟蹤一個請求,從接收請求,處理到返回的整個流程,有沒有好的辦法   思考:微服務中的鏈路追蹤是否利用了ThreadLocal特性
  • 數據庫的讀寫分離
  • 還有比如Spring的事務管理,用ThreadLocal存儲Connection,從而各個DAO可以獲取同一Connection,可以進行事務回滾,提交等操作。

    
問:如果我啟動另外一個線程。那麼在主線程設置的Threadlocal值能被子線程拿到嗎?     原始的ThreadLocal是不具有繼承(或者說傳遞)特性的     
問:那該如何解決ThreadLocal無法傳遞的問題呢?     用ThreadLocal的子類 InheritableThreadLocal,InheritableThreadLocal是具有傳遞性的

  /**
  * 重寫Threadlocal類中的getMap方法,在原Threadlocal中是返回
  * t.theadLocals,而在這麼卻是返回了inheritableThreadLocals,因為
  * Thread類中也有一個要保存父子傳遞的變量
  */ ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals; }
    /**  * 同理,在創建ThreadLocalMap的時候不是給t.threadlocal賦值  *而是給inheritableThreadLocals變量賦值  *  */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

解析:因為InheritableThreadLocal重寫了ThreadLocal中的getMap 和createMap方法,這兩個方法維護的是Thread中的另外一個成員變量  inheritableThreadLocals,線程在創建的時候回複製inheritableThreadLocals中的值 ;

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
  //Thread類中維護的成員變量,ThreadLocal會維護該變量
ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */
//Thread中維護的成員變量 ,
InheritableThreadLocal 中維護該變量
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;


 

//Thread init方法中的關鍵代碼,簡單來說是將父類中inheritableThreadLocals中的值拷貝到當前線程的inheritableThreadLocals中(淺拷貝,拷貝的是value的地址引用)
 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

總結

  • ThreadLocal類封裝了getMap()、Set()、Get()、Remove()4個核心方法。
  • 通過getMap()獲取每個子線程Thread持有自己的ThreadLocalMap實例, 因此它們是不存在併發競爭的。可以理解為每個線程有自己的變量副本。
  • ThreadLocalMap中Entry[]數組存儲數據,初始化長度16,後續每次都是1.5倍擴容。主線程中定義了幾個ThreadLocal變量,Entry[]才有幾個key。
  • Entry的key是對ThreadLocal的弱引用,當拋棄掉ThreadLocal對象時,垃圾收集器會忽略這個key的引用而清理掉ThreadLocal對象, 防止了內存泄漏。

    tips:上面四個總結來源於其他技術博客,個人認為總結的比較合理所以直接摘抄過來了

拓展:

  ThreadLocal在線程池中使用容易發生的問題: 內存泄漏,先看下圖

  

  每個thread中都存在一個map, map的類型是ThreadLocal.ThreadLocalMap. Map中的key為一個threadlocal實例. 這個Map的確使用了弱引用,不過弱引用只是針對key. 每個key都弱引用指向threadlocal. 當把threadlocal實例置為null以後,沒有任何強引用指向threadlocal實例,所以threadlocal將會被gc回收. 但是,我們的value卻不能回收,因為存在一條從current thread連接過來的強引用. 只有當前thread結束以後, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value將全部被GC回收.

  所以得出一個結論就是只要這個線程對象被gc回收,就不會出現內存泄露,但在threadLocal設為null和線程結束這段時間不會被回收的,就發生了我們認為的內存泄露。其實這是一個對概念理解的不一致,也沒什麼好爭論的。最要命的是線程對象不被回收的情況,這就發生了真正意義上的內存泄露。比如使用線程池的時候,線程結束是不會銷毀的,會再次使用的。就可能出現內存泄露。  

  PS.Java為了最小化減少內存泄露的可能性和影響,在ThreadLocal的get,set的時候都會清除線程Map里所有key為null的value。所以最怕的情況就是,threadLocal對象設null了,開始發生“內存泄露”,然後使用線程池,這個線程結束,線程放回線程池中不銷毀,這個線程一直不被使用,或者分配使用了又不再調用get,set方法,那麼這個期間就會發生真正的內存泄露。 

 

  1. JVM利用設置ThreadLocalMap的Key為弱引用,來避免內存泄露。
  2. JVM利用調用remove、get、set方法的時候,回收弱引用。
  3. 當ThreadLocal存儲很多Key為null的Entry的時候,而不再去調用remove、get、set方法,那麼將導致內存泄漏。
  4. 當使用static ThreadLocal的時候,延長ThreadLocal的生命周期,那也可能導致內存泄漏。因為,static變量在類未加載的時候,它就已經加載,當線程結束的時候,static變量不一定會回收。那麼,比起普通成員變量使用的時候才加載,static的生命周期加長將更容易導致內存泄漏危機。

 

  參考鏈接:

 

在線程池中使用ThreadLocal

通過上面的分析可以知道InheritableThreadLocal是通過Thread()的inint方法實現父子之間的傳遞的,但是線程池是統一創建線程並實現復用的,這樣就好導致下面的問題發生:

  •   線程不會銷毀,ThreadLocal也不會被銷毀,這樣會導致ThreadLoca會隨着Thread的復用而復用
  •   子線程無法通過InheritableThreadLocal實現傳遞性(因為沒有單獨的調用Thread的Init方法進行map的複製),子線程中get到的是null或者是其他線程復用的錯亂值(疑問點還沒搞清楚原因,後續補充::在異步線程中會出現null的情況,同步線程不會出現)     

    ps:線程池中的線程是什麼時候創建的?

 

  解決方案:

    下面兩個鏈接有詳細的說明,我就不重複寫了,後續我會將本文進一般優化並添加一些例子來幫助說明,歡迎收藏,關於本文有不同的意見歡迎評論指正……

    

    

 

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

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

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

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

分類
發燒車訊

[NLP] Adaptive Softmax

1. Overview

Adaptive softmax算法在鏈接1中的論文中提出,該算法目的是為了提高softmax函數的運算效率,適用於一些具有非常大詞彙量的神經網絡。

在NLP的大部分任務中,都會用到softmax,但是對於詞彙量非常大的任務,每次進行完全的softmax會有非常大的計算量,很耗時(每次預測一個token都需要O(|V|)的時間複雜度)。

所以paper中提出adaptive softmax來提升softmax的運算效率。

1) 該算法的提出利用到了單詞分佈不均衡的特點(unbalanced word distribution)來形成clusters, 這樣在計算softmax時可以避免對詞彙量大小的線性依賴關係,降低計算時間;

2) 另外通過結合現代架構和矩陣乘積操作的特點,通過使其更適合GPU單元的方式進一步加速計算。

2. Introduction

2.1 一般降低softmax計算複雜度的兩種方式

1) 考慮原始分佈:近似原始概率分佈或近似原始概率分佈的子集

2) 構造近似模型,但是產生準確的概率分佈。比如:hierarchical softmax.

(上述兩個方法可以大致區分為:一是準確的模型產生近似概率分佈,二是用近似模型產生準確的概率分佈)

2.2 本文貢獻點

paper中主要採用的上述(2)的方式,主要借鑒的是hierarchical softmax和一些變型。與以往工作的不同在於,本paper結合了GPU的特點進行了計算加速。主要

1. 定義了一種可以生成 近似層次模型 的策略,該策略同時考慮了矩陣乘積運算的計算時間。這個計算時間並非與矩陣的維數是簡單線性關係。

2. 在近來的GPU上對該模型進行了實證分析。在所提出的優化算法中也包含了對實際計算時間模型的定義。

3. 與一般的softmax相比,有2× 到 10× 的加速。這等價於在同等算力限制下提高了準確度。另外一點非常重要,該paper所提出的這種高效計算的方法與一些通過并行提高效率的方法相比,在給定一定量的training data下,對準確度並沒有損失。

3. Adaptive Softmax 

3.1 矩陣乘積運算的計算時間模型

hidden states: (B × d); Word representation: (d × k); 這兩個矩陣的乘積運算時間: g(k, B)。其中B是batch size, d: hidden layer 的size, k vectors的數量。

(1) 固定B,d, 探究k值大小與g(k)的關係:

在K40、 M40兩種GPU model中進行實驗,發現k在大約為$k_0 \approx 50$的範圍之內,g(k)是常數級別的,在$k > k_0$后,呈線性依賴關係。其計算模型為:

同樣的,關於B的計算時間的函數也呈現這樣的關係。這可以表示,矩陣相乘時,當其中一個維度很小時,矩陣乘法是低效的。

如何理解呢?比如對於 $k_1 = 2^2$, $k_2 = 2^4$,它們均在同樣的常量時間內完成運算,那麼顯然對於$k1$量級有算力被浪費了。

那麼這也說明,一些words的層次結構,其每個節點只具有少量的子節點(比如huffman 編碼的tree),是次優的。

(2) 探究batch size B值大小與g(B)的關係:

同樣的,當探究計算用時與batch size $B$,的關係時,發現在矩陣乘法運算中,其中一個維度很小時計算並不高效。因此會導致:

(i) 在層次結構中,因為每個節點只有少數一些孩子節點 (比如Huffman 樹),其計算效率是次優的;

(ii) 對於按照詞頻劃分clusters后,那些只包含稀有詞的cluster,被選擇的概率為p, 並且其batch size也縮減為 $p B$,也會出現以上所述的低效的矩陣乘積運算問題。

(3) 本文的計算模型

所以paper中綜合考慮了k與B,提出了下面的計算模型:

對於Adaptive Softmax, 其核心思想是根據詞頻大小,將不同詞頻區間的詞分為不同的clusters,按照詞頻高的優先訪問的原則,來對每個cluster整體,進而對cluster中的每個詞進行訪問,進行softmax操作。

那麼paper中首先以2個clusters為例,對該模型進行講解,之後推廣到多個clusters的一般情況。

3.2 Two-Clusters Case

根據Zipf定律,在Penn TreeBank中 20%的詞,可以覆蓋一般文檔中87%的出現過的詞。

直觀地,可以將字典$V$中的詞分為$V_h$、$V_t$兩個部分。其中$V_h$表示高頻詞集合(head),$V_t$表示低頻詞集合(tail)。一般地,$|V_h| << |V_t|$,$P(V_h) >> P(V_t)$。

(1) Clusters 的組織

直觀地可以想到對於這兩個clusters有兩種組織方式:(i) 兩個clusters都是2層的樹結構,(ii) 將head部分用一個短列表保存,對tail部分用二層樹結構。何種方式更優可以就其計算效率和準確率進行比較:

在準確率方面,(i) 較之 (ii) 一般有 5 ~ 10 %的下降。原因如下:

對於屬於cluster $c$的每個詞$w$的概率計算為: 

若採用(i):  $P(c | h) * P(w | c, h)$

若採用(ii): 對於高頻詞head部分可以直接計算獲得 $P(w | h)$,其更為簡單直接。

因此,除非(i)(ii)的計算時間有非常大的差別,否則選擇(ii)的組織方式。

(2) 對計算時間的縮短

 

圖2. Two clusters示意圖

如圖2所示,$k_h = |V_h|, k_t = k – k_h, p_t = 1 – p_h$

(i) 對於第一層:對於batch size 為B的輸入,在head中對$k_h$個高頻詞以及1個單位的二層cluster的向量(加陰影部分),共$k_h + 1$個向量,做softmax,

這樣占文檔中$p_h$的詞可以基本確定下來,在head 的 short list 部分找到對應的單詞;

而如果與陰影部分進行softmax值更大,表明待預測的詞,在第二層的低頻詞部分;

第一層的計算開銷為:$g(k_h + 1, B)$

(ii) 在short list 中確定了 $p_h × B$ 的內容后,還剩下 $p_t B$的輸入需要在tail中繼續進行softmax來確定所預測的詞。

第二層中需要對$k_t$個向量做softmax;

第二層的計算開銷為:$g(k_t, pB)$

綜上,總的計算開銷為:$$C = g(k_h + 1, B) + g(k_t, p_t B)$$

相比於最初的softmax,分母中需要對詞典中的每個詞的向量逐一進行計算;採用adaptive softmax可以使得該過程分為兩部分進行,每部分只需對該部分所包含的詞的向量進行計算即可,降低了計算用時。

論文中的Figure 2显示了,對於$k_h$的合理劃分,最高可以實現相較於完全softmax的5x加速。

(3) 不同cluster的分類器能力不同

由於每個cluster基本是獨立計算的,它們並不要求有相同的能力(capacity)。

一般可以為高頻詞的cluster予以更高的capacity,而可以適當降低詞頻詞的cluster的capacity。因為低頻詞在文檔中很少出現,因此對其capacity的適當降低並不會非常影響總體的性能。

在本文中,採取了為不同的clusters共享隱層,通過加映射矩陣的方式降低分類器的輸入大小。一般的,tail部分的映射矩陣將其大小從$d$維縮小到$d_t = d / 4$維。

3.3 一般性情況

在3.2中以分為2個cluster為例,介紹了adaptive softmax如何組織並計算的,這裏將其拓展為一般情況,即可以分為多個clusters時的處理方式。

假設將整個詞典分為一個head部分和J個詞頻詞cluster部分,那麼有:

$$V = V_h \cup V_1 … V_j, V_i \cap V_j = \phi$$

其中$V_h$在第一層,其餘均在第二層,如圖Figure 3所示。

每個cluster中的詞向量的數目為$k_i = |V_i|$,單詞$w$屬於某個cluster的概率為:$p_i = \sum_{w \in V_i} p(w)$.

那麼,

計算head部分的時間開銷為:$C_h = g(J + k_h, B)$

計算每個二層cluster的時間開銷為:$\forall_i, C_i = g(k_i, p_i B)$

那麼總的時間開銷為:$C = g(J + k_h, B) + \sum_i g(k_i, p_i B) \tag8$

 

回顧公式(7):

$$g(k,B) = max(c + \lambda k_0 B_0, c + \lambda k B) \tag7$$

這裡有兩部分,一個是常數部分$c + \lambda k_0 B_0$,一個是線性變換部分$c + \lambda k B$。

通過3.1可知,在常數部分GPU的算力並未得到充分利用,因此應盡量避免落入該部分。那麼需要滿足:

$kB \geq k_0B_0$,這樣在求max時,可以利用以上的第二部分。

將(8)式代入(7)的第二部分得:

 

那麼接下來的目標就是根據(10) 以最小化時間開銷C. 

在(10)中,$J ,B$是固定的,那麼可以重點關注$\sum_i p_ik_i$與$k_h$。

(1) $\sum_i p_ik_i$

假設$p_{i + j} = p_i + p_j$, 那麼 $p_jk_j = (p_{i + j} – p_i) k_j$

$p_ik_i + p_jk_j = p_i(k_i – k_j) + p_{i + j}k_j \tag{11}$  

假設$k_i > k_j, p_{i + j}$, $k_j$均固定,那麼(11)中只有$p_i$可變,可以通過減小$p_i$的方式使(11)式減小。=> 也即$k_i > k_j$ 且 $p_i$盡量小,即包含越多詞的cluster,其概率越小。

(2) $k_h$

減小$k_h$可以使(10)式減小。 => 即為可以使高頻詞所在的cluster包含更少的詞。

綜上,給定了cluster的個數J,與batch size B,可以通過給大的cluster分配以更小的概率降低時間開銷C.

另外paper中講到可以用動態規劃的方法,在給定了J后,對$k_i$的大小進行劃分。

cluster的劃分個數$J$。paper中實驗了採用不同的J所導致的不同計算時間。如圖Figure 4所示。雖然在$10 ~ 15$區間內效果最好,但$ > 5$之後效果沒有非常明顯的提升。所以文中建議採用2~5個clusters。

實驗主要在text8, europarl, one billion word三個數據集上做的,通過比較ppl發現,adaptive softmax仍能保持低的ppl,並且相比於原模型有2x到10x的提速。

  

參考鏈接:

1. Efficient softmax approximation for GPUs: 

Some of us get dipped in flat, some in satin, some in gloss. But every once in a while you find someone who’s iridescent, and when you do, nothing will ever compare. ~ Flipped

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

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

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

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

分類
發燒車訊

Spring框架學習總結(上)

目錄

@

1、Spring的概述

在學習SSM框架中,我建議初學者最好先學Spring框架,其次mybatis接着springMVC,先學mybatis當然也是可以的,今天我們就以絕對優雅的姿態闖進Spring世界,系好安全帶,準備好了嗎,出發了哦!!!咳咳….平時開發接觸最多的估計就是IOC容器,它可以裝載bean(所謂的bean也就是我們java中的類,當然也包括servicedao裏面),有了這個機制,我們就不用在每次使用這個類的時候為它初始化,很少看到鍵字new。另外spring的aop,事務管理等等都是我們經常用到的,可見Spring的尤為重要的作用Spring的核心是控制反轉(IoC)和面向切面(AOP)

1.1什麼是Spring

肯定有熊dei會問SE/EE開發的一站式框架所謂的一站式是什麼意思,(哼,人類,我早就猜到你會問了)
所謂一站式框架指的是有EE開發的每一層解決方案。

WEB層 :SpringMVC

Service層 :Spring的Bean管理,Spring聲明式事務

DAO層 :Spring的Jdbc模板,Spring的ORM模塊

1.2為什麼學習Spring

俗話說,人狠話不多(兄嘚看圖)

1.3Spring的版本

Spring3.x、Spring4.x和Spring5.x

1.4Spring的體繫結構

正所謂,人狠話不多(兄嘚看圖)

2、Spring的入門(IOC)

2.1什麼IOC

一說起IOC我就想起了武哥對IOC的理解的幾個例子,可謂通俗易懂,非常適合剛入門Spring的兄嘚!有興趣的可以去了解了解武哥,武哥博客:https://blog.csdn.net/eson_15

IOC(Inverse of Control):控制反轉,也可以稱為依賴倒置。
控制反轉:將對象的創建權反轉給(交給)Spring。

所謂依賴,從程序的角度看,就是比如A要調用B的方法,那麼A就依賴於B,反正A要用到B,則A依

賴於B。所謂倒置,你必須理解如果不倒置,會怎麼著,因為A必須要有B,才可以調用B,如果不倒

置,意思就是A主動獲取B的實例:B b = new B(),這就是最簡單的獲取B實例的方法(當然還有各種

設計模式可以幫助你去獲得B的實例,比如工廠、Locator等等),然後你就可以調用b對象了。所

以,不倒置,意味着A要主動獲取B,才能使用B;到了這裏,就應該明白了倒置的意思了。倒置就是

A要調用B的話,A並不需要主動獲取B,而是由其它人自動將B送上門來。

2.2通俗理解IOC

形象的舉例就是:
通常情況下,假如你有一天在家裡口渴了,要喝水,那麼你可以到你小區的小賣部去,告訴他們,你需要一瓶水,然後小賣部給你一瓶水!這本來沒有太大問題,關鍵是如果小賣部很遠,那麼你必須知道:從你家如何到小賣部;小賣部里是否有你需要的水;你還要考慮是否開着車去;等等等等,也許有太多的問題要考慮了。也就是說,為了一瓶水,你還可能需要依賴於車等等這些交通工具或別的工具,問題是不是變得複雜了?那麼如何解決這個問題呢?
解決這個問題的方法很簡單:小賣部提供送貨上門服務,凡是小賣部的會員,你只要告知小賣部你需要什麼,小賣部將主動把貨物給你送上門來!這樣一來,你只需要做兩件事情,你就可以活得更加輕鬆自在:
第一:向小賣部註冊為會員。
第二:告訴小賣部你需要什麼。

這和Spring的做法很類似!Spring就是小賣部,你就是A對象,水就是B對象
第一:在Spring中聲明一個類:A
第二:告訴Spring,A需要B

假設A是UserAction類,而B是UserService類

<bean id="userService" class="org.leadfar.service.UserService"/>
<bean id="documentService" class="org.leadfar.service.DocumentService"/>
<bean id="orgService" class="org.leadfar.service.OrgService"/>
 
<bean id="userAction" class="org.leadfar.web.UserAction">
     <property name="userService" ref="userService"/>
</bean>

在Spring這個商店(工廠)中,有很多對象/服務:userService,documentService,orgService,也有很多會員:userAction等等,聲明userAction需要userService即可,Spring將通過你給它提供的通道主動把userService送上門來,因此UserAction的代碼示例類似如下所示:

package org.leadfar.web;
public class UserAction{
     private UserService userService;
     public String login(){
          userService.valifyUser(xxx);
     }
     public void setUserService(UserService userService){
          this.userService = userService;
     }
}

在這段代碼裏面,你無需自己創建UserService對象(Spring作為背後無形的手,把UserService對象通過你定義的setUserService()方法把它主動送給了你,這就叫依賴注入!),當然咯,我們也可以使用註解來注入。Spring依賴注入的實現技術是:動態代理

2.3下載Spring的開發包以及解壓說明

官網下載:http://spring.io/
什麼?不會下載?what???
好吧,已打包好了QAQ:https://pan.baidu.com/s/18wyE-5SRWcCu12iPOX56pg
什麼?沒有網盤?what???
有事請燒香謝謝…

解壓之後,文件說明:
docs :Spring的開發規範和API
libs :Spring的開發的jar和源碼
schema :Spring的配置文件的約束

2.4創建web項目,引入jar包

2.5創建普通接口和實現類

創建普通接口,定義一個eat方法

package com.gx.sping;

public interface IUserDao {

    public void eat();
}

創建普通實現類

package com.gx.sping;

public class UserDaoimpl implements IUserDao {
  @Override
    public void eat() {
        // TODO Auto-generated method stub
        System.out.println(用戶eat了");
    }
}

2.6Spring的IOC底層實現原理

創建普通接口和類出現的問題:
如果底層的實現切換了,需要修改源代碼,能不能不修改程序源代碼對程序進行擴展?
重點來了,要想不改變源碼,Spring的IOC就能實現!如下圖:Spring的IOC底層實現

2.7將實現類交給Spring管理

1、在classpath下(也就是src)創建一個XML文件

2、文件名最好統一叫applicationContext.xml

3、其xml文件的內容頭為schema約束

4、約束文件位置在spring的解壓路徑下lspring-framework-4.2.4.RELEASE\docs\spring-framework-reference\html\xsd-configuration.htm

5、不要求xml文件的內容頭能夠背出來,但要了解的是你要知道它是怎麼來的

6、xml文件的內容頭添加后,將實現類交給Spring管理

applicationContext.xml配置文件如下

<?xml version="1.0" encoding="UTF-8"?>
<beans 
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd">

 <!-- 實現類UserDaoimpl交給Spring管理 -->
     <bean id="IuserDao" class="com.gx.Ioc.UserDaoimpl" ></bean>
</beans>

2.8編寫測試類

package com.gx.Ioc;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpingDemo1 {
    @Test
    public void demo11() {
        // 面向接口傳統方式
        UserDaoimpl userdao = new UserDaoimpl();
        userdao.eat();
    }
       //Spring的bean管理方式
    @Test
    public void demo22() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        IUserDao userdao = (IUserDao) applicationContext.getBean("IuserDao");
        userdao.eat();

    }

}

兄嘚,如果測試不成功最好看看二者是否對應!!!

兄dei,到這裏,Spring的入門(IOC)算是入門了,是不是覺得很有成就感啊?

拉倒吧! 我都不好意思說了.(兄dei,我錯了,是我飄了,呀呀呀,兄dei別打臉鴨QAQ)

但是我依舊是阻止不了你驕傲的心.

那就頂我,讓我感受感受你的驕傲!哈哈哈QAQ

2.9 IOC和DI

IOC不是什麼技術,而是一種設計思想,IOC能指導我們如何設計出松耦合、更優良的程序。傳統應用程序都是由我們在類內部主動創建依賴對象,從而導致類與類之間高耦合,難於測試;有了IoC容器后,把創建和查找依賴對象的控制權交給了Spring容器,由容器進行注入組合對象,所以對象與對象之間是鬆散耦合,這樣利於功能復用,更重要的是使得程序的整個體繫結構變得非常靈活。
IOC:控制反轉,將對象的創建權反轉給了Spring。
DI:依賴注入,前提必須有IOC的環境,Spring管理這個類的時候將類的依賴的屬性注入(設置)進來。比如說下面講到的Spring的屬性注入其實就是典型的DI

所謂繼承:is a

Class A{

}
Class B extends A{

}

所謂依賴:

Class A{

}
Class B{
    public void xxx(A a){

}
}

所謂聚合:has a

3、Spring的工廠類

3.1Spring工廠類的結構

3.2老版本的工廠類:BeanFactory

BeanFactory:調用getBean的時候,才會生成類的實例。

3.3新版本的工廠類:ApplicationContext

ApplicationContext:加載配置文件的時候,就會將Spring管理的類都實例化
ApplicationContext有兩個實現類
1、ClassPathXmlApplicationContext :加載類路徑下的配置文件
2、FileSystemXmlApplicationContext :加載文件系統下的配置文件

4、Spring的配置

4.1XML的提示配置(Schema的配置)

在XML文件中要使用各種標籤來給spring進行配置,博主我這佩奇腦袋怎麼可能記住spring中所有的標籤呢,不怕不怕,博主我會配置XML的提示配置QAQ,會了這一招就算兄dei你是喬治腦袋也不用擔心(再說了我看兄dei各各英俊瀟洒,玉樹臨風,聰明絕頂…咳咳,暴露了暴露了)

4.2Bean的相關的配置(< bean >標籤的id和name的配置)

id :使用了約束中的唯一約束。裏面不能出現特殊字符的。上面提及到了要與getbean參數值對應

name :沒有使用約束中的唯一約束(理論上可以出現重複的,但是實際開發不能出現的)。裏面可以出現特殊字符。

4.3Bean的生命周期的配置(了解)

init-method :Bean被初始化的時候執行的方法
destroy-method :Bean被銷毀的時候執行的方法(Bean是單例創建,工廠關閉)

4.4Bean的作用範圍的配置(重點)

scope屬性Bean的作用範圍

scope屬性值如下(主要用的是前二者)
singletonscope屬性的默認值,Spring會採用單例模式創建這個對象。
prototype多例模式。(Struts2和Spring整合一定會用到)
request :應用在web項目中,Spring創建這個類以後,將這個類存入到request範圍中。
session :應用在web項目中,Spring創建這個類以後,將這個類存入到session範圍中。
globalsession :應用在web項目中,必須在porlet環境下使用。但是如果沒有這種環境,相對於session。

5、Spring的屬性注入

首先,創建幾個普通類

com.gx.spring.demo.Car
public class Car {
    private String name;
    private Double price;
    
    public Car(String name, Double price) {
        super();
        this.name = name;
        this.price = price;
    }
    @Override
    public String toString() {
        return "Car [name=" + name + ", price=" + price + "]";
    }
}
com.gx.spring.demo.Car2
/**
 * 用作set方法的屬性注入類
 */
public class Car2 {
    private String name;
    private Double price;
    public void setName(String name) {
        this.name = name;
    }
    public void setPrice(Double price) {
        this.price = price;
    }
    @Override
    public String toString() {
        return "Car2 [name=" + name + ", price=" + price + "]";
    }
    
}
com.gx.spring.demo.Person 
/**
 * 用作set方法的對象屬性注入類
 */
public class Person {
    private String name;
    private Car2 car2;
    public void setName(String name) {
        this.name = name;
    }
    public void setCar2(Car2 car2) {
        this.car2 = car2;
    }
    @Override
    public String toString() {
        return "Employee [name=" + name + ", car2=" + car2 + "]";
    }
}

5.1構造方法的方式的屬性注入

構造方法的屬性注入
constructor-arg 標籤用於配置構造方法的屬性注入
name :參數的名稱
value:設置普通數據
ref:引用數據,一般是另一個bean id值

當然了,構造方法的方式的屬性注入也支持對象屬性的注入,標籤中對應屬性也是ref
如果只有一個有參數的構造方法並且參數類型與注入的bean類型匹配,那就會注入到該構造方法中

applicationContext.xml中配置:

<!-- 構造方法的方式 -->
    <bean id="car" class="com.gx.spring.demo.Car">
        <constructor-arg name="name" value="瑪莎拉蒂"/>
        <constructor-arg name="price" value="800000"/>
    </bean>

測試方法:

    /**
     * 構造方法方式的普通屬性注入方法
     */
    public void demo1(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        Car car = (Car) applicationContext.getBean("car");
        System.out.println(car);
    }

5.2Set方法的方式的屬性注入【開發常用】

Set方法的普通屬性注入
property 標籤用於配置Set方法的屬性注入
name :參數的名稱
value:設置普通數據
ref:引用數據,一般是另一個bean id值

applicationContext.xml中配置:

<!-- set方法的方式 -->
<bean id="car2" class="com.gx.spring.demo.Car2">
        <property name="name" value="法拉利黃金跑車"/>
        <property name="price" value="10000000"/>
</bean> 

測試方法:

@Test
    /**
     * set方法方式的屬性注入
     */
    public void demo2(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        Car2 car2 = (Car2) applicationContext.getBean("car2");
        System.out.println(car2);
    }

Set方法設置對象類型的屬性
applicationContext.xml中配置:

<!-- set方法注入對象類型的屬性 -->
<bean id="Person" class="com.gx.spring.demo.Person">
            <!-- value:設置普通類型的值,ref:設置其他的類的id或name-->
        <property name="name" value="濤哥"/>
        <property name="car2" ref="car2"/>
    </bean> 

測試方法:

@Test
    /**
     * set方法注入對象類型
     */
    public void demo3(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        Person person= (Person) applicationContext.getBean("Person");
        System.out.println(person);
    }

5.3註解的方式屬性注入【開發常用】

@Component (作用在類上通用:組件)
@Component(“userService”)相當於< bean id=”userService” class=”…”>

衍生:
@Controller Web層
@Service 業務層
@Repository 持久層
這三個註解是為了讓標註類本身的用途清晰

屬性注入的註解 ( 可以沒有set方法
普通類型屬性:@Value

對象類型屬性:@Resource對應bean中的id)= @Autowired(類型)+ @Qualifier(名稱)

5.3.1註解的理解

額,初學框架,註解二字可能對於大部分熊dei來說,太過於陌生,註解其實就是在一個類、方法、屬性上,使用@註解名稱,就比如是我們最熟悉的接實現口中的方法默認會有一個 @Override (熊dei,這樣理解能接受?)

5.3.2註解的jar包導入

Spring3.x註解的jar包
在Spring3.x的版本中,使用註解開發,只需要 spring核心基礎四包外 + log4j包 + 1個依賴包 即可

Spring4.x註解的jar包
然而在Spring4.x版本之後則需在 再添加一個要引入 spring-aop 的 jar 包,因為,spring4.x版本中一些註解的依賴方法封裝在spring-aop 的 jar 包中

5.3.3引入註解的context約束

所謂約束就是就是就是約束啦(搽汗),其中bean約束是最基本的約束!(下圖也可以看出)

引入約束:(引入 context 的約束):

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
5.3.4編寫相關的類
public interface UserDao {
public void sayHello();
}
public class UserDaoImpl implements UserDao {
@Override
public void sayHello() {
System.out.println("Hello Spring...");
} }
5.3.5配置註解掃描

Spring的註解開發:組件掃描(不使用類上註解的時候可以不用組件掃描
使用註解方式,需要開啟組件掃描< context:component-scan base-package=直接包名或者包名.類名/>,當然開發中一般都是base-package=包名,畢竟這樣可以掃描整個包,方便開發
Spring 的註解開發:組件掃描(類上註解: 可以直接使用屬性注入的註解)

<!-- Spring 的註解開發:組件掃描(類上註解: 可以直接使用屬性注入的註解) -->
<context:component-scan base-package="com.gx.spring.demo1"/>
5.3.6 在相關的類上添加註解

1、使用類上註解方式@Component(value=“userDao”),相當於< bean id=”userDao class=”com.gx.類名”>< /bean>
當然value屬性名可以省去直接@Component(”userDao”),當然@Component(“value值任意寫建議取的要有意義”)
2、註解方式可以沒有set方法

@Component(value="userDao")  //相當於配置了<bean id="userDao" class="com.gx.UserDaoImpl "></bean>
public class UserDaoImpl implements UserDao {
@Override
public void sayHello() {
System.out.println("Hello Spring Annotation...");
} }
5.3.7 編寫測試類
@Test
public void demo3() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.sayHello();
}

5.4P名稱空間的屬性注入(Spring2.5以後)

通過引入p名稱空間完成屬性的注入:
寫法:
普通屬性 p:屬性名=”值”
對象屬性 p:屬性名-ref=”值”

P名稱空間的約束引入

使用p名稱空間

5.5 SpEL的屬性注入(Spring3.0以後)

SpEL:Spring Expression Language,Spring的表達式語言。
語法: #{SpEL}

5.6集合類型屬性注入(了解)

集合類型屬性配置:
集合的注入都是在< property>標籤中添加子標籤
數組:< array >
List:< list >
Set:< set >
Map:< map > ,map存放k/v 鍵值對,使用 描述
Properties:< props> < prop key=””>< /prop>
普通數據:< value >
引用數據:< ref >

    <!-- Spring的集合屬性的注入============================ -->
    <!-- 注入數組類型 -->
    <bean id="collectionBean" class="com.gx.spring.demo.CollectionBean">
        <!-- 數組類型 -->
        <property name="arrs">
            <list>
                <value>天才</value>
                <value>王二</value>
                <value>冠希</value>
            </list>
        </property>
        
        <!-- 注入list集合 -->
        <property name="list">
            <list>
                <value>李兵</value>
                <value>趙如何</value>
                <value>鄧鳳</value>
            </list>
        </property>
        
        <!-- 注入set集合 -->
        <property name="set">
            <set>
                <value>aaa</value>
                <value>bbb</value>
                <value>ccc</value>
            </set>
        </property>
        
        <!-- 注入Map集合 -->
        <property name="map">
            <map>
                <entry key="aaa" value="111"/>
                <entry key="bbb" value="222"/>
                <entry key="ccc" value="333"/>
            </map>
        </property>
    </bean>

6、Spring的分模塊開發的配置

分模塊配置:
在加載配置文件的時候,加載多個,沒錯,這就是傳說中的騷操作,堪稱開掛級別的操作(當然,這是可以的不是開掛)

在一個配置文件中引入多個配置文件,簡直優秀!!!

到這裏,恭喜恭喜,各位熊dei以優雅的儀式感闖進Spring世界,對Spring的IOC以及DI有了一定了解了,是不是也很期待Spring的Aop吶,畢竟Spring的核心是控制反轉(IOC)和面向切面(AOP)。

(哎哎,別打..別打..別打臉….)

如果本文對你有一點點幫助,就請點個讚唄,手留余香,謝謝!

最後,歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術…

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

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

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

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

分類
發燒車訊

PL真有意思(一):引言

前言

斷斷續續學編譯原理到之前發過寫一個編譯器和正則表達式引擎系列文章也有一段時間了,然後最近看完PLP這本書,這本書應該算是入門書,但是對我這種半吊子收穫很大。所以為了彌補最近學操作系統和接外包摸的魚,就想寫寫看完這本書的收穫。(為拙劣的標題道歉

程序設計語言的譜系

現在的新語言都是一撮一撮的出來,但是基本都可以用他們的計算模型來分成兩類,一類是更關心計算機做什麼的說明式,一類是更關心計算機怎麼做的命令式

一般認為像函數式邏輯式語言都算是說明式,而馮諾依曼式和面向對象的都被認為是命令式

函數式

函數式是基於函數的遞歸定義的計算模型,一般從本質上來看,函數式把程序認為是一種從輸入到輸出的函數,使用更簡單的函數通過逐步細化的過程來定義

邏輯式

邏輯式里經典的應該就是Prolog了,邏輯式一般將計算看作是一種找出滿足某些特定關係的值的嘗試過程

馮諾依曼式

馮諾依曼式最重要的就是通過副作用也就是修改寄存器里的值來對後續計算產生影響,像C和Fortran都屬於馮諾依曼式

幾個例子

如果C語言來實現求最大公約數,可以發現C語言偏向通過迭代和反覆修改變量的值來實現

int gcd(int a, intb) {
    while (a != b) {
      if (a > b) {
        a = a - b;
      } else {
        b = b - a;
      }
    }
}

從lisp來看,則更加關注輸入和輸出的數學關係,要算出最大公約數,需要對函數的不斷的擴充和精簡

(define gcd
  (lambda (a b)
    (cond ((= a b) a)
          ((> a b) (gcd (- a b) b))
          (else (gcd (- b a) a)))))

對於像C或者Java入門的人,看到Prolog可能頭都大了,因為Prolog和命令式的思考邏輯完全不同,邏輯式更傾向給出一組公理和檢測規則,期望系統能給出相應合理的值,我也僅限於能看懂這些小程序

gcd(A,B,G) :- A = B, G = A.
gcd(A,B,G) :- A > B, C is A - B, gcd(C,B,G)
gcd(A,B,G) :- B > A, C is B-A
gcd(C,A,G)

編譯和解釋

下面再看兩個概念,編譯和解釋。

編譯一般是指從一個語言到另一個語言的翻譯,無論是高級語言到彙編還是高級語言到高級語言都算是編譯。而解釋就是直接執行代碼,但是現代的解釋器,一般還有虛擬機一層,即翻譯到虛擬機指令再由虛擬機進行執行

自舉

很多語言的編譯器都是由自己編譯而成的,所以問題就是,最開始的編譯器是怎麼編譯的?

假設現在要為Java編寫一個編譯器,我們可以先用C語言編寫一個Java小子集的編譯器,然後再手動將C語言翻譯到這個Java子集,就可以由這個子集編譯運行自己,然後就可以不斷擴充這個編譯器

編譯概覽

其實這個在之前那個寫編譯器的系列是說得最詳細的,這個系列是想要寫寫筆記對實踐和語言設計結合的說。

  • 詞法分析

有關詞法分析其實就是將字符流化成單詞流,記錄每個單詞的信息,然後通常還會有其它更多的信息讓編譯器更好的生成錯誤信息

  • 語法分析

語法分析通常會用到一個概念:上下文無關文法,就是用來定義語法形式的,比如while語句就可以這樣表示

while-statment := WHILE ( expr ) statment

一般語法分析過程最後的輸出都是樹狀結構

  • 語義分析和中間代碼生成

語法分析只保證源代碼語法格式的正確,但是卻不能保證其它的正確性,比如對於靜態類型的語言,可能就會在編譯時直接檢測出類型錯誤

而在語義分析過程一般還需要藉助一個叫做符號表的數據結構,這個符號表將每個標識符都映射到關於它的已知信息

中間代碼的生成通常是會將樹形結構翻譯成更接近彙編的中間線性格式,但是中間格式不是必須的,比如之前那個系列里還寫了C的解釋器,雖然很殘缺,它是沒有中間代碼的,連虛擬機都沒有,是直接進行遍歷語法樹進行執行解釋的

  • 代碼優化

有些代碼改進是機器無關的,即可以在中間格式上就進行優化,但是有的代碼優化是平台相關的,所以就需要在最後對目標語言優化

  • 目標代碼生成

代碼生成階段就是根據之前生成的線性結構和符號表信息對目標代碼的生成

小結

這一篇主要說了幾個範式的語言和編譯過程的概括,對摸魚進行懺悔

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

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

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

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

分類
發燒車訊

美國原油均價下跌,市場憂產油過剩

美國能源資訊局(EIA)公佈,6月26日全美普通汽油平均零售價格為每加侖2.288美元,創下半年新低;較前週下跌3美分,較去年同期下跌4.1美分。各地區零售汽油均價全面下跌,西岸地區的零售汽油均價最高達每加侖2.826美元,較前週下跌3.4美分;墨西哥灣地區的零售汽油均價最低為每加侖2.053美元,較前週下跌3.8美分。NYMEX原油期貨上週下跌3.9%,因擔憂油市過剩以及美國產量持續增長的影響。

美國汽車協會(AAA)報告表示,6月26日全美普通無鉛汽油平均零售價格為每加侖2.26美元,較前週下跌3美分,較一個月前下跌11美分,較去年同期下跌4美分。AAA表示,包括美國煉油廠原油加工量處在新高水平、汽油以及原油庫存高企,以及今年以來的需求表現較為疲弱等,都是造成零售汽油均價下跌的主因。美國汽油需求已經有所回升,6月16日當週,美國汽油日均需求較前週926.9萬桶增至981.6萬桶,逼近5月底創下新高的982.2萬桶。

AAA表示,即將到來的美國獨立紀念日假期(7月4日),預計將有創同期新高的4,420萬人出遊(離家超過50英里),比去年還要增加125萬人或2.9%。其中,預計將有3,750萬人開車出遊,同樣較去年同期增加2.9%。AAA資深副總裁Bill Sutherland表示,就業市場強勁、薪資增加以及消費信心提高等,都是今年出遊人數將創下歷年同期新高的主要原因。AAA表示,當前美國零售汽油均價逼近歷年的同期新低,但鑑於下週的假期來臨,零售汽油均價可能會有小幅上漲。

《Oilprice.com》報導,相比十年前在油田自然衰竭的影響下,市場認為全球的產油上限即將到來;如今市場更多的是認為石油消費的巔峰將會到來,主要因為電動車興起的影響。一份調查顯示,如果電動車的年增長率維持在60%,則2023年的全球石油日需求量將會比當前減少200萬桶;如果年增長率為30%,則2025年的全球石油日需求量會比當前減少200萬桶。

不過,實際數據顯示,至少目前為止石油需求增長並未下滑;過去十年全球石油日需求量年均增長110萬桶,過去五年則年均增長140萬桶,2016年則增長160萬桶,而去年的電動車銷售增長41%。報導認為,包括人口以及中產階級增長,以及開發中國家汽車銷售持續增加等,都是令全球石油需求仍持續攀高的主因。

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

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

【其他文章推薦】

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

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

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

分類
發燒車訊

使用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的網絡文章即可。

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

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

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

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