FFMpeg.AutoGen(2)講解官方example代碼:編碼


官方Example代碼里編碼的作業:把上一講解碼出來的jpg圖片,編碼成264格式的文件。

編碼的流程,也是本文目錄

1.把圖片轉換byte[]

2.把圖片轉換成yuv格式,封裝成avframe。

3.編碼。

先貼出編碼的主函數。函數里分析下來,就是通過調用相關類和函數實現的目錄三步。

  1         /// <summary>
  2         /// 編碼 把解碼出來的jpg文件,再編碼成UV420P
  3         /// </summary>
  4         private static unsafe void EncodeImagesToH264()
  5         {
  6 
  7             //獲取解碼出來的文件隊列
  8             var frameFiles = Directory.GetFiles(".", "frame.*.jpg").OrderBy(x => x).ToArray();
  9             //獲取第一張幀圖片
 10             var fistFrameImage = Image.FromFile(frameFiles.First());
 11 
 12             //設置導出媒體信息
 13             var outputFileName = "out.h264";
 14             var fps = 25;
 15             var sourceSize = fistFrameImage.Size;
 16             var sourcePixelFormat = AVPixelFormat.AV_PIX_FMT_BGR24;
 17             var destinationSize = sourceSize;
 18             var destinationPixelFormat = AVPixelFormat.AV_PIX_FMT_YUV420P;
 19             //創建格式轉換其 把rgb 轉變成yuv ,同時對分辨率進行縮放
 20             using (var vfc = new VideoFrameConverter(sourceSize, sourcePixelFormat, destinationSize, destinationPixelFormat))
 21             {
 22                 // be advise only ffmpeg based player (like ffplay or vlc) can play this file, for the others you need to go through muxing
 23                 //建議基於ffmpeg的播放器播放這個文件out.h264,否則需要多路復用技術
 24                 //這個文件就是用ffmpeg把rgb圖片轉變成264的一個個幀。
 25                 using (var fs = File.Open(outputFileName, FileMode.Create))
 26 
 27                 {
 28                     //創建264轉換 把要保存的文件句柄fs 幀率fps 源大小destinationSize 傳入
 29                     using (var vse = new H264VideoStreamEncoder(fs, fps, destinationSize))
 30                     {
 31                         var frameNumber = 0;
 32                         //讀取每一張圖片,作為一幀
 33                         foreach (var frameFile in frameFiles)
 34                         {
 35                             byte[] bitmapData;
 36 
 37                             using (var frameImage = Image.FromFile(frameFile))
 38                             using (var frameBitmap = frameImage is Bitmap bitmap ? bitmap : new Bitmap(frameImage))// is 后面接變量申明 這個寫法比較有意思
 39                             {
 40                                 bitmapData = GetBitmapData(frameBitmap);
 41                             }
 42                             //固化pBitmapData內存地址
 43                             fixed (byte* pBitmapData = bitmapData)
 44                             {
 45                                 //指針數組用於保存指向幀實際內存空間的地址
 46                                 var data = new byte_ptrArray8 { [0] = pBitmapData };
 47                                 //每行大小
 48                                 var linesize = new int_array8 { [0] = bitmapData.Length / sourceSize.Height };
 49                                 var frame = new AVFrame
 50                                 {
 51                                     data = data,
 52                                     linesize = linesize,
 53                                     height = sourceSize.Height
 54                                 };
 55                                 //把rgb轉換為yuv,同時對分辨率進行縮放
 56                                 var convertedFrame = vfc.Convert(frame);
 57                                 //設置時間戳 幀的序號 x 幀率
 58                                 convertedFrame.pts = frameNumber * fps;
 59                                 //把yuv420p編碼成264,並寫到 "out.h264"文件中
 60                                 vse.Encode(convertedFrame);
 61                             }
 62 
 63                             Console.WriteLine($"frame: {frameNumber}");
 64                             frameNumber++;
 65                         }
 66                     }
 67                 }
 68             }
 69         }
 70 

1.把圖片轉換byte[]

主要使用這個函數:bitmapData = GetBitmapData(frameBitmap); 函數簡單代碼很少,直接看代碼和我的注釋即可。

  1         /// <summary>
  2         /// 把bitmap轉換為byte[]
  3         /// 從Scan0開始把每個像素字節返回成數組byte[]
  4         /// </summary>
  5         /// <param name="frameBitmap"></param>
  6         /// <returns></returns>
  7         private static byte[] GetBitmapData(Bitmap frameBitmap)
  8         {
  9             var bitmapData = frameBitmap.LockBits(new Rectangle(Point.Empty, frameBitmap.Size), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
 10             try
 11             {
 12                 //Stride像素實際占據字節長度
 13                 var length = bitmapData.Stride * bitmapData.Height;
 14                 var data = new byte[length];
 15                 //Scan0 放位圖像素內存中的第一個地址
 16                 Marshal.Copy(bitmapData.Scan0, data, 0, length);
 17                 return data;
 18             }
 19             finally
 20             {
 21                 frameBitmap.UnlockBits(bitmapData);
 22             }
 23         }
 24     }

2.把圖片轉換成yuv格式,封裝成avframe。

使用VideoFrameConverter類,進行圖像格式轉換從rgb轉為yuv,此類在上一篇有具體描述這里不再闡述:

var sourcePixelFormat = AVPixelFormat.AV_PIX_FMT_BGR24;

var destinationPixelFormat = AVPixelFormat.AV_PIX_FMT_YUV420P;

var vfc = new VideoFrameConverter(sourceSize, sourcePixelFormat, destinationSize, destinationPixelFormat)

從上面代碼看到,在申明和實例化時,告訴VideoFrameConverter對象源格式24bit rgb 需要轉換為 yuv420p。同時指定了源和目的的尺寸,可以縮放。

並通過vfc.Convert(frame)轉換成yuv格式。返回的數據是封裝好的AVFrame格式。

3.編碼

通過實現H264VideoStreamEncoder類實現編碼264。編碼的核心使用的是依舊是解碼器AVCodecContext此時它應該被成為編碼器。因為是編碼,所以沒有解碼獲取媒體信息獲取有效流索引這些操作。建議閱讀源碼。文件末尾附筆者注釋過的源碼。

流程為

3.1.創建編碼器

通過ffmpeg.avcodec_alloc_context3(_pCodec)創建AVCodecContext。這里的_pCodec是通過ffmpeg.avcodec_find_encoder(AVCodecID.AV_CODEC_ID_H264)獲取的。

3.2.配置編碼器

配置AVCodecContext的width\height\time_base(時間戳基准,這里是1/fps ,時間基准的詳細內容可看參考文檔【2】)\pix_fmt(幀像素格式,也就是源格式)\設置參數preset 值為veryslow(264的參數,使用函數ffmpeg.av_opt_set(_pCodecContext->priv_data, "preset", "veryslow", 0)設置)。

3.3.打開編碼器

ffmpeg.avcodec_open2(_pCodecContext, _pCodec, null)。


4.輪詢編碼

實例外,循環調用Encode(AVFrame)進行編碼。合計3步,編碼2步,寫入文件流1步:

1)放入編碼器ffmpeg.avcodec_send_frame(_pCodecContext, &frame)

2)從編碼器讀取編碼后的幀 通過參數返回 error = ffmpeg.avcodec_receive_packet(_pCodecContext, pPacket)。

3)把編碼后的AVPacket包格式用UnmanagedMemoryStream寫入文件流。


參考文檔:

【1】FFmpeg X264的preset和tune 2017-05-25 Lerry.Zhao

【2】ffmpeg里time_base總結 2016-11-02 耕地

附件:

  1 using System;
  2 using System.Drawing;
  3 using System.IO;
  4 
  5 namespace FFmpeg.AutoGen.Example
  6 {
  7      /// <summary>
  8      /// H264轉換類
  9      /// </summary>
 10     public sealed unsafe class H264VideoStreamEncoder : IDisposable
 11     {
 12         private readonly Size _frameSize;
 13         private readonly int _linesizeU;
 14         private readonly int _linesizeV;
 15         private readonly int _linesizeY;
 16         private readonly AVCodec* _pCodec;
 17         private readonly AVCodecContext* _pCodecContext;
 18         private readonly Stream _stream;
 19         private readonly int _uSize;
 20         private readonly int _ySize;
 21 
 22         /// <summary>
 23         /// 構造H264VideoStreamEncoder
 24         /// </summary>
 25         /// <param name="stream">轉換源流</param>
 26         /// <param name="fps">幀率信息</param>
 27         /// <param name="frameSize">幀大小</param>
 28         public H264VideoStreamEncoder(Stream stream, int fps, Size frameSize)
 29         {
 30             _stream = stream;
 31             _frameSize = frameSize;
 32 
 33             var codecId = AVCodecID.AV_CODEC_ID_H264;
 34             _pCodec = ffmpeg.avcodec_find_encoder(codecId);
 35             if (_pCodec == null) throw new InvalidOperationException("Codec not found.");
 36             //根據解碼器分配一個AVCodecContext ,僅僅分配工具,還沒有初始化。
 37             _pCodecContext = ffmpeg.avcodec_alloc_context3(_pCodec);
 38             //配置解碼器格式信息
 39             _pCodecContext->width = frameSize.Width;
 40             _pCodecContext->height = frameSize.Height;
 41             _pCodecContext->time_base = new AVRational {num = 1, den = fps};
 42             _pCodecContext->pix_fmt = AVPixelFormat.AV_PIX_FMT_YUV420P;
 43             //設置參數preset 值為veryslow 配置264的參數 
 44             ffmpeg.av_opt_set(_pCodecContext->priv_data, "preset", "veryslow", 0);
 45             //打開編碼器
 46             ffmpeg.avcodec_open2(_pCodecContext, _pCodec, null).ThrowExceptionIfError();
 47             //每一行yuv的大小 每個像素的y值是記錄的。相鄰兩行的各兩個像素共享一個UV
 48             _linesizeY = frameSize.Width;
 49             _linesizeU = frameSize.Width / 2;
 50             _linesizeV = frameSize.Width / 2;
 51             //y的大小就是像素的數量 uv只有像素的1/4 四個像素共享一個uv
 52             _ySize = _linesizeY * frameSize.Height;
 53             _uSize = _linesizeU * frameSize.Height / 2;
 54         }
 55 
 56         public void Dispose()
 57         {
 58             ffmpeg.avcodec_close(_pCodecContext);
 59             ffmpeg.av_free(_pCodecContext);
 60             ffmpeg.av_free(_pCodec);
 61         }
 62 
 63         /// <summary>
 64         /// 編碼成264格式
 65         /// </summary>
 66         /// <param name="frame">源幀</param>
 67         public void Encode(AVFrame frame)
 68         {
 69             if (frame.format != (int) _pCodecContext->pix_fmt) throw new ArgumentException("Invalid pixel format.", nameof(frame));
 70             if (frame.width != _frameSize.Width) throw new ArgumentException("Invalid width.", nameof(frame));
 71             if (frame.height != _frameSize.Height) throw new ArgumentException("Invalid height.", nameof(frame));
 72             if (frame.linesize[0] != _linesizeY) throw new ArgumentException("Invalid Y linesize.", nameof(frame));
 73             if (frame.linesize[1] != _linesizeU) throw new ArgumentException("Invalid U linesize.", nameof(frame));
 74             if (frame.linesize[2] != _linesizeV) throw new ArgumentException("Invalid V linesize.", nameof(frame));
 75             if (frame.data[1] - frame.data[0] != _ySize) throw new ArgumentException("Invalid Y data size.", nameof(frame));
 76             if (frame.data[2] - frame.data[1] != _uSize) throw new ArgumentException("Invalid U data size.", nameof(frame));
 77 
 78             //創建AVPacket包
 79             var pPacket = ffmpeg.av_packet_alloc();
 80             try
 81             {
 82                 int error;
 83                 do
 84                 {
 85                     //把幀放入解碼器
 86                     ffmpeg.avcodec_send_frame(_pCodecContext, &frame).ThrowExceptionIfError();
 87                     //從解碼器里讀取幀,放到pPacket包里
 88                     error = ffmpeg.avcodec_receive_packet(_pCodecContext, pPacket);
 89                 } while (error == ffmpeg.AVERROR(ffmpeg.EAGAIN));
 90 
 91                 error.ThrowExceptionIfError();
 92                 //UnmanagedMemoryStream 類提供從托管代碼訪問非托管內存塊的能
 93                 //把包里的數據寫入_stream(構造函數傳入)
 94                 using (var packetStream = new UnmanagedMemoryStream(pPacket->data, pPacket->size)) packetStream.CopyTo(_stream);
 95             }
 96             finally
 97             {
 98                 ffmpeg.av_packet_unref(pPacket);
 99             }
100         }
101     }
102 }
View Code


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM